Skip to content

Commit a538aca

Browse files
authored
Merge pull request #393 from lilizoey/feature/editor-plugin
Add editor plugins, and registration of classes at the proper initialization level
2 parents 0d4670f + e05dc40 commit a538aca

File tree

14 files changed

+137
-15
lines changed

14 files changed

+137
-15
lines changed

godot-codegen/src/class_generator.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
506506

507507
let constructor = make_constructor(class, ctx);
508508
let api_level = util::get_api_level(class);
509+
let init_level = api_level.to_init_level();
509510

510511
let FnDefinitions {
511512
functions: methods,
@@ -618,6 +619,7 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
618619
type Base = #base_ty;
619620
type Declarer = crate::obj::dom::EngineDomain;
620621
type Mem = crate::obj::mem::#memory;
622+
const INIT_LEVEL: Option<crate::init::InitLevel> = #init_level;
621623

622624
fn class_name() -> ClassName {
623625
ClassName::from_ascii_cstr(#class_name_cstr)

godot-codegen/src/codegen_special_cases.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ const SELECTED_CLASSES: &[&str] = &[
127127
"CollisionObject2D",
128128
"CollisionShape2D",
129129
"Control",
130+
"EditorPlugin",
130131
"Engine",
131132
"FileAccess",
132133
"HTTPRequest",

godot-codegen/src/special_cases.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,15 @@ pub(crate) fn is_class_deleted(class_name: &TyName) -> bool {
7171

7272
match class_name {
7373
// Hardcoded cases that are not accessible.
74-
| "JavaClassWrapper" // only on Android.
75-
| "JavaScriptBridge" // only on WASM.
76-
| "ThemeDB" // lazily loaded; TODO enable this.
74+
// Only on Android.
75+
| "JavaClassWrapper"
76+
| "JNISingleton"
77+
| "JavaClass"
78+
// Only on WASM.
79+
| "JavaScriptBridge"
80+
| "JavaScriptObject"
81+
// lazily loaded; TODO enable this.
82+
| "ThemeDB"
7783

7884
// Thread APIs.
7985
| "Thread"
@@ -124,7 +130,6 @@ fn is_class_experimental(class_name: &TyName) -> bool {
124130
| "NavigationRegion3D"
125131
| "NavigationServer2D"
126132
| "NavigationServer3D"
127-
| "ProjectSettings"
128133
| "SkeletonModification2D"
129134
| "SkeletonModification2DCCDIK"
130135
| "SkeletonModification2DFABRIK"

godot-codegen/src/util.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ impl ClassCodegenLevel {
6666
Self::Lazy => unreachable!("lazy classes should be deleted at the moment"),
6767
}
6868
}
69+
70+
pub fn to_init_level(self) -> TokenStream {
71+
match self {
72+
Self::Servers => quote! { Some(crate::init::InitLevel::Servers) },
73+
Self::Scene => quote! { Some(crate::init::InitLevel::Scene) },
74+
Self::Editor => quote! { Some(crate::init::InitLevel::Editor) },
75+
Self::Lazy => quote! { None },
76+
}
77+
}
6978
}
7079

7180
// ----------------------------------------------------------------------------------------------------------------------------------------------

godot-core/src/init/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,12 @@ fn gdext_on_level_init(level: InitLevel) {
9292
}
9393
InitLevel::Scene => {
9494
sys::load_class_method_table(sys::ClassApiLevel::Scene);
95-
crate::auto_register_classes();
9695
}
9796
InitLevel::Editor => {
9897
sys::load_class_method_table(sys::ClassApiLevel::Editor);
9998
}
10099
}
100+
crate::auto_register_classes(level);
101101
}
102102
}
103103

godot-core/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ pub mod private {
168168
use std::io::Write;
169169
std::io::stdout().flush().expect("flush stdout");
170170
}
171+
172+
/// Ensure `T` is an editor plugin.
173+
pub const fn is_editor_plugin<T: crate::obj::Inherits<crate::engine::EditorPlugin>>() {}
171174
}
172175

173176
macro_rules! generate_gdextension_api_version {

godot-core/src/obj/traits.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use crate::builder::ClassBuilder;
88
use crate::builtin::GodotString;
9+
use crate::init::InitLevel;
910
use crate::obj::Base;
1011

1112
use crate::builtin::meta::ClassName;
@@ -34,6 +35,12 @@ where
3435
/// Defines the memory strategy.
3536
type Mem: mem::Memory;
3637

38+
/// During which initialization level this class is available/should be initialized with Godot.
39+
///
40+
/// Is `None` if the class has complicated initialization requirements, and generally cannot be inherited
41+
/// from.
42+
const INIT_LEVEL: Option<InitLevel>;
43+
3744
/// The name of the class, under which it is registered in Godot.
3845
///
3946
/// This may deviate from the Rust struct name: `HttpRequest::class_name().as_str() == "HTTPRequest"`.
@@ -60,6 +67,7 @@ unsafe impl GodotClass for () {
6067
type Base = ();
6168
type Declarer = dom::EngineDomain;
6269
type Mem = mem::ManualMemory;
70+
const INIT_LEVEL: Option<InitLevel> = None;
6371

6472
fn class_name() -> ClassName {
6573
ClassName::none()

godot-core/src/registry.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#![allow(dead_code)] // FIXME
88

9+
use crate::init::InitLevel;
10+
use crate::log;
911
use crate::obj::*;
1012
use crate::private::as_storage;
1113
use crate::storage::InstanceStorage;
@@ -29,6 +31,7 @@ use std::{fmt, ptr};
2931
pub struct ClassPlugin {
3032
pub class_name: ClassName,
3133
pub component: PluginComponent,
34+
pub init_level: Option<InitLevel>,
3235
}
3336

3437
/// Type-erased function object, holding a `register_class` function.
@@ -109,6 +112,9 @@ pub enum PluginComponent {
109112
p_name: sys::GDExtensionConstStringNamePtr,
110113
) -> sys::GDExtensionClassCallVirtual,
111114
},
115+
116+
#[cfg(since_api = "4.1")]
117+
EditorPlugin,
112118
}
113119

114120
// ----------------------------------------------------------------------------------------------------------------------------------------------
@@ -120,6 +126,8 @@ struct ClassRegistrationInfo {
120126
generated_register_fn: Option<ErasedRegisterFn>,
121127
user_register_fn: Option<ErasedRegisterFn>,
122128
godot_params: sys::GDExtensionClassCreationInfo,
129+
init_level: InitLevel,
130+
is_editor_plugin: bool,
123131
}
124132

125133
/// Registers a class with static type information.
@@ -128,7 +136,8 @@ pub fn register_class<
128136
+ cap::ImplementsGodotVirtual
129137
+ cap::GodotToString
130138
+ cap::GodotNotification
131-
+ cap::GodotRegisterClass,
139+
+ cap::GodotRegisterClass
140+
+ GodotClass,
132141
>() {
133142
// TODO: provide overloads with only some trait impls
134143

@@ -155,22 +164,33 @@ pub fn register_class<
155164
raw: callbacks::register_class_by_builder::<T>,
156165
}),
157166
godot_params,
167+
init_level: T::INIT_LEVEL.unwrap_or_else(|| {
168+
panic!("Unknown initialization level for class {}", T::class_name())
169+
}),
170+
is_editor_plugin: false,
158171
});
159172
}
160173

