diff --git a/godot-core/src/builder/mod.rs b/godot-core/src/builder/mod.rs index d8a094607..a1ecbe27d 100644 --- a/godot-core/src/builder/mod.rs +++ b/godot-core/src/builder/mod.rs @@ -10,6 +10,9 @@ use std::marker::PhantomData; mod method; +/// Class builder to store state for registering a class with Godot. +/// +/// In the future this will be used, but for now it's a dummy struct. pub struct ClassBuilder { _c: PhantomData, } diff --git a/godot-core/src/private.rs b/godot-core/src/private.rs index 653e89d64..766db1f7f 100644 --- a/godot-core/src/private.rs +++ b/godot-core/src/private.rs @@ -9,7 +9,8 @@ pub use crate::gen::classes::class_macros; pub use crate::obj::rtti::ObjectRtti; pub use crate::registry::callbacks; pub use crate::registry::plugin::{ - ClassPlugin, ErasedDynGd, ErasedRegisterFn, ErasedRegisterRpcsFn, InherentImpl, PluginItem, + ClassPlugin, DynTraitImpl, ErasedDynGd, ErasedRegisterFn, ITraitImpl, InherentImpl, PluginItem, + Struct, }; pub use crate::storage::{as_storage, Storage}; pub use sys::out; diff --git a/godot-core/src/registry/callbacks.rs b/godot-core/src/registry/callbacks.rs index 5047ef149..5e56b03e9 100644 --- a/godot-core/src/registry/callbacks.rs +++ b/godot-core/src/registry/callbacks.rs @@ -12,7 +12,9 @@ use crate::builder::ClassBuilder; use crate::builtin::{StringName, Variant}; -use crate::obj::{cap, Base, GodotClass, UserClass}; +use crate::classes::Object; +use crate::obj::{bounds, cap, AsDyn, Base, Bounds, Gd, GodotClass, Inherits, UserClass}; +use crate::registry::plugin::ErasedDynGd; use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted}; use godot_ffi as sys; use std::any::Any; @@ -401,3 +403,21 @@ pub fn register_user_methods_constants(_class_builde pub fn register_user_rpcs(object: &mut dyn Any) { T::__register_rpcs(object); } + +/// # Safety +/// +/// `obj` must be castable to `T`. +pub unsafe fn dynify_fn(obj: Gd) -> ErasedDynGd +where + T: GodotClass + Inherits + AsDyn + Bounds, + D: ?Sized + 'static, +{ + // SAFETY: `obj` is castable to `T`. + let obj = unsafe { obj.try_cast::().unwrap_unchecked() }; + let obj = obj.into_dyn::(); + let obj = obj.upcast::(); + + ErasedDynGd { + boxed: Box::new(obj), + } +} diff --git a/godot-core/src/registry/class.rs b/godot-core/src/registry/class.rs index 45a381281..68b701cd0 100644 --- a/godot-core/src/registry/class.rs +++ b/godot-core/src/registry/class.rs @@ -14,8 +14,8 @@ use crate::meta::ClassName; use crate::obj::{cap, DynGd, Gd, GodotClass}; use crate::private::{ClassPlugin, PluginItem}; use crate::registry::callbacks; -use crate::registry::plugin::{ErasedDynifyFn, ErasedRegisterFn, InherentImpl}; -use crate::{classes, godot_error, sys}; +use crate::registry::plugin::{DynTraitImpl, ErasedRegisterFn, ITraitImpl, InherentImpl, Struct}; +use crate::{godot_error, sys}; use sys::{interface_fn, out, Global, GlobalGuard, GlobalLockError}; /// Returns a lock to a global map of loaded classes, by initialization level. @@ -43,9 +43,8 @@ fn global_loaded_classes_by_name() -> GlobalGuard<'static, HashMap GlobalGuard<'static, HashMap>> { - static DYN_TRAITS_BY_TYPEID: Global>> = +fn global_dyn_traits_by_typeid() -> GlobalGuard<'static, HashMap>> { + static DYN_TRAITS_BY_TYPEID: Global>> = Global::default(); lock_or_panic(&DYN_TRAITS_BY_TYPEID, "dyn traits") @@ -66,12 +65,6 @@ pub struct LoadedClass { // Currently empty, but should already work for per-class queries. pub struct ClassMetadata {} -/// Represents a `dyn Trait` implemented (and registered) for a class. -pub struct DynToClassRelation { - implementing_class_name: ClassName, - erased_dynify_fn: ErasedDynifyFn, -} - // ---------------------------------------------------------------------------------------------------------------------------------------------- // This works as long as fields are called the same. May still need individual #[cfg]s for newer fields. @@ -111,7 +104,7 @@ struct ClassRegistrationInfo { is_editor_plugin: bool, /// One entry for each `dyn Trait` implemented (and registered) for this class. - dynify_fns_by_trait: HashMap, + dynify_fns_by_trait: HashMap, /// Used to ensure that each component is only filled once. component_already_filled: [bool; 4], @@ -233,14 +226,11 @@ pub fn auto_register_classes(init_level: InitLevel) { let metadata = ClassMetadata {}; // Transpose Class->Trait relations to Trait->Class relations. - for (trait_type_id, dynify_fn) in info.dynify_fns_by_trait.drain() { + for (trait_type_id, dyn_trait_impl) in info.dynify_fns_by_trait.drain() { dyn_traits_by_typeid .entry(trait_type_id) .or_default() - .push(DynToClassRelation { - implementing_class_name: class_name, - erased_dynify_fn: dynify_fn, - }); + .push(dyn_trait_impl); } loaded_classes_by_level @@ -289,15 +279,16 @@ pub fn auto_register_rpcs(object: &mut T) { } } -/// Tries to upgrade a polymorphic `Gd` to `DynGd`, where the `T` -> `D` relation is only present via derived objects. +/// Tries to convert a `Gd` to a `DynGd` for some class `T` and trait object `D`, where the trait may only be implemented for +/// some subclass of `T`. /// -/// This works without direct `T: AsDyn` because it considers `object`'s dynamic type `Td : Inherits`. +/// This works even when `T` doesn't implement `AsDyn`, as long as the dynamic class of `object` implements `AsDyn`. /// -/// Only direct relations are considered, i.e. the `Td: AsDyn` must be fulfilled (and registered). If any intermediate base class of `Td` -/// implements the trait `D`, this will not consider it. Base-derived conversions are theoretically possible, but need quite a bit of extra -/// machinery. +/// This only looks for an `AsDyn` implementation in the dynamic class, the conversion will fail if the dynamic class doesn't +/// implement `AsDyn` even if there exists some superclass that does implement `AsDyn`. This restriction could in theory be +/// lifted, but would need quite a bit of extra machinery to work. pub(crate) fn try_dynify_object( - object: Gd, + mut object: Gd, ) -> Result, ConvertError> { let typeid = any::TypeId::of::(); let trait_name = sys::short_type_name::(); @@ -310,28 +301,16 @@ pub(crate) fn try_dynify_object( // TODO maybe use 2nd hashmap instead of linear search. // (probably not pair of typeid/classname, as that wouldn't allow the above check). - let dynamic_class = object.dynamic_class_string(); - for relation in relations { - if dynamic_class == relation.implementing_class_name.to_string_name() { - let erased = (relation.erased_dynify_fn)(object.upcast_object()); - - // Must succeed, or was registered wrong. - let dyn_gd_object = erased.boxed.downcast::>(); - - // SAFETY: the relation ensures that the **unified** (for storage) pointer was of type `DynGd`. - let dyn_gd_object = unsafe { dyn_gd_object.unwrap_unchecked() }; - - // SAFETY: the relation ensures that the **original** pointer was of type `DynGd`. - let dyn_gd_t = unsafe { dyn_gd_object.cast_unchecked::() }; - - return Ok(dyn_gd_t); + match relation.get_dyn_gd(object) { + Ok(dyn_gd) => return Ok(dyn_gd), + Err(obj) => object = obj, } } let error = FromGodotError::UnimplementedDynTrait { trait_name, - class_name: dynamic_class.to_string(), + class_name: object.dynamic_class_string().to_string(), }; Err(error.into_error(object)) @@ -344,7 +323,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { // out!("| reg (before): {c:?}"); // out!("| comp: {component:?}"); match item { - PluginItem::Struct { + PluginItem::Struct(Struct { base_class_name, generated_create_fn, generated_recreate_fn, @@ -357,7 +336,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { is_instantiable, #[cfg(all(since_api = "4.3", feature = "register-docs"))] docs: _, - } => { + }) => { c.parent_class_name = Some(base_class_name); c.default_virtual_fn = default_get_virtual_fn; c.register_properties_fn = Some(register_properties_fn); @@ -414,7 +393,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { c.register_methods_constants_fn = Some(register_methods_constants_fn); } - PluginItem::ITraitImpl { + PluginItem::ITraitImpl(ITraitImpl { user_register_fn, user_create_fn, user_recreate_fn, @@ -429,7 +408,7 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { user_property_get_revert_fn, #[cfg(all(since_api = "4.3", feature = "register-docs"))] virtual_method_docs: _, - } => { + }) => { c.user_register_fn = user_register_fn; // The following unwraps of fill_into() shouldn't panic, since rustc will error if there are @@ -453,20 +432,17 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) { c.godot_params.free_property_list_func = user_free_property_list_fn; c.godot_params.property_can_revert_func = user_property_can_revert_fn; c.godot_params.property_get_revert_func = user_property_get_revert_fn; - c.user_virtual_fn = Some(get_virtual_fn); + c.user_virtual_fn = get_virtual_fn; } - PluginItem::DynTraitImpl { - dyn_trait_typeid, - erased_dynify_fn, - } => { - let prev = c - .dynify_fns_by_trait - .insert(dyn_trait_typeid, erased_dynify_fn); + PluginItem::DynTraitImpl(dyn_trait_impl) => { + let type_id = *dyn_trait_impl.dyn_trait_typeid(); + + let prev = c.dynify_fns_by_trait.insert(type_id, dyn_trait_impl); assert!( prev.is_none(), "Duplicate registration of {:?} for class {}", - dyn_trait_typeid, + type_id, c.class_name ); } diff --git a/godot-core/src/registry/plugin.rs b/godot-core/src/registry/plugin.rs index c35518e93..cad0dd547 100644 --- a/godot-core/src/registry/plugin.rs +++ b/godot-core/src/registry/plugin.rs @@ -5,15 +5,18 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::classes::Object; #[cfg(all(since_api = "4.3", feature = "register-docs"))] use crate::docs::*; use crate::init::InitLevel; use crate::meta::ClassName; -use crate::obj::Gd; +use crate::obj::{bounds, cap, Bounds, DynGd, Gd, GodotClass, Inherits, UserClass}; +use crate::registry::callbacks; use crate::registry::class::GodotGetVirtual; use crate::{classes, sys}; use std::any::Any; use std::{any, fmt}; + // TODO(bromeon): some information coming from the proc-macro API is deferred through PluginItem, while others is directly // translated to code. Consider moving more code to the PluginItem, which allows for more dynamic registration and will // be easier for a future builder API. @@ -21,22 +24,48 @@ use std::{any, fmt}; // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Piece of information that is gathered by the self-registration ("plugin") system. +/// +/// You should not manually construct this struct, but rather use [`ClassPlugin::new()`]. #[derive(Debug)] pub struct ClassPlugin { - pub class_name: ClassName, - pub item: PluginItem, + /// The name of the class to register plugins for. + /// + /// This is used to group plugins so that all class properties for a single class can be registered at the same time. + /// Incorrectly setting this value should not cause any UB but will likely cause errors during registration time. + pub(crate) class_name: ClassName, + /// Which [`InitLevel`] this plugin should be registered at. + /// + /// Incorrectly setting this value should not cause any UB but will likely cause errors during registration time. // Init-level is per ClassPlugin and not per PluginItem, because all components of all classes are mixed together in one // huge linker list. There is no per-class aggregation going on, so this allows to easily filter relevant classes. - pub init_level: InitLevel, + pub(crate) init_level: InitLevel, + + /// The actual item being registered. + pub(crate) item: PluginItem, +} + +impl ClassPlugin { + /// Creates a new `ClassPlugin`, automatically setting the `class_name` and `init_level` to the values defined in [`GodotClass`]. + pub fn new(item: PluginItem) -> Self { + Self { + class_name: T::class_name(), + init_level: T::INIT_LEVEL, + item, + } + } } -/// Type-erased function object, holding a `register_class` function. +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Type-erased values + +/// Type-erased function object, holding a function which should called during class registration. #[derive(Copy, Clone)] pub struct ErasedRegisterFn { - // Wrapper needed because Debug can't be derived on function pointers with reference parameters, so this won't work: - // pub type ErasedRegisterFn = fn(&mut dyn std::any::Any); + // A wrapper is needed here because Debug can't be derived on function pointers with reference parameters, so this won't work: + // `pub type ErasedRegisterFn = fn(&mut dyn std::any::Any);`` // (see https://stackoverflow.com/q/53380040) + /// The actual function to be called during class registration. pub raw: fn(&mut dyn Any), } @@ -46,8 +75,15 @@ impl fmt::Debug for ErasedRegisterFn { } } +/// Type-erased function object, holding a function which should be called during RPC function registration. #[derive(Copy, Clone)] pub struct ErasedRegisterRpcsFn { + // A wrapper is needed here because Debug can't be derived on function pointers with reference parameters, so this won't work: + // `pub type ErasedRegisterFn = fn(&mut dyn std::any::Any);` + // (see https://stackoverflow.com/q/53380040) + /// The actual function to be called during RPC function registration. + /// + /// This should be called with a reference to the object that we want to register RPC functions for. pub raw: fn(&mut dyn Any), } @@ -57,195 +93,470 @@ impl fmt::Debug for ErasedRegisterRpcsFn { } } -pub type ErasedDynifyFn = fn(Gd) -> ErasedDynGd; +/// Type-erased function which converts a `Gd` into a `DynGd` for some trait object `D`. +/// +/// See [`DynTraitImpl`] for usage. +pub type ErasedDynifyFn = unsafe fn(Gd) -> ErasedDynGd; + +/// Type-erased `DynGd` for some trait object `D`. +/// +/// See [`DynTraitImpl`] for usage. +pub struct ErasedDynGd { + pub boxed: Box, +} type GodotCreateFn = unsafe extern "C" fn( _class_userdata: *mut std::ffi::c_void, #[cfg(since_api = "4.4")] _notify_postinitialize: sys::GDExtensionBool, ) -> sys::GDExtensionObjectPtr; +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Plugin items + +/// Represents the data part of a [`ClassPlugin`] instance. +/// +/// Each enumerator represents a different item in Rust code, which is processed by an independent proc macro (for example, +/// `#[derive(GodotClass)]` on structs, or `#[godot_api]` on impl blocks). +#[derive(Clone, Debug)] +pub enum PluginItem { + /// Class definition itself, must always be available -- created by `#[derive(GodotClass)]`. + Struct(Struct), + + /// Collected from `#[godot_api] impl MyClass`. + InherentImpl(InherentImpl), + + /// Collected from `#[godot_api] impl I... for MyClass`. + ITraitImpl(ITraitImpl), + + /// Collected from `#[godot_dyn]` macro invocations. + DynTraitImpl(DynTraitImpl), +} + +/// Helper function which checks that the field has not been set before. +fn set(field: &mut Option, value: T) { + assert!(field.is_none(), "attempted to set field more than once",); + *field = Some(value); +} + +/// The data for a class definition. +#[derive(Clone, Debug)] +pub struct Struct { + /// The name of the base class in Godot. + /// + /// This must match [`GodotClass::Base`]'s class name. + pub(crate) base_class_name: ClassName, + + /// Godot low-level `create` function, wired up to library-generated `init`. + /// + /// This is mutually exclusive with [`ITraitImpl::user_create_fn`]. + pub(crate) generated_create_fn: Option, + + /// Godot low-level `recreate` function, used when hot-reloading a user class. + /// + /// This is mutually exclusive with [`ITraitImpl::user_recreate_fn`]. + pub(crate) generated_recreate_fn: Option< + unsafe extern "C" fn( + p_class_userdata: *mut std::ffi::c_void, + p_object: sys::GDExtensionObjectPtr, + ) -> sys::GDExtensionClassInstancePtr, + >, + + /// Callback to library-generated function which registers properties in the `struct` definition. + pub(crate) register_properties_fn: ErasedRegisterFn, + + /// Function called by Godot when an object of this class is freed. + /// + /// Always implemented as [`callbacks::free`]. + pub(crate) free_fn: unsafe extern "C" fn( + _class_user_data: *mut std::ffi::c_void, + instance: sys::GDExtensionClassInstancePtr, + ), + + /// Calls `__before_ready()`, if there is at least one `OnReady` field. Used if there is no `#[godot_api] impl` block + /// overriding ready. + pub(crate) default_get_virtual_fn: Option< + unsafe extern "C" fn( + p_userdata: *mut std::os::raw::c_void, + p_name: sys::GDExtensionConstStringNamePtr, + ) -> sys::GDExtensionClassCallVirtual, + >, + + /// Whether `#[class(tool)]` was used. + pub(crate) is_tool: bool, + + /// Whether the base class is an `EditorPlugin`. + pub(crate) is_editor_plugin: bool, + + /// Whether `#[class(internal)]` was used. + pub(crate) is_internal: bool, + + /// Whether the class has a default constructor. + pub(crate) is_instantiable: bool, + #[cfg(all(since_api = "4.3", feature = "register-docs"))] + pub(crate) docs: Option, +} + +impl Struct { + pub fn new( + #[cfg(all(since_api = "4.3", feature = "register-docs"))] docs: Option, + ) -> Self { + Self { + base_class_name: T::Base::class_name(), + generated_create_fn: None, + generated_recreate_fn: None, + register_properties_fn: ErasedRegisterFn { + raw: callbacks::register_user_properties::, + }, + free_fn: callbacks::free::, + default_get_virtual_fn: None, + is_tool: false, + is_editor_plugin: false, + is_internal: false, + is_instantiable: false, + #[cfg(all(since_api = "4.3", feature = "register-docs"))] + docs, + } + } + + pub fn with_generated(mut self) -> Self { + set(&mut self.generated_create_fn, callbacks::create::); + #[cfg(since_api = "4.2")] + { + set(&mut self.generated_recreate_fn, callbacks::recreate::); + } + self + } + + pub fn with_default_get_virtual_fn(mut self) -> Self { + set( + &mut self.default_get_virtual_fn, + callbacks::default_get_virtual::, + ); + self + } + + pub fn with_tool(mut self) -> Self { + self.is_tool = true; + self + } + + pub fn with_editor_plugin(mut self) -> Self { + self.is_editor_plugin = true; + self + } + + pub fn with_internal(mut self) -> Self { + self.is_internal = true; + self + } + + pub fn with_instantiable(mut self) -> Self { + self.is_instantiable = true; + self + } +} + +/// Stores registration functions for methods, constants, and documentation from inherent `#[godot_api]` impl blocks. #[derive(Clone, Debug)] pub struct InherentImpl { /// Callback to library-generated function which registers functions and constants in the `impl` block. /// /// Always present since that's the entire point of this `impl` block. - pub register_methods_constants_fn: ErasedRegisterFn, + pub(crate) register_methods_constants_fn: ErasedRegisterFn, - /// Callback to library-generated function which calls [`Node::rpc_config`](crate::classes::Node::rpc_config) for each function annotated with `#[rpc]` on the `impl` block. + /// Callback to library-generated function which calls [`Node::rpc_config`](crate::classes::Node::rpc_config) for each function annotated + /// with `#[rpc]` on the `impl` block. /// - /// This function is called in [`UserClass::__before_ready()`](crate::obj::UserClass::__before_ready) definitions generated by the `#[derive(GodotClass)]` macro. - pub register_rpcs_fn: Option, + /// This function is called in [`UserClass::__before_ready()`](crate::obj::UserClass::__before_ready) definitions generated by the + /// `#[derive(GodotClass)]` macro. + // This field is only used during codegen-full. + #[cfg_attr(not(feature = "codegen-full"), expect(dead_code))] + pub(crate) register_rpcs_fn: Option, #[cfg(all(since_api = "4.3", feature = "register-docs"))] pub docs: InherentImplDocs, } -/// Represents the data part of a [`ClassPlugin`] instance. -/// -/// Each enumerator represents a different item in Rust code, which is processed by an independent proc macro (for example, -/// `#[derive(GodotClass)]` on structs, or `#[godot_api]` on impl blocks). -#[derive(Clone, Debug)] -pub enum PluginItem { - /// Class definition itself, must always be available -- created by `#[derive(GodotClass)]`. - Struct { - base_class_name: ClassName, +impl InherentImpl { + pub fn new( + #[cfg(all(since_api = "4.3", feature = "register-docs"))] docs: InherentImplDocs, + ) -> Self { + Self { + register_methods_constants_fn: ErasedRegisterFn { + raw: callbacks::register_user_methods_constants::, + }, + register_rpcs_fn: Some(ErasedRegisterRpcsFn { + raw: callbacks::register_user_rpcs::, + }), + #[cfg(all(since_api = "4.3", feature = "register-docs"))] + docs, + } + } +} - /// Godot low-level `create` function, wired up to library-generated `init`. - generated_create_fn: Option, +#[derive(Default, Clone, Debug)] +pub struct ITraitImpl { + #[cfg(all(since_api = "4.3", feature = "register-docs"))] + /// Virtual method documentation. + pub(crate) virtual_method_docs: &'static str, - generated_recreate_fn: Option< - unsafe extern "C" fn( - p_class_userdata: *mut std::ffi::c_void, - p_object: sys::GDExtensionObjectPtr, - ) -> sys::GDExtensionClassInstancePtr, - >, + /// Callback to user-defined `register_class` function. + pub(crate) user_register_fn: Option, - /// Callback to library-generated function which registers properties in the `struct` definition. - register_properties_fn: ErasedRegisterFn, + /// Godot low-level `create` function, wired up to the user's `init`. + /// + /// This is mutually exclusive with [`Struct::generated_create_fn`]. + pub(crate) user_create_fn: Option, - free_fn: unsafe extern "C" fn( - _class_user_data: *mut std::ffi::c_void, - instance: sys::GDExtensionClassInstancePtr, + /// Godot low-level `recreate` function, used when hot-reloading a user class. + /// + /// This is mutually exclusive with [`Struct::generated_recreate_fn`]. + pub(crate) user_recreate_fn: Option< + unsafe extern "C" fn( + p_class_userdata: *mut ::std::os::raw::c_void, + p_object: sys::GDExtensionObjectPtr, + ) -> sys::GDExtensionClassInstancePtr, + >, + + /// User-defined `to_string` function. + pub(crate) user_to_string_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + r_is_valid: *mut sys::GDExtensionBool, + r_out: sys::GDExtensionStringPtr, ), + >, + + /// User-defined `on_notification` function. + #[cfg(before_api = "4.2")] + pub(crate) user_on_notification_fn: + Option, + #[cfg(since_api = "4.2")] + pub(crate) user_on_notification_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + p_what: i32, + p_reversed: sys::GDExtensionBool, + ), + >, + + /// User-defined `set_property` function. + pub(crate) user_set_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + p_name: sys::GDExtensionConstStringNamePtr, + p_value: sys::GDExtensionConstVariantPtr, + ) -> sys::GDExtensionBool, + >, + + /// User-defined `get_property` function. + pub(crate) user_get_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + p_name: sys::GDExtensionConstStringNamePtr, + r_ret: sys::GDExtensionVariantPtr, + ) -> sys::GDExtensionBool, + >, + + /// Callback for other virtual methods specific to each class. + pub(crate) get_virtual_fn: Option, + + /// User-defined `get_property_list` function. + pub(crate) user_get_property_list_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + r_count: *mut u32, + ) -> *const sys::GDExtensionPropertyInfo, + >, + + // We do not support using this in Godot < 4.3, however it's easier to leave this in and fail elsewhere when attempting to use + // this in Godot < 4.3. + #[cfg(before_api = "4.3")] + pub(crate) user_free_property_list_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + p_list: *const sys::GDExtensionPropertyInfo, + ), + >, + /// Frees the property list created in the user-defined `get_property_list` function. + #[cfg(since_api = "4.3")] + pub(crate) user_free_property_list_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + p_list: *const sys::GDExtensionPropertyInfo, + p_count: u32, + ), + >, - /// Calls `__before_ready()`, if there is at least one `OnReady` field. Used if there is no `#[godot_api] impl` block - /// overriding ready. - default_get_virtual_fn: Option, + /// Part of user-defined `property_get_revert` function. + /// + /// This effectively just calls [`Option::is_some`] on the return value of the `property_get_revert` function. + pub(crate) user_property_can_revert_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + p_name: sys::GDExtensionConstStringNamePtr, + ) -> sys::GDExtensionBool, + >, + + /// Part of user-defined `property_get_revert` function. + /// + /// This returns null when the return value of `property_get_revert` is `None`, and otherwise returns the value contained + /// within the `Some`. + pub(crate) user_property_get_revert_fn: Option< + unsafe extern "C" fn( + p_instance: sys::GDExtensionClassInstancePtr, + p_name: sys::GDExtensionConstStringNamePtr, + r_ret: sys::GDExtensionVariantPtr, + ) -> sys::GDExtensionBool, + >, +} - /// Whether `#[class(tool)]` was used. - is_tool: bool, +impl ITraitImpl { + pub fn new( + #[cfg(all(since_api = "4.3", feature = "register-docs"))] virtual_method_docs: &'static str, + ) -> Self { + Self { + #[cfg(all(since_api = "4.3", feature = "register-docs"))] + virtual_method_docs, + get_virtual_fn: Some(callbacks::get_virtual::), + ..Default::default() + } + } - /// Whether the base class is an `EditorPlugin`. - is_editor_plugin: bool, + pub fn with_register(mut self) -> Self { + set( + &mut self.user_register_fn, + ErasedRegisterFn { + raw: callbacks::register_class_by_builder::, + }, + ); - /// Whether `#[class(internal)]` was used. - is_internal: bool, + self + } - /// Whether the class has a default constructor. - is_instantiable: bool, + pub fn with_create(mut self) -> Self { + set(&mut self.user_create_fn, callbacks::create::); + #[cfg(since_api = "4.3")] + set(&mut self.user_recreate_fn, callbacks::recreate::); + self + } - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - docs: Option, - }, + pub fn with_string(mut self) -> Self { + set(&mut self.user_to_string_fn, callbacks::to_string::); + self + } - /// Collected from `#[godot_api] impl MyClass`. - InherentImpl(InherentImpl), + pub fn with_on_notification(mut self) -> Self { + set( + &mut self.user_on_notification_fn, + callbacks::on_notification::, + ); + self + } - /// Collected from `#[godot_api] impl I... for MyClass`. - ITraitImpl { - /// Virtual method documentation. - #[cfg(all(since_api = "4.3", feature = "register-docs"))] - virtual_method_docs: &'static str, - - /// Callback to user-defined `register_class` function. - user_register_fn: Option, - - /// Godot low-level `create` function, wired up to the user's `init`. - user_create_fn: Option, - - user_recreate_fn: Option< - unsafe extern "C" fn( - p_class_userdata: *mut ::std::os::raw::c_void, - p_object: sys::GDExtensionObjectPtr, - ) -> sys::GDExtensionClassInstancePtr, - >, - - /// User-defined `to_string` function. - user_to_string_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, - r_is_valid: *mut sys::GDExtensionBool, - r_out: sys::GDExtensionStringPtr, - ), - >, - - /// User-defined `on_notification` function. - #[cfg(before_api = "4.2")] - user_on_notification_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, // - p_what: i32, - ), - >, - #[cfg(since_api = "4.2")] - user_on_notification_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, // - p_what: i32, - p_reversed: sys::GDExtensionBool, - ), - >, - - user_set_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, - p_name: sys::GDExtensionConstStringNamePtr, - p_value: sys::GDExtensionConstVariantPtr, - ) -> sys::GDExtensionBool, - >, - - user_get_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, - p_name: sys::GDExtensionConstStringNamePtr, - r_ret: sys::GDExtensionVariantPtr, - ) -> sys::GDExtensionBool, - >, - - /// Callback for other virtuals. - get_virtual_fn: GodotGetVirtual, - - /// Callback for other virtuals. - user_get_property_list_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, - r_count: *mut u32, - ) -> *const sys::GDExtensionPropertyInfo, - >, - - // We do not support using this in Godot < 4.3, however it's easier to leave this in and fail elsewhere when attempting to use - // this in Godot < 4.3. - #[cfg(before_api = "4.3")] - user_free_property_list_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, - p_list: *const sys::GDExtensionPropertyInfo, - ), - >, + pub fn with_get_property(mut self) -> Self { + set(&mut self.user_get_fn, callbacks::get_property::); + self + } + + pub fn with_set_property(mut self) -> Self { + set(&mut self.user_set_fn, callbacks::set_property::); + self + } + + pub fn with_get_property_list(mut self) -> Self { + set( + &mut self.user_get_property_list_fn, + callbacks::get_property_list::, + ); #[cfg(since_api = "4.3")] - user_free_property_list_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, - p_list: *const sys::GDExtensionPropertyInfo, - p_count: u32, - ), - >, - - user_property_can_revert_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, - p_name: sys::GDExtensionConstStringNamePtr, - ) -> sys::GDExtensionBool, - >, - - user_property_get_revert_fn: Option< - unsafe extern "C" fn( - p_instance: sys::GDExtensionClassInstancePtr, - p_name: sys::GDExtensionConstStringNamePtr, - r_ret: sys::GDExtensionVariantPtr, - ) -> sys::GDExtensionBool, - >, - }, - - DynTraitImpl { - /// TypeId of the `dyn Trait` object. - dyn_trait_typeid: any::TypeId, - - /// Function that converts a `Gd` to a type-erased `DynGd` (with the latter erased for common storage). - erased_dynify_fn: fn(Gd) -> ErasedDynGd, - }, + set( + &mut self.user_free_property_list_fn, + callbacks::free_property_list::, + ); + self + } + + pub fn with_property_get_revert(mut self) -> Self { + set( + &mut self.user_property_get_revert_fn, + callbacks::property_get_revert::, + ); + set( + &mut self.user_property_can_revert_fn, + callbacks::property_can_revert::, + ); + self + } } -pub struct ErasedDynGd { - pub boxed: Box, +/// Representation of a `#[godot_dyn]` invocation. +/// +/// Stores all the information needed for `DynGd` re-enrichment. +#[derive(Clone, Debug)] +pub struct DynTraitImpl { + /// The class that this `dyn Trait` implementation corresponds to. + class_name: ClassName, + + /// TypeId of the `dyn Trait` object. + dyn_trait_typeid: any::TypeId, + + /// Function used to get a `DynGd` from a `Gd`. This is used in the [`FromGodot`](crate::meta::FromGodot) implementation + /// of [`DynGd`]. This function is always implemented as [`callbacks::dynify_fn::`] where `T` is the class represented by `class_name` + /// and `D` is the trait object corresponding to `dyn_trait_typeid`. + /// + /// Function that converts a `Gd` to a type-erased `DynGd` (with the latter erased for common storage). + erased_dynify_fn: ErasedDynifyFn, +} + +impl DynTraitImpl { + pub fn new() -> Self + where + T: GodotClass + + Inherits + + crate::obj::AsDyn + + Bounds, + D: ?Sized + 'static, + { + Self { + class_name: T::class_name(), + dyn_trait_typeid: std::any::TypeId::of::(), + erased_dynify_fn: callbacks::dynify_fn::, + } + } + + /// The type id of the trait object this was registered with. + pub fn dyn_trait_typeid(&self) -> &any::TypeId { + &self.dyn_trait_typeid + } + + /// Convert a [`Gd`] to a [`DynGd`] using `self`. + /// + /// This will fail if the dynamic class of `object` does not match the [`ClassName`] stored in `self`. + pub fn get_dyn_gd( + &self, + object: Gd, + ) -> Result, Gd> { + let dynamic_class = object.dynamic_class_string(); + + if dynamic_class != self.class_name.to_string_name() { + return Err(object); + } + + // SAFETY: `DynTraitImpl::new` ensures that this function is safe to call when `object` is castable to `self.class_name`. + // Since the dynamic class of `object` is `self.class_name`, it must be castable to `self.class_name`. + let erased_dyn = unsafe { (self.erased_dynify_fn)(object.upcast_object()) }; + + let dyn_gd_object = erased_dyn.boxed.downcast::>(); + + // SAFETY: `callbacks::dynify_fn` returns a `DynGd` which has been type erased. So this downcast wil always succeed. + let dyn_gd_object = unsafe { dyn_gd_object.unwrap_unchecked() }; + + // SAFETY: This is effectively upcasting a value which has class equal to `self.class_name` to a `DynGd`. Since the class of + // `object` is `T` and its dynamic class is `self.class_name`, this means that `T` must be a superclass of `self.class_name`. Thus + // this upcast is safe. + let dyn_gd_t = unsafe { dyn_gd_object.cast_unchecked::() }; + + Ok(dyn_gd_t) + } } diff --git a/godot-macros/src/class/data_models/inherent_impl.rs b/godot-macros/src/class/data_models/inherent_impl.rs index 4eeff41f2..eff201247 100644 --- a/godot-macros/src/class/data_models/inherent_impl.rs +++ b/godot-macros/src/class/data_models/inherent_impl.rs @@ -153,19 +153,9 @@ pub fn transform_inherent_impl( }; let class_registration = quote! { - ::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin { - class_name: #class_name_obj, - item: #prv::PluginItem::InherentImpl(#prv::InherentImpl { - register_methods_constants_fn: #prv::ErasedRegisterFn { - raw: #prv::callbacks::register_user_methods_constants::<#class_name>, - }, - register_rpcs_fn: Some(#prv::ErasedRegisterRpcsFn { - raw: #prv::callbacks::register_user_rpcs::<#class_name>, - }), - #docs - }), - init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL, - }); + ::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin::new::<#class_name>( + #prv::PluginItem::InherentImpl(#prv::InherentImpl::new::<#class_name>(#docs)) + )); }; let result = quote! { diff --git a/godot-macros/src/class/data_models/interface_trait_impl.rs b/godot-macros/src/class/data_models/interface_trait_impl.rs index 6a14c24f4..2dcf8c600 100644 --- a/godot-macros/src/class/data_models/interface_trait_impl.rs +++ b/godot-macros/src/class/data_models/interface_trait_impl.rs @@ -6,6 +6,7 @@ */ use crate::class::{into_signature_info, make_virtual_callback, BeforeKind, SignatureInfo}; +use crate::util::ident; use crate::{util, ParseResult}; use proc_macro2::{Ident, TokenStream}; @@ -15,7 +16,6 @@ use quote::quote; pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult { let (class_name, trait_path, trait_base_class) = util::validate_trait_impl_virtual(&original_impl, "godot_api")?; - let class_name_obj = util::class_name_obj(&class_name); let mut godot_init_impl = TokenStream::new(); let mut to_string_impl = TokenStream::new(); @@ -26,17 +26,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult ParseResult Some(#prv::ErasedRegisterFn { - raw: #prv::callbacks::register_class_by_builder::<#class_name> - }), - }); + modifiers.push((cfg_attrs, ident("with_register"))); } "init" => { @@ -110,18 +86,8 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult Some(#prv::callbacks::create::<#class_name>), - }); - if cfg!(since_api = "4.2") { - recreate_fn = Some(quote! { - #recreate_fn - #(#cfg_attrs)* - () => Some(#prv::callbacks::recreate::<#class_name>), - }); - } + + modifiers.push((cfg_attrs, ident("with_create"))); } "to_string" => { @@ -136,11 +102,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult Some(#prv::callbacks::to_string::<#class_name>), - }); + modifiers.push((cfg_attrs, ident("with_string"))); } "on_notification" => { @@ -160,11 +122,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult Some(#prv::callbacks::on_notification::<#class_name>), - }); + modifiers.push((cfg_attrs, ident("with_on_notification"))); } "get_property" => { @@ -182,10 +140,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult Some(#prv::callbacks::get_property::<#class_name>), - }); + modifiers.push((cfg_attrs, ident("with_get_property"))); } "set_property" => { @@ -203,10 +158,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult Some(#prv::callbacks::set_property::<#class_name>), - }); + modifiers.push((cfg_attrs, ident("with_set_property"))); } #[cfg(before_api = "4.3")] @@ -238,14 +190,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult Some(#prv::callbacks::get_property_list::<#class_name>), - }); - free_property_list_fn = Some(quote! { - #(#cfg_attrs)* - () => Some(#prv::callbacks::free_property_list::<#class_name>), - }); + modifiers.push((cfg_attrs, ident("with_get_property_list"))); } "property_get_revert" => { @@ -263,15 +208,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult Some(#prv::callbacks::property_get_revert::<#class_name>), - }); - - property_can_revert_fn = Some(quote! { - #(#cfg_attrs)* - () => Some(#prv::callbacks::property_can_revert::<#class_name>), - }); + modifiers.push((cfg_attrs, ident("with_property_get_revert"))); } // Other virtual methods, like ready, process etc. @@ -340,22 +277,19 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult(); } + } + }); + + let item_constructor = quote! { { + let mut item = #prv::ITraitImpl::new::<#class_name>(#docs); + #(#modifiers)* + item + } + }; // See also __default_virtual_call() codegen. let (hash_param, hashes_use, match_expr); @@ -401,25 +335,9 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult, - #docs - }, - init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL, - }); + ::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin::new::<#class_name>( + #prv::PluginItem::ITraitImpl(#item_constructor) + )); }; Ok(result) @@ -478,30 +396,6 @@ impl OverriddenVirtualFn<'_> { } } -/// Expects either Some(quote! { () => A, () => B, ... }) or None as the 'tokens' parameter. -/// The idea is that the () => ... arms can be annotated by cfg attrs, so, if any of them compiles (and assuming the cfg -/// attrs only allow one arm to 'survive' compilation), their return value (Some(...)) will be prioritized over the -/// 'None' from the catch-all arm at the end. If, however, none of them compile, then None is returned from the last -/// match arm. -fn convert_to_match_expression_or_none(tokens: Option) -> TokenStream { - if let Some(tokens) = tokens { - quote! { - { - // When one of the () => ... arms is present, the last arm intentionally won't ever match. - #[allow(unreachable_patterns)] - // Don't warn when only _ => None is present as all () => ... arms were removed from compilation. - #[allow(clippy::match_single_binding)] - match () { - #tokens - _ => None, - } - } - } - } else { - quote! { None } - } -} - #[cfg(before_api = "4.3")] fn make_inactive_class_check(return_value: TokenStream) -> TokenStream { quote! { diff --git a/godot-macros/src/class/derive_godot_class.rs b/godot-macros/src/class/derive_godot_class.rs index 6af0a4ad7..3bc98b25e 100644 --- a/godot-macros/src/class/derive_godot_class.rs +++ b/godot-macros/src/class/derive_godot_class.rs @@ -30,10 +30,13 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult { ); } + let mut modifiers = Vec::new(); let named_fields = named_fields(class)?; let mut struct_cfg = parse_struct_attributes(class)?; let mut fields = parse_fields(named_fields, struct_cfg.init_strategy)?; - let is_editor_plugin = struct_cfg.is_editor_plugin(); + if struct_cfg.is_editor_plugin() { + modifiers.push(quote! { with_editor_plugin }) + } let mut deprecations = std::mem::take(&mut struct_cfg.deprecations); deprecations.append(&mut fields.deprecations); @@ -54,9 +57,9 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult { quote! { ClassName::alloc_next_unicode(#class_name_str) } }; - let class_name_obj = util::class_name_obj(class_name); - - let is_internal = struct_cfg.is_internal; + if struct_cfg.is_internal { + modifiers.push(quote! { with_internal }) + } let base_ty = &struct_cfg.base_ty; #[cfg(all(feature = "register-docs", since_api = "4.3"))] let docs = crate::docs::make_definition_docs( @@ -67,7 +70,6 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult { #[cfg(not(all(feature = "register-docs", since_api = "4.3")))] let docs = quote! {}; let base_class = quote! { ::godot::classes::#base_ty }; - let base_class_name_obj = util::class_name_obj(&base_class); let inherits_macro = format_ident!("unsafe_inherits_transitive_{}", base_ty); let prv = quote! { ::godot::private }; @@ -98,18 +100,12 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult { let mut init_expecter = TokenStream::new(); let mut godot_init_impl = TokenStream::new(); - let mut create_fn = quote! { None }; - let mut recreate_fn = quote! { None }; let mut is_instantiable = true; match struct_cfg.init_strategy { InitStrategy::Generated => { godot_init_impl = make_godot_init_impl(class_name, &fields); - create_fn = quote! { Some(#prv::callbacks::create::<#class_name>) }; - - if cfg!(since_api = "4.2") { - recreate_fn = quote! { Some(#prv::callbacks::recreate::<#class_name>) }; - } + modifiers.push(quote! { with_generated::<#class_name> }); } InitStrategy::UserDefined => { let fn_name = format_ident!("class_{}_must_have_an_init_method", class_name); @@ -126,14 +122,17 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult { is_instantiable = false; } }; + if is_instantiable { + modifiers.push(quote! { with_instantiable }); + } - let default_get_virtual_fn = if has_default_virtual { - quote! { Some(#prv::callbacks::default_get_virtual::<#class_name>) } - } else { - quote! { None } - }; + if has_default_virtual { + modifiers.push(quote! { with_default_get_virtual_fn::<#class_name> }); + } - let is_tool = struct_cfg.is_tool; + if struct_cfg.is_tool { + modifiers.push(quote! { with_tool }) + } Ok(quote! { impl ::godot::obj::GodotClass for #class_name { @@ -166,39 +165,11 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult { #( #deprecations )* #( #errors )* - ::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin { - class_name: #class_name_obj, - item: #prv::PluginItem::Struct { - base_class_name: #base_class_name_obj, - generated_create_fn: #create_fn, - generated_recreate_fn: #recreate_fn, - register_properties_fn: #prv::ErasedRegisterFn { - raw: #prv::callbacks::register_user_properties::<#class_name>, - }, - 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_internal: #is_internal, - is_instantiable: #is_instantiable, - #docs - }, - init_level: { - let level = <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL; - let base_level = <#base_class as ::godot::obj::GodotClass>::INIT_LEVEL; - - // Sanity check for init levels. Note that this does not cover cases where GodotClass is manually defined; - // might make sense to add a run-time check during class registration. - assert!( - level >= base_level, - "Class `{class}` has init level `{level:?}`, but its base class has init level `{base_level:?}`.\n\ - A class cannot be registered before its base class.", - class = #class_name_str, - ); - - level - } - }); + ::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin::new::<#class_name>( + #prv::PluginItem::Struct( + #prv::Struct::new::<#class_name>(#docs)#(.#modifiers())* + ) + )); #prv::class_macros::#inherits_macro!(#class_name); }) diff --git a/godot-macros/src/class/godot_dyn.rs b/godot-macros/src/class/godot_dyn.rs index d34621372..0aeca2bb8 100644 --- a/godot-macros/src/class/godot_dyn.rs +++ b/godot-macros/src/class/godot_dyn.rs @@ -6,7 +6,7 @@ */ use crate::util::bail; -use crate::{util, ParseResult}; +use crate::ParseResult; use proc_macro2::TokenStream; use quote::quote; @@ -33,7 +33,6 @@ pub fn attribute_godot_dyn(input_decl: venial::Item) -> ParseResult }; let class_path = &decl.self_ty; - let class_name_obj = util::class_name_obj(class_path); //&util::extract_typename(class_path)); let prv = quote! { ::godot::private }; //let dynify_fn = format_ident!("__dynify_{}", class_name); @@ -51,27 +50,9 @@ pub fn attribute_godot_dyn(input_decl: venial::Item) -> ParseResult } } - ::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin { - class_name: #class_name_obj, - item: #prv::PluginItem::DynTraitImpl { - dyn_trait_typeid: std::any::TypeId::of::(), - erased_dynify_fn: { - fn dynify_fn(obj: ::godot::obj::Gd<::godot::classes::Object>) -> #prv::ErasedDynGd { - // SAFETY: runtime class type is statically known here and linked to the `class_name` field of the plugin. - let obj = unsafe { obj.try_cast::<#class_path>().unwrap_unchecked() }; - let obj = obj.into_dyn::(); - let obj = obj.upcast::<::godot::classes::Object>(); - - #prv::ErasedDynGd { - boxed: Box::new(obj), - } - } - - dynify_fn - } - }, - init_level: <#class_path as ::godot::obj::GodotClass>::INIT_LEVEL, - }); + ::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin::new::<#class_path>( + #prv::PluginItem::DynTraitImpl(#prv::DynTraitImpl::new::<#class_path, dyn #trait_path>())) + ); }; diff --git a/godot-macros/src/docs.rs b/godot-macros/src/docs.rs index abe967862..d1648317f 100644 --- a/godot-macros/src/docs.rs +++ b/godot-macros/src/docs.rs @@ -26,14 +26,14 @@ pub fn make_definition_docs( .filter_map(member) .collect::(); Some(quote! { - docs: ::godot::docs::StructDocs { + ::godot::docs::StructDocs { base: #base_escaped, description: #desc_escaped, members: #members, - }.into() + } }) })() - .unwrap_or(quote! { docs: None }) + .unwrap_or(TokenStream::new()) } pub fn make_inherent_impl_docs( @@ -77,7 +77,7 @@ pub fn make_inherent_impl_docs( .collect::(); quote! { - docs: ::godot::docs::InherentImplDocs { + ::godot::docs::InherentImplDocs { methods: #methods, signals_block: #signals_block, constants_block: #constants_block, @@ -97,7 +97,7 @@ pub fn make_virtual_impl_docs(vmethods: &[ImplMember]) -> TokenStream { .filter_map(make_virtual_method_docs) .collect::(); - quote! { virtual_method_docs: #virtual_methods, } + quote! { #virtual_methods } } /// `///` is expanded to `#[doc = "…"]`. diff --git a/itest/rust/src/register_tests/constant_test.rs b/itest/rust/src/register_tests/constant_test.rs index 33af9ce12..6262202cf 100644 --- a/itest/rust/src/register_tests/constant_test.rs +++ b/itest/rust/src/register_tests/constant_test.rs @@ -167,18 +167,11 @@ impl godot::obj::cap::ImplementsGodotApi for HasOtherConstants { // TODO once this is done via proc-macro, see if `register-docs` is still used in register_docs_test.rs. Otherwise, remove feature from Cargo.toml. godot::sys::plugin_add!( __GODOT_PLUGIN_REGISTRY in ::godot::private; - ::godot::private::ClassPlugin { - class_name: HasOtherConstants::class_name(), - item: ::godot::private::PluginItem::InherentImpl(::godot::private::InherentImpl { - register_methods_constants_fn: ::godot::private::ErasedRegisterFn { - raw: ::godot::private::callbacks::register_user_methods_constants::, - }, - register_rpcs_fn: None, - #[cfg(feature = "register-docs")] - docs: ::godot::docs::InherentImplDocs::default(), - }), - init_level: HasOtherConstants::INIT_LEVEL, - } + ::godot::private::ClassPlugin::new::( + ::godot::private::PluginItem::InherentImpl( + ::godot::private::InherentImpl::new::() + ) + ) ); macro_rules! test_enum_export {