Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For v4.3+, switch #[class(tool)] to use Godot's native "runtime class" feature #619

Merged
merged 1 commit into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions godot-core/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct ClassConfig {
pub is_tool: bool,
}

// Starting from 4.3, Godot has "runtime classes"; this emulation is no longer needed.
#[cfg(before_api = "4.3")]
pub fn is_class_inactive(is_tool: bool) -> bool {
if is_tool {
return false;
Expand All @@ -43,6 +45,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 #[class(tool)] and 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<dyn std::any::Any + Send>) {
if let Some(s) = err.downcast_ref::<&'static str>() {
print_panic_message(s);
Expand Down
98 changes: 69 additions & 29 deletions godot-core/src/registry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -250,27 +257,20 @@ pub fn register_class<

out!("Manually register class {}", std::any::type_name::<T>());

// 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::<T>),
notification_func: Some(callbacks::on_notification::<T>),
reference_func: Some(callbacks::reference::<T>),
unreference_func: Some(callbacks::unreference::<T>),
create_instance_func: Some(callbacks::create::<T>),
free_instance_func: Some(callbacks::free::<T>),
get_virtual_func: Some(callbacks::get_virtual::<T>),
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::<T>),
notification_func: Some(callbacks::on_notification::<T>),
reference_func: Some(callbacks::reference::<T>),
unreference_func: Some(callbacks::unreference::<T>),
create_instance_func: Some(callbacks::create::<T>),
recreate_instance_func: Some(callbacks::recreate::<T>),
free_instance_func: Some(callbacks::free::<T>),
get_virtual_func: Some(callbacks::get_virtual::<T>),
get_rid_func: None,
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -494,29 +499,35 @@ 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(),
parent_class_name.string_sys(),
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(),
parent_class_name.string_sys(),
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());
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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(),
}
}
3 changes: 3 additions & 0 deletions godot-ffi/src/binding/multi_threaded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ impl BindingStorage {
}

pub struct GdextConfig {
/// True if only `#[class(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<bool>,
}

Expand Down
3 changes: 3 additions & 0 deletions godot-macros/src/class/derive_godot_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult<TokenStream> {
quote! { None }
};

let is_tool = struct_cfg.is_tool;

Ok(quote! {
impl ::godot::obj::GodotClass for #class_name {
type Base = #base_class;
Expand Down Expand Up @@ -136,6 +138,7 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult<TokenStream> {
},
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,
Expand Down
6 changes: 6 additions & 0 deletions godot-macros/src/class/godot_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,8 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
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;
}
Expand All @@ -751,6 +753,8 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
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;
}
Expand All @@ -772,6 +776,8 @@ fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStream>
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;
}
Expand Down
6 changes: 6 additions & 0 deletions godot-macros/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,16 @@ 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) {
return None;
}
}
}

#[cfg(since_api = "4.3")]
pub fn make_virtual_tool_check() -> TokenStream {
TokenStream::new()
}
Loading