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

Implement dynTrait for Gd<T: TraitA> #930

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,12 @@ pub mod cap {
fn __godot_property_get_revert(&self, property: StringName) -> Option<Variant>;
}

#[doc(hidden)]
pub trait DynTrait: GodotClass {
#[doc(hidden)]
fn __register_dyn_traits();
}

/// Auto-implemented for `#[godot_api] impl MyClass` blocks
pub trait ImplementsGodotApi: GodotClass {
#[doc(hidden)]
Expand Down
3 changes: 2 additions & 1 deletion godot-core/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ErasedRegisterFn, ErasedRegisterRpcsFn, InherentImpl, PluginItem,
ClassPlugin, ErasedRegisterDynTraitFn, ErasedRegisterFn, ErasedRegisterRpcsFn, InherentImpl,
PluginItem,
};
pub use crate::storage::{as_storage, Storage};
pub use sys::out;
Expand Down
4 changes: 4 additions & 0 deletions godot-core/src/registry/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ pub fn register_user_properties<T: cap::ImplementsGodotExports>(_class_builder:
T::__register_exports();
}

pub fn register_user_dyn_traits<T: cap::DynTrait>(_class_builder: &mut dyn Any) {
T::__register_dyn_traits();
}

pub fn register_user_methods_constants<T: cap::ImplementsGodotApi>(_class_builder: &mut dyn Any) {
// let class_builder = class_builder
// .downcast_mut::<ClassBuilder<T>>()
Expand Down
12 changes: 10 additions & 2 deletions godot-core/src/registry/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::ptr;
use crate::init::InitLevel;
use crate::meta::ClassName;
use crate::obj::{cap, GodotClass};
use crate::private::{ClassPlugin, PluginItem};
use crate::private::{ClassPlugin, ErasedRegisterDynTraitFn, PluginItem};
use crate::registry::callbacks;
use crate::registry::plugin::{ErasedRegisterFn, InherentImpl};
use crate::{godot_error, sys};
Expand Down Expand Up @@ -47,7 +47,7 @@ struct ClassRegistrationInfo {
user_register_fn: Option<ErasedRegisterFn>,
default_virtual_fn: sys::GDExtensionClassGetVirtual, // Option (set if there is at least one OnReady field)
user_virtual_fn: sys::GDExtensionClassGetVirtual, // Option (set if there is a `#[godot_api] impl I*`)

register_dyn_trait_fn: Option<ErasedRegisterDynTraitFn>,
/// Godot low-level class creation parameters.
#[cfg(before_api = "4.2")]
godot_params: sys::GDExtensionClassCreationInfo,
Expand Down Expand Up @@ -140,6 +140,7 @@ pub fn register_class<
init_level: T::INIT_LEVEL,
is_editor_plugin: false,
component_already_filled: Default::default(), // [false; N]
register_dyn_trait_fn: None,
});
}

Expand Down Expand Up @@ -245,10 +246,12 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
is_instantiable,
#[cfg(all(since_api = "4.3", feature = "docs"))]
docs: _,
register_dyn_trait_fn,
} => {
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.register_dyn_trait_fn = Some(register_dyn_trait_fn);
c.is_editor_plugin = is_editor_plugin;

// Classes marked #[class(no_init)] are translated to "abstract" in Godot. This disables their default constructor.
Expand Down Expand Up @@ -437,6 +440,10 @@ fn register_class_raw(mut info: ClassRegistrationInfo) {
(register_fn.raw)(&mut class_builder);
}

if let Some(register_dyn_trait) = info.register_dyn_trait_fn {
(register_dyn_trait.raw)(&mut class_builder);
}

if info.is_editor_plugin {
unsafe { interface_fn!(editor_add_plugin)(class_name.string_sys()) };
}
Expand Down Expand Up @@ -484,6 +491,7 @@ fn default_registration_info(class_name: ClassName) -> ClassRegistrationInfo {
user_register_fn: None,
default_virtual_fn: None,
user_virtual_fn: None,
register_dyn_trait_fn: None,
godot_params: default_creation_info(),
init_level: InitLevel::Scene,
is_editor_plugin: false,
Expand Down
13 changes: 13 additions & 0 deletions godot-core/src/registry/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ impl fmt::Debug for ErasedRegisterFn {
}
}

#[derive(Copy, Clone)]
pub struct ErasedRegisterDynTraitFn {
pub raw: fn(&mut dyn Any),
}

impl fmt::Debug for ErasedRegisterDynTraitFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{:0>16x}", self.raw as usize)
}
}

