From a0f8f28aa045d12af260fa3d17c4ceb1e727a3c0 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Tue, 20 Feb 2024 22:07:29 +0100 Subject: [PATCH] For v4.3+, switch #[class(tool)] to use Godot's native "runtime class" feature --- godot-core/src/private.rs | 16 ++++ godot-core/src/registry/mod.rs | 98 ++++++++++++++------ godot-ffi/src/binding/multi_threaded.rs | 3 + godot-macros/src/class/derive_godot_class.rs | 3 + godot-macros/src/class/godot_api.rs | 6 ++ godot-macros/src/util/mod.rs | 6 ++ 6 files changed, 103 insertions(+), 29 deletions(-) diff --git a/godot-core/src/private.rs b/godot-core/src/private.rs index 019637a96..0a2d6db35 100644 --- a/godot-core/src/private.rs +++ b/godot-core/src/private.rs @@ -30,6 +30,7 @@ pub struct ClassConfig { pub is_tool: bool, } +#[cfg(before_api = "4.3")] pub fn is_class_inactive(is_tool: bool) -> bool { if is_tool { return false; @@ -43,6 +44,21 @@ pub fn is_class_inactive(is_tool: bool) -> bool { && global_config.is_editor_or_init(is_editor) } +// Starting from 4.3, Godot has "runtime classes"; we only need to check whether editor is running. +// #[cfg(since_api = "4.3")] +pub fn is_class_runtime(is_tool: bool) -> bool { + if is_tool { + return false; + } + + // SAFETY: only invoked after global library initialization. + let global_config = unsafe { sys::config() }; + + // If this is not a #[tool], but we only run #[tool] classes in the editor, then don't run this in editor -> make it a runtime class. + // If we run all classes in the editor (!tool_only_in_editor), then it's not a runtime class. + global_config.tool_only_in_editor +} + pub fn print_panic(err: Box) { if let Some(s) = err.downcast_ref::<&'static str>() { print_panic_message(s); diff --git a/godot-core/src/registry/mod.rs b/godot-core/src/registry/mod.rs index e398b85a7..b967cc648 100644 --- a/godot-core/src/registry/mod.rs +++ b/godot-core/src/registry/mod.rs @@ -94,6 +94,9 @@ pub enum PluginItem { ) -> sys::GDExtensionClassCallVirtual, >, + /// Whether `#[class(tool)]` was used. + is_tool: bool, + /// Whether `#[class(editor_plugin)]` was used. is_editor_plugin: bool, @@ -205,8 +208,11 @@ struct ClassRegistrationInfo { /// Godot low-level class creation parameters. #[cfg(before_api = "4.2")] godot_params: sys::GDExtensionClassCreationInfo, - #[cfg(since_api = "4.2")] + #[cfg(all(since_api = "4.2", before_api = "4.3"))] godot_params: sys::GDExtensionClassCreationInfo2, + #[cfg(since_api = "4.3")] + godot_params: sys::GDExtensionClassCreationInfo3, + #[allow(dead_code)] // Currently unused; may be useful for diagnostics in the future. init_level: InitLevel, is_editor_plugin: bool, @@ -238,6 +244,7 @@ impl ClassRegistrationInfo { } /// Registers a class with static type information. +// Currently dead code, but will be needed for builder API. Don't remove. pub fn register_class< T: cap::GodotDefault + cap::ImplementsGodotVirtual @@ -250,27 +257,20 @@ pub fn register_class< out!("Manually register class {}", std::any::type_name::()); + // This works as long as fields are called the same. May still need individual #[cfg]s for newer fields. #[cfg(before_api = "4.2")] - let godot_params = sys::GDExtensionClassCreationInfo { - to_string_func: Some(callbacks::to_string::), - notification_func: Some(callbacks::on_notification::), - reference_func: Some(callbacks::reference::), - unreference_func: Some(callbacks::unreference::), - create_instance_func: Some(callbacks::create::), - free_instance_func: Some(callbacks::free::), - get_virtual_func: Some(callbacks::get_virtual::), - get_rid_func: None, - class_userdata: ptr::null_mut(), // will be passed to create fn, but global per class - ..default_creation_info() - }; - #[cfg(since_api = "4.2")] - let godot_params = sys::GDExtensionClassCreationInfo2 { + type CreationInfo = sys::GDExtensionClassCreationInfo; + #[cfg(all(since_api = "4.2", before_api = "4.3"))] + type CreationInfo = sys::GDExtensionClassCreationInfo2; + #[cfg(since_api = "4.3")] + type CreationInfo = sys::GDExtensionClassCreationInfo3; + + let godot_params = CreationInfo { to_string_func: Some(callbacks::to_string::), notification_func: Some(callbacks::on_notification::), reference_func: Some(callbacks::reference::), unreference_func: Some(callbacks::unreference::), create_instance_func: Some(callbacks::create::), - recreate_instance_func: Some(callbacks::recreate::), free_instance_func: Some(callbacks::free::), get_virtual_func: Some(callbacks::get_virtual::), get_rid_func: None, @@ -384,11 +384,15 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { register_properties_fn, free_fn, default_get_virtual_fn, + is_tool, is_editor_plugin, is_hidden, is_instantiable, } => { c.parent_class_name = Some(base_class_name); + c.default_virtual_fn = default_get_virtual_fn; + c.register_properties_fn = Some(register_properties_fn); + c.is_editor_plugin = is_editor_plugin; // Classes marked #[class(no_init)] are translated to "abstract" in Godot. This disables their default constructor. // "Abstract" is a misnomer -- it's not an abstract base class, but rather a "utility/static class" (although it can have instance @@ -399,6 +403,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { // // See also: https://github.com/godotengine/godot/pull/58972 c.godot_params.is_abstract = (!is_instantiable) as sys::GDExtensionBool; + c.godot_params.free_instance_func = Some(free_fn); fill_into( &mut c.godot_params.create_instance_func, @@ -420,11 +425,11 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { #[cfg(before_api = "4.2")] assert!(generated_recreate_fn.is_none()); // not used - c.godot_params.free_instance_func = Some(free_fn); - c.default_virtual_fn = default_get_virtual_fn; - c.register_properties_fn = Some(register_properties_fn); - - c.is_editor_plugin = is_editor_plugin; + #[cfg(since_api = "4.3")] + { + c.godot_params.is_runtime = + crate::private::is_class_runtime(is_tool) as sys::GDExtensionBool; + } } PluginItem::InherentImpl { @@ -494,12 +499,12 @@ fn register_class_raw(mut info: ClassRegistrationInfo) { info.godot_params.get_virtual_func = info.user_virtual_fn.or(info.default_virtual_fn); } + // The explicit () type notifies us if Godot API ever adds a return type. + #[allow(clippy::let_unit_value)] unsafe { // Try to register class... #[cfg(before_api = "4.2")] - #[allow(clippy::let_unit_value)] - // notifies us if Godot API ever adds a return type. let _: () = interface_fn!(classdb_register_extension_class)( sys::get_library(), class_name.string_sys(), @@ -507,9 +512,7 @@ fn register_class_raw(mut info: ClassRegistrationInfo) { ptr::addr_of!(info.godot_params), ); - #[cfg(since_api = "4.2")] - #[allow(clippy::let_unit_value)] - // notifies us if Godot API ever adds a return type. + #[cfg(all(since_api = "4.2", before_api = "4.3"))] let _: () = interface_fn!(classdb_register_extension_class2)( sys::get_library(), class_name.string_sys(), @@ -517,6 +520,14 @@ fn register_class_raw(mut info: ClassRegistrationInfo) { ptr::addr_of!(info.godot_params), ); + #[cfg(since_api = "4.3")] + let _: () = interface_fn!(classdb_register_extension_class3)( + sys::get_library(), + class_name.string_sys(), + parent_class_name.string_sys(), + ptr::addr_of!(info.godot_params), + ); + // ...then see if it worked. // This is necessary because the above registration does not report errors (apart from console output). let tag = interface_fn!(classdb_get_class_tag)(class_name.string_sys()); @@ -602,8 +613,8 @@ fn default_registration_info(class_name: ClassName) -> ClassRegistrationInfo { #[cfg(before_api = "4.2")] fn default_creation_info() -> sys::GDExtensionClassCreationInfo { sys::GDExtensionClassCreationInfo { - is_abstract: false as u8, is_virtual: false as u8, + is_abstract: false as u8, set_func: None, get_func: None, get_property_list_func: None, @@ -622,11 +633,12 @@ fn default_creation_info() -> sys::GDExtensionClassCreationInfo { } } -#[cfg(since_api = "4.2")] +#[cfg(all(since_api = "4.2", before_api = "4.3"))] fn default_creation_info() -> sys::GDExtensionClassCreationInfo2 { sys::GDExtensionClassCreationInfo2 { - is_abstract: false as u8, is_virtual: false as u8, + is_abstract: false as u8, + is_exposed: true as sys::GDExtensionBool, set_func: None, get_func: None, get_property_list_func: None, @@ -646,6 +658,34 @@ fn default_creation_info() -> sys::GDExtensionClassCreationInfo2 { call_virtual_with_data_func: None, get_rid_func: None, class_userdata: ptr::null_mut(), + } +} + +#[cfg(since_api = "4.3")] +fn default_creation_info() -> sys::GDExtensionClassCreationInfo3 { + sys::GDExtensionClassCreationInfo3 { + is_virtual: false as u8, + is_abstract: false as u8, is_exposed: true as sys::GDExtensionBool, + is_runtime: true as sys::GDExtensionBool, + set_func: None, + get_func: None, + get_property_list_func: None, + free_property_list_func: None, + property_can_revert_func: None, + property_get_revert_func: None, + validate_property_func: None, + notification_func: None, + to_string_func: None, + reference_func: None, + unreference_func: None, + create_instance_func: None, + free_instance_func: None, + recreate_instance_func: None, + get_virtual_func: None, + get_virtual_call_data_func: None, + call_virtual_with_data_func: None, + get_rid_func: None, + class_userdata: ptr::null_mut(), } } diff --git a/godot-ffi/src/binding/multi_threaded.rs b/godot-ffi/src/binding/multi_threaded.rs index 5cb2cc48d..bd7823b5c 100644 --- a/godot-ffi/src/binding/multi_threaded.rs +++ b/godot-ffi/src/binding/multi_threaded.rs @@ -73,7 +73,10 @@ impl BindingStorage { } pub struct GdextConfig { + /// True if only `#[tool]` classes are active in editor; false if all classes are. pub tool_only_in_editor: bool, + + /// Whether the extension is loaded in an editor. is_editor: OnceLock, } diff --git a/godot-macros/src/class/derive_godot_class.rs b/godot-macros/src/class/derive_godot_class.rs index 0786921ce..ba2533ebd 100644 --- a/godot-macros/src/class/derive_godot_class.rs +++ b/godot-macros/src/class/derive_godot_class.rs @@ -104,6 +104,8 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult { quote! { None } }; + let is_tool = struct_cfg.is_tool; + Ok(quote! { impl ::godot::obj::GodotClass for #class_name { type Base = #base_class; @@ -136,6 +138,7 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult { }, free_fn: #prv::callbacks::free::<#class_name>, default_get_virtual_fn: #default_get_virtual_fn, + is_tool: #is_tool, is_editor_plugin: #is_editor_plugin, is_hidden: #is_hidden, is_instantiable: #is_instantiable, diff --git a/godot-macros/src/class/godot_api.rs b/godot-macros/src/class/godot_api.rs index 72504830e..098c1bf6c 100644 --- a/godot-macros/src/class/godot_api.rs +++ b/godot-macros/src/class/godot_api.rs @@ -729,6 +729,8 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult impl ::godot::obj::cap::GodotNotification for #class_name { fn __godot_notification(&mut self, what: i32) { use ::godot::obj::UserClass as _; + + #[cfg(before_api = "4.3")] if ::godot::private::is_class_inactive(Self::__config().is_tool) { return; } @@ -751,6 +753,8 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult impl ::godot::obj::cap::GodotGet for #class_name { fn __godot_get_property(&self, property: ::godot::builtin::StringName) -> Option<::godot::builtin::Variant> { use ::godot::obj::UserClass as _; + + #[cfg(before_api = "4.3")] if ::godot::private::is_class_inactive(Self::__config().is_tool) { return None; } @@ -772,6 +776,8 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult impl ::godot::obj::cap::GodotSet for #class_name { fn __godot_set_property(&mut self, property: ::godot::builtin::StringName, value: ::godot::builtin::Variant) -> bool { use ::godot::obj::UserClass as _; + + #[cfg(before_api = "4.3")] if ::godot::private::is_class_inactive(Self::__config().is_tool) { return false; } diff --git a/godot-macros/src/util/mod.rs b/godot-macros/src/util/mod.rs index 26c444a43..da30119eb 100644 --- a/godot-macros/src/util/mod.rs +++ b/godot-macros/src/util/mod.rs @@ -261,6 +261,7 @@ pub(crate) fn extract_cfg_attrs( }) } +#[cfg(before_api = "4.3")] pub fn make_virtual_tool_check() -> TokenStream { quote! { if ::godot::private::is_class_inactive(Self::__config().is_tool) { @@ -268,3 +269,8 @@ pub fn make_virtual_tool_check() -> TokenStream { } } } + +#[cfg(since_api = "4.3")] +pub fn make_virtual_tool_check() -> TokenStream { + TokenStream::new() +}