godot_core/registry/
plugin.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use std::any::Any;
9use std::{any, fmt};
10
11use crate::init::InitLevel;
12use crate::meta::ClassId;
13use crate::obj::{bounds, cap, Bounds, DynGd, Gd, GodotClass, Inherits, UserClass};
14use crate::registry::callbacks;
15use crate::registry::class::GodotGetVirtual;
16use crate::{classes, sys};
17
18// TODO(bromeon): some information coming from the proc-macro API is deferred through PluginItem, while others is directly
19// translated to code. Consider moving more code to the PluginItem, which allows for more dynamic registration and will
20// be easier for a future builder API.
21
22// ----------------------------------------------------------------------------------------------------------------------------------------------
23
24/// Piece of information that is gathered by the self-registration ("plugin") system.
25///
26/// You should not manually construct this struct, but rather use [`ClassPlugin::new()`].
27#[derive(Debug)]
28pub struct ClassPlugin {
29    /// The name of the class to register plugins for.
30    ///
31    /// This is used to group plugins so that all class properties for a single class can be registered at the same time.
32    /// Incorrectly setting this value should not cause any UB but will likely cause errors during registration time.
33    pub(crate) class_name: ClassId,
34
35    /// Which [`InitLevel`] this plugin should be registered at.
36    ///
37    /// Incorrectly setting this value should not cause any UB but will likely cause errors during registration time.
38    // Init-level is per ClassPlugin and not per PluginItem, because all components of all classes are mixed together in one
39    // huge linker list. There is no per-class aggregation going on, so this allows to easily filter relevant classes.
40    pub(crate) init_level: InitLevel,
41
42    /// The actual item being registered.
43    pub(crate) item: PluginItem,
44}
45
46impl ClassPlugin {
47    /// Creates a new `ClassPlugin`, automatically setting the `class_name` and `init_level` to the values defined in [`GodotClass`].
48    pub fn new<T: GodotClass>(item: PluginItem) -> Self {
49        Self {
50            class_name: T::class_id(),
51            init_level: T::INIT_LEVEL,
52            item,
53        }
54    }
55}
56
57// ----------------------------------------------------------------------------------------------------------------------------------------------
58// Type-erased values
59
60/// Type-erased function object, holding a function which should called during class registration.
61#[derive(Copy, Clone)]
62pub struct ErasedRegisterFn {
63    // A wrapper is needed here because Debug can't be derived on function pointers with reference parameters, so this won't work:
64    // pub type ErasedRegisterFn = fn(&mut dyn std::any::Any);
65    // (see https://stackoverflow.com/q/53380040)
66    /// The actual function to be called during class registration.
67    pub raw: fn(&mut dyn Any),
68}
69
70impl fmt::Debug for ErasedRegisterFn {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        write!(f, "0x{:0>16x}", self.raw as usize)
73    }
74}
75
76/// Type-erased function object, holding a function which should be called during RPC function registration.
77#[derive(Copy, Clone)]
78pub struct ErasedRegisterRpcsFn {
79    // A wrapper is needed here because Debug can't be derived on function pointers with reference parameters, so this won't work:
80    // pub type ErasedRegisterFn = fn(&mut dyn std::any::Any);
81    // (see https://stackoverflow.com/q/53380040)
82    /// The actual function to be called during RPC function registration.
83    ///
84    /// This should be called with a reference to the object that we want to register RPC functions for.
85    pub raw: fn(&mut dyn Any),
86}
87
88impl fmt::Debug for ErasedRegisterRpcsFn {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "0x{:0>16x}", self.raw as usize)
91    }
92}
93
94/// Type-erased function which converts a `Gd<Object>` into a `DynGd<Object, D>` for some trait object `D`.
95///
96/// See [`DynTraitImpl`] for usage.
97pub type ErasedDynifyFn = unsafe fn(Gd<classes::Object>) -> ErasedDynGd;
98
99/// Type-erased `DynGd<Object, D>` for some trait object `D`.
100///
101/// See [`DynTraitImpl`] for usage.
102pub struct ErasedDynGd {
103    pub boxed: Box<dyn Any>,
104}
105
106type GodotCreateFn = unsafe extern "C" fn(
107    _class_userdata: *mut std::ffi::c_void,
108    #[cfg(since_api = "4.4")] _notify_postinitialize: sys::GDExtensionBool,
109) -> sys::GDExtensionObjectPtr;
110
111// ----------------------------------------------------------------------------------------------------------------------------------------------
112// Plugin items
113
114/// Represents the data part of a [`ClassPlugin`] instance.
115///
116/// Each enumerator represents a different item in Rust code, which is processed by an independent proc macro (for example,
117/// `#[derive(GodotClass)]` on structs, or `#[godot_api]` on impl blocks).
118#[derive(Clone, Debug)]
119pub enum PluginItem {
120    /// Class definition itself, must always be available -- created by `#[derive(GodotClass)]`.
121    Struct(Struct),
122
123    /// Collected from `#[godot_api] impl MyClass`.
124    InherentImpl(InherentImpl),
125
126    /// Collected from `#[godot_api] impl I... for MyClass`.
127    ITraitImpl(ITraitImpl),
128
129    /// Collected from `#[godot_dyn]` macro invocations.
130    DynTraitImpl(DynTraitImpl),
131}
132
133/// Helper function which checks that the field has not been set before.
134fn set<T>(field: &mut Option<T>, value: T) {
135    assert!(field.is_none(), "attempted to set field more than once",);
136    *field = Some(value);
137}
138
139/// The data for a class definition.
140#[derive(Clone, Debug)]
141pub struct Struct {
142    /// The name of the base class in Godot.
143    ///
144    /// This must match [`GodotClass::Base`]'s class name.
145    pub(crate) base_class_name: ClassId,
146
147    /// Godot low-level `create` function, wired up to library-generated `init`.
148    ///
149    /// For `#[class(no_init)]`, behavior depends on Godot version:
150    /// - 4.5 and later: `None`
151    /// - until 4.4: a dummy function that fails, to not break hot reloading.
152    ///
153    /// This is mutually exclusive with [`ITraitImpl::user_create_fn`].
154    pub(crate) generated_create_fn: Option<GodotCreateFn>,
155
156    /// Godot low-level `recreate` function, used when hot-reloading a user class.
157    ///
158    /// This is mutually exclusive with [`ITraitImpl::user_recreate_fn`].
159    pub(crate) generated_recreate_fn: Option<
160        unsafe extern "C" fn(
161            p_class_userdata: *mut std::ffi::c_void,
162            p_object: sys::GDExtensionObjectPtr,
163        ) -> sys::GDExtensionClassInstancePtr,
164    >,
165
166    /// Callback to library-generated function which registers properties in the `struct` definition.
167    pub(crate) register_properties_fn: ErasedRegisterFn,
168
169    /// Callback on refc-increment. Only for `RefCounted` classes.
170    pub(crate) reference_fn: sys::GDExtensionClassReference,
171
172    /// Callback on refc-decrement. Only for `RefCounted` classes.
173    pub(crate) unreference_fn: sys::GDExtensionClassUnreference,
174
175    /// Function called by Godot when an object of this class is freed.
176    ///
177    /// Always implemented as [`callbacks::free`].
178    pub(crate) free_fn: unsafe extern "C" fn(
179        _class_user_data: *mut std::ffi::c_void,
180        instance: sys::GDExtensionClassInstancePtr,
181    ),
182
183    /// Calls `__before_ready()`, if there is at least one `OnReady` field. Used if there is no `#[godot_api] impl` block
184    /// overriding ready.
185    pub(crate) default_get_virtual_fn: Option<GodotGetVirtual>,
186
187    /// Whether `#[class(tool)]` was used.
188    pub(crate) is_tool: bool,
189
190    /// Whether the base class is an `EditorPlugin`.
191    pub(crate) is_editor_plugin: bool,
192
193    /// Whether `#[class(internal)]` was used.
194    pub(crate) is_internal: bool,
195
196    /// Whether the class has a default constructor.
197    pub(crate) is_instantiable: bool,
198}
199
200impl Struct {
201    pub fn new<T: GodotClass + cap::ImplementsGodotExports>() -> Self {
202        let refcounted = <T::Memory as bounds::Memory>::IS_REF_COUNTED;
203
204        Self {
205            base_class_name: T::Base::class_id(),
206            generated_create_fn: None,
207            generated_recreate_fn: None,
208            register_properties_fn: ErasedRegisterFn {
209                raw: callbacks::register_user_properties::<T>,
210            },
211            free_fn: callbacks::free::<T>,
212            default_get_virtual_fn: None,
213            is_tool: false,
214            is_editor_plugin: false,
215            is_internal: false,
216            is_instantiable: false,
217            // While Godot doesn't do anything with these callbacks for non-RefCounted classes, we can avoid instantiating them in Rust.
218            reference_fn: refcounted.then_some(callbacks::reference::<T>),
219            unreference_fn: refcounted.then_some(callbacks::unreference::<T>),
220        }
221    }
222
223    pub fn with_generated<T: GodotClass + cap::GodotDefault>(mut self) -> Self {
224        set(&mut self.generated_create_fn, callbacks::create::<T>);
225
226        set(&mut self.generated_recreate_fn, callbacks::recreate::<T>);
227        self
228    }
229
230    // Workaround for https://github.com/godot-rust/gdext/issues/874, before https://github.com/godotengine/godot/pull/99133 is merged in 4.5.
231    #[cfg(before_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.5")))]
232    pub fn with_generated_no_default<T: GodotClass>(mut self) -> Self {
233        set(&mut self.generated_create_fn, callbacks::create_null::<T>);
234
235        set(
236            &mut self.generated_recreate_fn,
237            callbacks::recreate_null::<T>,
238        );
239        self
240    }
241
242    pub fn with_default_get_virtual_fn<T: GodotClass + UserClass>(mut self) -> Self {
243        set(
244            &mut self.default_get_virtual_fn,
245            callbacks::default_get_virtual::<T>,
246        );
247        self
248    }
249
250    pub fn with_tool(mut self) -> Self {
251        self.is_tool = true;
252        self
253    }
254
255    pub fn with_editor_plugin(mut self) -> Self {
256        self.is_editor_plugin = true;
257        self
258    }
259
260    pub fn with_internal(mut self) -> Self {
261        self.is_internal = true;
262        self
263    }
264
265    pub fn with_instantiable(mut self) -> Self {
266        self.is_instantiable = true;
267        self
268    }
269}
270
271/// Stores registration functions for methods, constants, and documentation from inherent `#[godot_api]` impl blocks.
272#[derive(Clone, Debug)]
273pub struct InherentImpl {
274    /// Callback to library-generated function which registers functions and constants in the `impl` block.
275    ///
276    /// Always present since that's the entire point of this `impl` block.
277    pub(crate) register_methods_constants_fn: ErasedRegisterFn,
278
279    /// Callback to library-generated function which calls [`Node::rpc_config`](crate::classes::Node::rpc_config) for each function annotated
280    /// with `#[rpc]` on the `impl` block.
281    ///
282    /// This function is called in [`UserClass::__before_ready()`](crate::obj::UserClass::__before_ready) definitions generated by the
283    /// `#[derive(GodotClass)]` macro.
284    // This field is only used during codegen-full.
285    #[cfg_attr(not(feature = "codegen-full"), expect(dead_code))]
286    pub(crate) register_rpcs_fn: Option<ErasedRegisterRpcsFn>,
287}
288
289impl InherentImpl {
290    pub fn new<T: cap::ImplementsGodotApi>() -> Self {
291        Self {
292            register_methods_constants_fn: ErasedRegisterFn {
293                raw: callbacks::register_user_methods_constants::<T>,
294            },
295            register_rpcs_fn: Some(ErasedRegisterRpcsFn {
296                raw: callbacks::register_user_rpcs::<T>,
297            }),
298        }
299    }
300}
301
302#[derive(Default, Clone, Debug)]
303pub struct ITraitImpl {
304    /// Callback to user-defined `register_class` function.
305    pub(crate) user_register_fn: Option<ErasedRegisterFn>,
306
307    /// Godot low-level `create` function, wired up to the user's `init`.
308    ///
309    /// This is mutually exclusive with [`Struct::generated_create_fn`].
310    pub(crate) user_create_fn: Option<GodotCreateFn>,
311
312    /// Godot low-level `recreate` function, used when hot-reloading a user class.
313    ///
314    /// This is mutually exclusive with [`Struct::generated_recreate_fn`].
315    pub(crate) user_recreate_fn: Option<
316        unsafe extern "C" fn(
317            p_class_userdata: *mut ::std::os::raw::c_void,
318            p_object: sys::GDExtensionObjectPtr,
319        ) -> sys::GDExtensionClassInstancePtr,
320    >,
321
322    /// User-defined `to_string` function.
323    pub(crate) user_to_string_fn: Option<
324        unsafe extern "C" fn(
325            p_instance: sys::GDExtensionClassInstancePtr,
326            r_is_valid: *mut sys::GDExtensionBool,
327            r_out: sys::GDExtensionStringPtr,
328        ),
329    >,
330
331    /// User-defined `on_notification` function.
332    pub(crate) user_on_notification_fn: Option<
333        unsafe extern "C" fn(
334            p_instance: sys::GDExtensionClassInstancePtr,
335            p_what: i32,
336            p_reversed: sys::GDExtensionBool,
337        ),
338    >,
339
340    /// User-defined `set_property` function.
341    pub(crate) user_set_fn: Option<
342        unsafe extern "C" fn(
343            p_instance: sys::GDExtensionClassInstancePtr,
344            p_name: sys::GDExtensionConstStringNamePtr,
345            p_value: sys::GDExtensionConstVariantPtr,
346        ) -> sys::GDExtensionBool,
347    >,
348
349    /// User-defined `get_property` function.
350    pub(crate) user_get_fn: Option<
351        unsafe extern "C" fn(
352            p_instance: sys::GDExtensionClassInstancePtr,
353            p_name: sys::GDExtensionConstStringNamePtr,
354            r_ret: sys::GDExtensionVariantPtr,
355        ) -> sys::GDExtensionBool,
356    >,
357
358    /// Callback for other virtual methods specific to each class.
359    pub(crate) get_virtual_fn: Option<GodotGetVirtual>,
360
361    /// User-defined `get_property_list` function.
362    pub(crate) user_get_property_list_fn: Option<
363        unsafe extern "C" fn(
364            p_instance: sys::GDExtensionClassInstancePtr,
365            r_count: *mut u32,
366        ) -> *const sys::GDExtensionPropertyInfo,
367    >,
368
369    // 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
370    // this in Godot < 4.3.
371    #[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
372    pub(crate) user_free_property_list_fn: Option<
373        unsafe extern "C" fn(
374            p_instance: sys::GDExtensionClassInstancePtr,
375            p_list: *const sys::GDExtensionPropertyInfo,
376        ),
377    >,
378    /// Frees the property list created in the user-defined `get_property_list` function.
379    #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
380    pub(crate) user_free_property_list_fn: Option<
381        unsafe extern "C" fn(
382            p_instance: sys::GDExtensionClassInstancePtr,
383            p_list: *const sys::GDExtensionPropertyInfo,
384            p_count: u32,
385        ),
386    >,
387
388    /// Part of user-defined `property_get_revert` function.
389    ///
390    /// This effectively just calls [`Option::is_some`] on the return value of the `property_get_revert` function.
391    pub(crate) user_property_can_revert_fn: Option<
392        unsafe extern "C" fn(
393            p_instance: sys::GDExtensionClassInstancePtr,
394            p_name: sys::GDExtensionConstStringNamePtr,
395        ) -> sys::GDExtensionBool,
396    >,
397
398    /// Part of user-defined `property_get_revert` function.
399    ///
400    /// This returns null when the return value of `property_get_revert` is `None`, and otherwise returns the value contained
401    /// within the `Some`.
402    pub(crate) user_property_get_revert_fn: Option<
403        unsafe extern "C" fn(
404            p_instance: sys::GDExtensionClassInstancePtr,
405            p_name: sys::GDExtensionConstStringNamePtr,
406            r_ret: sys::GDExtensionVariantPtr,
407        ) -> sys::GDExtensionBool,
408    >,
409    pub(crate) validate_property_fn: Option<
410        unsafe extern "C" fn(
411            p_instance: sys::GDExtensionClassInstancePtr,
412            p_property: *mut sys::GDExtensionPropertyInfo,
413        ) -> sys::GDExtensionBool,
414    >,
415}
416
417impl ITraitImpl {
418    pub fn new<T: GodotClass + cap::ImplementsGodotVirtual>() -> Self {
419        Self {
420            get_virtual_fn: Some(callbacks::get_virtual::<T>),
421            ..Default::default()
422        }
423    }
424
425    pub fn with_register<T: GodotClass + cap::GodotRegisterClass>(mut self) -> Self {
426        set(
427            &mut self.user_register_fn,
428            ErasedRegisterFn {
429                raw: callbacks::register_class_by_builder::<T>,
430            },
431        );
432
433        self
434    }
435
436    pub fn with_create<T: GodotClass + cap::GodotDefault>(mut self) -> Self {
437        set(&mut self.user_create_fn, callbacks::create::<T>);
438
439        #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
440        set(&mut self.user_recreate_fn, callbacks::recreate::<T>);
441        self
442    }
443
444    pub fn with_string<T: GodotClass + cap::GodotToString>(mut self) -> Self {
445        set(&mut self.user_to_string_fn, callbacks::to_string::<T>);
446        self
447    }
448
449    pub fn with_on_notification<T: GodotClass + cap::GodotNotification>(mut self) -> Self {
450        set(
451            &mut self.user_on_notification_fn,
452            callbacks::on_notification::<T>,
453        );
454        self
455    }
456
457    pub fn with_get_property<T: GodotClass + cap::GodotGet>(mut self) -> Self {
458        set(&mut self.user_get_fn, callbacks::get_property::<T>);
459        self
460    }
461
462    pub fn with_set_property<T: GodotClass + cap::GodotSet>(mut self) -> Self {
463        set(&mut self.user_set_fn, callbacks::set_property::<T>);
464        self
465    }
466
467    pub fn with_get_property_list<T: GodotClass + cap::GodotGetPropertyList>(mut self) -> Self {
468        set(
469            &mut self.user_get_property_list_fn,
470            callbacks::get_property_list::<T>,
471        );
472
473        #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
474        set(
475            &mut self.user_free_property_list_fn,
476            callbacks::free_property_list::<T>,
477        );
478        self
479    }
480
481    pub fn with_property_get_revert<T: GodotClass + cap::GodotPropertyGetRevert>(mut self) -> Self {
482        set(
483            &mut self.user_property_get_revert_fn,
484            callbacks::property_get_revert::<T>,
485        );
486        set(
487            &mut self.user_property_can_revert_fn,
488            callbacks::property_can_revert::<T>,
489        );
490        self
491    }
492
493    pub fn with_validate_property<T: GodotClass + cap::GodotValidateProperty>(mut self) -> Self {
494        set(
495            &mut self.validate_property_fn,
496            callbacks::validate_property::<T>,
497        );
498        self
499    }
500}
501
502/// Representation of a `#[godot_dyn]` invocation.
503///
504/// Stores all the information needed for `DynGd` re-enrichment.
505#[derive(Clone, Debug)]
506pub struct DynTraitImpl {
507    /// The class that this `dyn Trait` implementation corresponds to.
508    class_name: ClassId,
509
510    /// Base inherited class required for `DynGd<T, D>` exports (i.e. one specified in `#[class(base = ...)]`).
511    ///
512    /// Godot doesn't guarantee availability of all the GDExtension classes through the ClassDb while generating `PropertyHintInfo` for our exports.
513    /// Therefore, we rely on the built-in inherited base class in such cases.
514    /// Only [`class_name`][DynTraitImpl::class_name] is available at the time of adding given `DynTraitImpl` to plugin registry with `#[godot_dyn]`;
515    /// It is important to fill this information before registration.
516    ///
517    /// See also [`get_dyn_property_hint_string`][crate::registry::class::get_dyn_property_hint_string].
518    pub(crate) parent_class_name: Option<ClassId>,
519
520    /// TypeId of the `dyn Trait` object.
521    dyn_trait_typeid: any::TypeId,
522
523    /// Function used to get a `DynGd<T,D>` from a `Gd<Object>`. This is used in the [`FromGodot`](crate::meta::FromGodot) implementation
524    /// of [`DynGd`]. This function is always implemented as [`callbacks::dynify_fn::<T, D>`] where `T` is the class represented by `class_name`
525    /// and `D` is the trait object corresponding to `dyn_trait_typeid`.
526    ///
527    /// Function that converts a `Gd<Object>` to a type-erased `DynGd<Object, dyn Trait>` (with the latter erased for common storage).
528    erased_dynify_fn: ErasedDynifyFn,
529}
530
531impl DynTraitImpl {
532    pub fn new<T, D>() -> Self
533    where
534        T: GodotClass
535            + Inherits<classes::Object>
536            + crate::obj::AsDyn<D>
537            + Bounds<Declarer = bounds::DeclUser>,
538        D: ?Sized + 'static,
539    {
540        Self {
541            class_name: T::class_id(),
542            parent_class_name: None,
543            dyn_trait_typeid: std::any::TypeId::of::<D>(),
544            erased_dynify_fn: callbacks::dynify_fn::<T, D>,
545        }
546    }
547
548    /// The class that this `dyn Trait` implementation corresponds to.
549    pub fn class_name(&self) -> &ClassId {
550        &self.class_name
551    }
552
553    /// The type id of the trait object this was registered with.
554    pub fn dyn_trait_typeid(&self) -> any::TypeId {
555        self.dyn_trait_typeid
556    }
557
558    /// Convert a [`Gd<T>`] to a [`DynGd<T, D>`] using `self`.
559    ///
560    /// This will fail with `Err(object)` if the dynamic class of `object` does not match the [`ClassId`] stored in `self`.
561    pub fn get_dyn_gd<T: GodotClass, D: ?Sized + 'static>(
562        &self,
563        object: Gd<T>,
564    ) -> Result<DynGd<T, D>, Gd<T>> {
565        let dynamic_class = object.dynamic_class_string();
566
567        if dynamic_class != self.class_name.to_string_name() {
568            return Err(object);
569        }
570
571        let object = object.upcast_object();
572
573        // SAFETY: `DynTraitImpl::new` ensures that this function is safe to call when `object` is castable to `self.class_name`.
574        // Since the dynamic class of `object` is `self.class_name`, it must be castable to `self.class_name`.
575        let erased_dyn = unsafe { (self.erased_dynify_fn)(object) };
576
577        let dyn_gd_object = erased_dyn.boxed.downcast::<DynGd<classes::Object, D>>();
578
579        // SAFETY: `callbacks::dynify_fn` returns a `DynGd<Object, D>` which has been type erased. So this downcast will always succeed.
580        let dyn_gd_object = unsafe { dyn_gd_object.unwrap_unchecked() };
581
582        // SAFETY: This is effectively upcasting a value which has class equal to `self.class_name` to a `DynGd<T, D>`. Since the class of
583        // `object` is `T` and its dynamic class is `self.class_name`, this means that `T` must be a superclass of `self.class_name`. Thus
584        // this upcast is safe.
585        let dyn_gd_t = unsafe { dyn_gd_object.cast_unchecked::<T>() };
586
587        Ok(dyn_gd_t)
588    }
589}