161174
/// Lets Godot know about all classes that have self-registered through the plugin system.
162-
pub fn auto_register_classes() {
163-
out!("Auto-register classes...");
175+
pub fn auto_register_classes(init_level: InitLevel) {
176+
out!("Auto-register classes at level `{init_level:?}`...");
164177

165178
// Note: many errors are already caught by the compiler, before this runtime validation even takes place:
166179
// * missing #[derive(GodotClass)] or impl GodotClass for T
167180
// * duplicate impl GodotInit for T
168181
//
169-
170182
let mut map = HashMap::<ClassName, ClassRegistrationInfo>::new();
171183

172184
crate::private::iterate_plugins(|elem: &ClassPlugin| {
173185
//out!("* Plugin: {elem:#?}");
186+
match elem.init_level {
187+
None => {
188+
log::godot_error!("Unknown initialization level for class {}", elem.class_name);
189+
return;
190+
}
191+
Some(elem_init_level) if elem_init_level != init_level => return,
192+
_ => (),
193+
}
174194

175195
let name = elem.class_name;
176196
let class_info = map
@@ -183,11 +203,14 @@ pub fn auto_register_classes() {
183203
//out!("Class-map: {map:#?}");
184204

185205
for info in map.into_values() {
186-
out!("Register class: {}", info.class_name);
206+
out!(
207+
"Register class: {} at level `{init_level:?}`",
208+
info.class_name
209+
);
187210
register_class_raw(info);
188211
}
189212

190-
out!("All classes auto-registered.");
213+
out!("All classes for level `{init_level:?}` auto-registered.");
191214
}
192215

193216
/// Populate `c` with all the relevant data from `component` (depending on component type).
@@ -227,6 +250,10 @@ fn fill_class_info(component: PluginComponent, c: &mut ClassRegistrationInfo) {
227250
c.godot_params.notification_func = user_on_notification_fn;
228251
c.godot_params.get_virtual_func = Some(get_virtual_fn);
229252
}
253+
#[cfg(since_api = "4.1")]
254+
PluginComponent::EditorPlugin => {
255+
c.is_editor_plugin = true;
256+
}
230257
}
231258
// out!("| reg (after): {c:?}");
232259
// out!();
@@ -282,6 +309,13 @@ fn register_class_raw(info: ClassRegistrationInfo) {
282309
if let Some(register_fn) = info.user_register_fn {
283310
(register_fn.raw)(&mut class_builder);
284311
}
312+
313+
#[cfg(since_api = "4.1")]
314+
if info.is_editor_plugin {
315+
unsafe { interface_fn!(editor_add_plugin)(class_name.string_sys()) };
316+
}
317+
#[cfg(before_api = "4.1")]
318+
assert!(!info.is_editor_plugin);
285319
}
286320

287321
/// Callbacks that are passed as function pointers to Godot upon class registration.
@@ -444,6 +478,8 @@ fn default_registration_info(class_name: ClassName) -> ClassRegistrationInfo {
444478
generated_register_fn: None,
445479
user_register_fn: None,
446480
godot_params: default_creation_info(),
481+
init_level: InitLevel::Scene,
482+
is_editor_plugin: false,
447483
}
448484
}
449485

godot-macros/src/class/derive_godot_class.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult<TokenStream> {
3333
let prv = quote! { ::godot::private };
3434
let godot_exports_impl = make_property_impl(class_name, &fields);
3535

36+
let editor_plugin = if struct_cfg.is_editor_plugin {
37+
quote! {
38+
::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
39+
class_name: #class_name_obj,
40+
component: #prv::PluginComponent::EditorPlugin,
41+
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
42+
});
43+
44+
const _: () = #prv::is_editor_plugin::<#class_name>();
45+
}
46+
} else {
47+
quote! {}
48+
};
49+
3650
let (godot_init_impl, create_fn);
3751
if struct_cfg.has_generated_init {
3852
godot_init_impl = make_godot_init_impl(class_name, fields);
@@ -49,6 +63,7 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult<TokenStream> {
4963
type Base = #base_class;
5064
type Declarer = ::godot::obj::dom::UserDomain;
5165
type Mem = <Self::Base as ::godot::obj::GodotClass>::Mem;
66+
const INIT_LEVEL: Option<::godot::init::InitLevel> = <#base_class as ::godot::obj::GodotClass>::INIT_LEVEL;
5267

5368
fn class_name() -> ::godot::builtin::meta::ClassName {
5469
::godot::builtin::meta::ClassName::from_ascii_cstr(#class_name_cstr)
@@ -66,8 +81,11 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult<TokenStream> {
6681
generated_create_fn: #create_fn,
6782
free_fn: #prv::callbacks::free::<#class_name>,
6883
},
84+
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
6985
});
7086

87+
#editor_plugin
88+
7189
#prv::class_macros::#inherits_macro!(#class_name);
7290
})
7391
}
@@ -89,6 +107,7 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
89107
let mut base_ty = ident("RefCounted");
90108
let mut has_generated_init = false;
91109
let mut is_tool = false;
110+
let mut is_editor_plugin = false;
92111