#[derive(Copy, Clone)]
pub struct ErasedRegisterRpcsFn {
pub raw: fn(&mut dyn Any),
Expand Down Expand Up @@ -96,6 +107,8 @@ pub enum PluginItem {
/// Callback to library-generated function which registers properties in the `struct` definition.
register_properties_fn: ErasedRegisterFn,

register_dyn_trait_fn: ErasedRegisterDynTraitFn,

free_fn: unsafe extern "C" fn(
_class_user_data: *mut std::ffi::c_void,
instance: sys::GDExtensionClassInstancePtr,
Expand Down
38 changes: 38 additions & 0 deletions godot-macros/src/class/derive_godot_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {

let is_tool = struct_cfg.is_tool;

let dyn_trait_impl = make_dyn_trait_impl(struct_cfg.dyn_traits, class_name);

Ok(quote! {
impl ::godot::obj::GodotClass for #class_name {
type Base = #base_class;
Expand Down Expand Up @@ -147,6 +149,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
#godot_exports_impl
#user_class_impl
#init_expecter
#dyn_trait_impl
#( #deprecations )*

::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
Expand All @@ -158,6 +161,9 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
register_properties_fn: #prv::ErasedRegisterFn {
raw: #prv::callbacks::register_user_properties::<#class_name>,
},
register_dyn_trait_fn: #prv::ErasedRegisterDynTraitFn {
raw: #prv::callbacks::register_user_dyn_traits::<#class_name>,
},
free_fn: #prv::callbacks::free::<#class_name>,
default_get_virtual_fn: #default_get_virtual_fn,
is_tool: #is_tool,
Expand Down Expand Up @@ -213,6 +219,7 @@ struct ClassAttributes {
is_internal: bool,
rename: Option<Ident>,
deprecations: Vec<TokenStream>,
dyn_traits: Vec<Ident>,
}

impl ClassAttributes {
Expand Down Expand Up @@ -250,6 +257,29 @@ fn make_godot_init_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
}
}

fn make_dyn_trait_impl(dyn_traits: Vec<Ident>, class_name: &Ident) -> TokenStream {
// note – we assume that methods "register_traitname_dispatch" are explicitly imported by the user. It is common approach among such cases, for example see Enum Dispatch
let register_fns: Vec<TokenStream> = dyn_traits
.iter()
.map(|t| {
let ident = ident(&format!(
"register_{}_dispatch",
t.to_string().to_lowercase()
));
quote! {
#ident::<#class_name>()
}
})
.collect();
quote! {
impl ::godot::obj::cap::DynTrait for #class_name {
fn __register_dyn_traits() {
#(#register_fns);*
}
}
}
}

fn make_user_class_impl(
class_name: &Ident,
is_tool: bool,
Expand Down Expand Up @@ -335,6 +365,7 @@ fn parse_struct_attributes(class: &venial::Struct) -> ParseResult<ClassAttribute
let mut is_internal = false;
let mut rename: Option<Ident> = None;
let mut deprecations = vec![];
let mut dyn_traits = vec![];

// #[class] attribute on struct
if let Some(mut parser) = KvParser::parse(&class.attributes, "class")? {
Expand Down Expand Up @@ -382,6 +413,12 @@ fn parse_struct_attributes(class: &venial::Struct) -> ParseResult<ClassAttribute
});
}

if let Some(mut dyn_traits_list) = parser.handle_list("dyn_trait")? {
while let Ok(Some(token)) = dyn_traits_list.try_next_ident() {
dyn_traits.push(token);
}
}

parser.finish()?;
}

Expand All @@ -394,6 +431,7 @@ fn parse_struct_attributes(class: &venial::Struct) -> ParseResult<ClassAttribute
is_internal,
rename,
deprecations,
dyn_traits,
})
}

Expand Down
Loading
Loading