Skip to content

Commit

Permalink
Merge pull request #619 from godot-rust/feature/runtime-classes
Browse files Browse the repository at this point in the history
For v4.3+, switch `#[class(tool)]` to use Godot's native "runtime class" feature
  • Loading branch information
Bromeon committed Feb 21, 2024
2 parents 3b7c674 + c5d5534 commit 82903f9
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 29 deletions.
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()
}

0 comments on commit 82903f9

Please sign in to comment.