93112
// #[class] attribute on struct
94113
if let Some(mut parser) = KvParser::parse(&class.attributes, "class")? {
@@ -104,13 +123,19 @@ fn parse_struct_attributes(class: &Struct) -> ParseResult<ClassAttributes> {
104123
is_tool = true;
105124
}
106125

126+
// TODO: better error message when using in Godot 4.0
127+
if parser.handle_alone_ident("editor_plugin")?.is_some() {
128+
is_editor_plugin = true;
129+
}
130+
107131
parser.finish()?;
108132
}
109133

110134
Ok(ClassAttributes {
111135
base_ty,
112136
has_generated_init,
113137
is_tool,
138+
is_editor_plugin,
114139
})
115140
}
116141

@@ -190,6 +215,7 @@ struct ClassAttributes {
190215
base_ty: Ident,
191216
has_generated_init: bool,
192217
is_tool: bool,
218+
is_editor_plugin: bool,
193219
}
194220

195221
fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {

godot-macros/src/class/godot_api.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ fn transform_inherent_impl(mut decl: Impl) -> Result<TokenStream, Error> {
193193
raw: #prv::callbacks::register_user_binds::<#class_name>,
194194
},
195195
},
196+
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
196197
});
197198
};
198199

@@ -505,6 +506,7 @@ fn transform_trait_impl(original_impl: Impl) -> Result<TokenStream, Error> {
505506
user_on_notification_fn: #on_notification_fn,
506507
get_virtual_fn: #prv::callbacks::get_virtual::<#class_name>,
507508
},
509+
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
508510
});
509511
};
510512

0 commit comments

Comments
 (0)