godot_core/registry/
class.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::collections::HashMap;
9use std::{any, ptr};
10
11use godot_ffi::join_with;
12use sys::{interface_fn, out, Global, GlobalGuard, GlobalLockError};
13
14use crate::classes::ClassDb;
15use crate::init::InitLevel;
16use crate::meta::error::FromGodotError;
17use crate::meta::ClassName;
18use crate::obj::{cap, DynGd, Gd, GodotClass};
19use crate::private::{ClassPlugin, PluginItem};
20use crate::registry::callbacks;
21use crate::registry::plugin::{DynTraitImpl, ErasedRegisterFn, ITraitImpl, InherentImpl, Struct};
22use crate::{classes, godot_error, godot_warn, sys};
23
24/// Returns a lock to a global map of loaded classes, by initialization level.
25///
26/// Needed for class unregistering. The `static` is populated during class registering. There is no actual concurrency here, because Godot
27/// calls register/unregister in the main thread. Mutex is just casual way to ensure safety in this non-performance-critical path.
28/// Note that we panic on concurrent access instead of blocking (fail-fast approach). If that happens, most likely something changed on Godot
29/// side and analysis required to adopt these changes.
30fn global_loaded_classes_by_init_level(
31) -> GlobalGuard<'static, HashMap<InitLevel, Vec<LoadedClass>>> {
32    static LOADED_CLASSES_BY_INIT_LEVEL: Global<
33        HashMap<InitLevel, Vec<LoadedClass>>, //.
34    > = Global::default();
35
36    lock_or_panic(&LOADED_CLASSES_BY_INIT_LEVEL, "loaded classes")
37}
38
39/// Returns a lock to a global map of loaded classes, by class name.
40///
41/// Complementary mechanism to the on-registration hooks like `__register_methods()`. This is used for runtime queries about a class, for
42/// information which isn't stored in Godot. Example: list related `dyn Trait` implementations.
43fn global_loaded_classes_by_name() -> GlobalGuard<'static, HashMap<ClassName, ClassMetadata>> {
44    static LOADED_CLASSES_BY_NAME: Global<HashMap<ClassName, ClassMetadata>> = Global::default();
45
46    lock_or_panic(&LOADED_CLASSES_BY_NAME, "loaded classes (by name)")
47}
48
49fn global_dyn_traits_by_typeid() -> GlobalGuard<'static, HashMap<any::TypeId, Vec<DynTraitImpl>>> {
50    static DYN_TRAITS_BY_TYPEID: Global<HashMap<any::TypeId, Vec<DynTraitImpl>>> =
51        Global::default();
52
53    lock_or_panic(&DYN_TRAITS_BY_TYPEID, "dyn traits")
54}
55
56// ----------------------------------------------------------------------------------------------------------------------------------------------
57
58/// Represents a class which is currently loaded and retained in memory.
59///
60/// Besides the name, this type holds information relevant for the deregistration of the class.
61pub struct LoadedClass {
62    name: ClassName,
63    is_editor_plugin: bool,
64}
65
66/// Represents a class which is currently loaded and retained in memory -- including metadata.
67//
68// Currently empty, but should already work for per-class queries.
69pub struct ClassMetadata {}
70
71// ----------------------------------------------------------------------------------------------------------------------------------------------
72
73// This works as long as fields are called the same. May still need individual #[cfg]s for newer fields.
74#[cfg(before_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.2")))]
75type GodotCreationInfo = sys::GDExtensionClassCreationInfo;
76#[cfg(all(since_api = "4.2", before_api = "4.3"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.2", before_api = "4.3"))))]
77type GodotCreationInfo = sys::GDExtensionClassCreationInfo2;
78#[cfg(all(since_api = "4.3", before_api = "4.4"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", before_api = "4.4"))))]
79type GodotCreationInfo = sys::GDExtensionClassCreationInfo3;
80#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
81type GodotCreationInfo = sys::GDExtensionClassCreationInfo4;
82
83#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
84pub(crate) type GodotGetVirtual = <sys::GDExtensionClassGetVirtual as sys::Inner>::FnPtr;
85#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
86pub(crate) type GodotGetVirtual = <sys::GDExtensionClassGetVirtual2 as sys::Inner>::FnPtr;
87
88#[derive(Debug)]
89struct ClassRegistrationInfo {
90    class_name: ClassName,
91    parent_class_name: Option<ClassName>,
92    // Following functions are stored separately, since their order matters.
93    register_methods_constants_fn: Option<ErasedRegisterFn>,
94    register_properties_fn: Option<ErasedRegisterFn>,
95    user_register_fn: Option<ErasedRegisterFn>,
96    default_virtual_fn: Option<GodotGetVirtual>, // Optional (set if there is at least one OnReady field)
97    user_virtual_fn: Option<GodotGetVirtual>, // Optional (set if there is a `#[godot_api] impl I*`)
98
99    /// Godot low-level class creation parameters.
100    godot_params: GodotCreationInfo,
101
102    #[allow(dead_code)] // Currently unused; may be useful for diagnostics in the future.
103    init_level: InitLevel,
104    is_editor_plugin: bool,
105
106    /// One entry for each `dyn Trait` implemented (and registered) for this class.
107    dynify_fns_by_trait: HashMap<any::TypeId, DynTraitImpl>,
108
109    /// Used to ensure that each component is only filled once.
110    component_already_filled: [bool; 4],
111}
112
113impl ClassRegistrationInfo {
114    fn validate_unique(&mut self, item: &PluginItem) {
115        // We could use mem::Discriminant, but match will fail to compile when a new component is added.
116
117        // Note: when changing this match, make sure the array has sufficient size.
118        let index = match item {
119            PluginItem::Struct { .. } => 0,
120            PluginItem::InherentImpl(_) => 1,
121            PluginItem::ITraitImpl { .. } => 2,
122
123            // Multiple dyn traits can be registered, thus don't validate for uniqueness.
124            // (Still keep array size, so future additions don't have to regard this).
125            PluginItem::DynTraitImpl { .. } => return,
126        };
127
128        if self.component_already_filled[index] {
129            panic!(
130                "Godot class `{}` is defined multiple times in Rust; you can rename it with #[class(rename=NewName)]",
131                self.class_name,
132            )
133        }
134
135        self.component_already_filled[index] = true;
136    }
137}
138
139/// Registers a class with static type information.
140#[expect(dead_code)] // Will be needed for builder API. Don't remove.
141pub(crate) fn register_class<
142    T: cap::GodotDefault
143        + cap::ImplementsGodotVirtual
144        + cap::GodotToString
145        + cap::GodotNotification
146        + cap::GodotRegisterClass
147        + GodotClass,
148>() {
149    // TODO: provide overloads with only some trait impls
150
151    out!("Manually register class {}", std::any::type_name::<T>());
152
153    let godot_params = GodotCreationInfo {
154        to_string_func: Some(callbacks::to_string::<T>),
155        notification_func: Some(callbacks::on_notification::<T>),
156        reference_func: Some(callbacks::reference::<T>),
157        unreference_func: Some(callbacks::unreference::<T>),
158        create_instance_func: Some(callbacks::create::<T>),
159        free_instance_func: Some(callbacks::free::<T>),
160        get_virtual_func: Some(callbacks::get_virtual::<T>),
161        class_userdata: ptr::null_mut(), // will be passed to create fn, but global per class
162        ..default_creation_info()
163    };
164
165    assert!(
166        !T::class_name().is_none(),
167        "cannot register () or unnamed class"
168    );
169
170    register_class_raw(ClassRegistrationInfo {
171        class_name: T::class_name(),
172        parent_class_name: Some(T::Base::class_name()),
173        register_methods_constants_fn: None,
174        register_properties_fn: None,
175        user_register_fn: Some(ErasedRegisterFn {
176            raw: callbacks::register_class_by_builder::<T>,
177        }),
178        user_virtual_fn: None,
179        default_virtual_fn: None,
180        godot_params,
181        init_level: T::INIT_LEVEL,
182        is_editor_plugin: false,
183        dynify_fns_by_trait: HashMap::new(),
184        component_already_filled: Default::default(), // [false; N]
185    });
186}
187
188/// Lets Godot know about all classes that have self-registered through the plugin system.
189pub fn auto_register_classes(init_level: InitLevel) {
190    out!("Auto-register classes at level `{init_level:?}`...");
191
192    // Note: many errors are already caught by the compiler, before this runtime validation even takes place:
193    // * missing #[derive(GodotClass)] or impl GodotClass for T
194    // * duplicate impl GodotDefault for T
195    //
196    let mut map = HashMap::<ClassName, ClassRegistrationInfo>::new();
197
198    crate::private::iterate_plugins(|elem: &ClassPlugin| {
199        // Filter per ClassPlugin and not PluginItem, because all components of all classes are mixed together in one huge list.
200        if elem.init_level != init_level {
201            return;
202        }
203
204        //out!("* Plugin: {elem:#?}");
205
206        let name = elem.class_name;
207        let class_info = map
208            .entry(name)
209            .or_insert_with(|| default_registration_info(name));
210
211        fill_class_info(elem.item.clone(), class_info);
212    });
213
214    // First register all the loaded classes and dyn traits.
215    // We need all the dyn classes in the registry to properly register DynGd properties;
216    // one can do it directly inside the loop – by locking and unlocking the mutex –
217    // but it is much slower and doesn't guarantee that all the dependent classes will be already loaded in most cases.
218    register_classes_and_dyn_traits(&mut map, init_level);
219
220    // Editor plugins should be added to the editor AFTER all the classes has been registered.
221    // Adding EditorPlugin to the Editor before registering all the classes it depends on might result in crash.
222    let mut editor_plugins: Vec<ClassName> = Vec::new();
223
224    // Actually register all the classes.
225    for info in map.into_values() {
226        #[cfg(feature = "debug-log")] #[cfg_attr(published_docs, doc(cfg(feature = "debug-log")))]
227        let class_name = info.class_name;
228
229        if info.is_editor_plugin {
230            editor_plugins.push(info.class_name);
231        }
232
233        register_class_raw(info);
234
235        out!("Class {class_name} loaded.");
236    }
237
238    // Will imminently add given class to the editor.
239    // It is expected and beneficial behaviour while we load library for the first time
240    // but (for now) might lead to some issues during hot reload.
241    // See also: (https://github.com/godot-rust/gdext/issues/1132)
242    for editor_plugin_class_name in editor_plugins {
243        unsafe { interface_fn!(editor_add_plugin)(editor_plugin_class_name.string_sys()) };
244    }
245
246    out!("All classes for level `{init_level:?}` auto-registered.");
247}
248
249fn register_classes_and_dyn_traits(
250    map: &mut HashMap<ClassName, ClassRegistrationInfo>,
251    init_level: InitLevel,
252) {
253    let mut loaded_classes_by_level = global_loaded_classes_by_init_level();
254    let mut loaded_classes_by_name = global_loaded_classes_by_name();
255    let mut dyn_traits_by_typeid = global_dyn_traits_by_typeid();
256
257    for info in map.values_mut() {
258        let class_name = info.class_name;
259        out!("Register class:   {class_name} at level `{init_level:?}`");
260
261        let loaded_class = LoadedClass {
262            name: class_name,
263            is_editor_plugin: info.is_editor_plugin,
264        };
265        let metadata = ClassMetadata {};
266
267        // Transpose Class->Trait relations to Trait->Class relations.
268        for (trait_type_id, mut dyn_trait_impl) in info.dynify_fns_by_trait.drain() {
269            // Note: Must be done after filling out the class info since plugins are being iterated in unspecified order.
270            dyn_trait_impl.parent_class_name = info.parent_class_name;
271
272            dyn_traits_by_typeid
273                .entry(trait_type_id)
274                .or_default()
275                .push(dyn_trait_impl);
276        }
277
278        loaded_classes_by_level
279            .entry(init_level)
280            .or_default()
281            .push(loaded_class);
282
283        loaded_classes_by_name.insert(class_name, metadata);
284    }
285}
286
287pub fn unregister_classes(init_level: InitLevel) {
288    let mut loaded_classes_by_level = global_loaded_classes_by_init_level();
289    let mut loaded_classes_by_name = global_loaded_classes_by_name();
290    // TODO clean up dyn traits
291
292    let loaded_classes_current_level = loaded_classes_by_level
293        .remove(&init_level)
294        .unwrap_or_default();
295
296    out!("Unregister classes of level {init_level:?}...");
297    for class in loaded_classes_current_level.into_iter().rev() {
298        // Remove from other map.
299        loaded_classes_by_name.remove(&class.name);
300
301        // Unregister from Godot.
302        unregister_class_raw(class);
303    }
304}
305
306#[cfg(feature = "codegen-full")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-full")))]
307pub fn auto_register_rpcs<T: GodotClass>(object: &mut T) {
308    // Find the element that matches our class, and call the closure if it exists.
309    if let Some(InherentImpl {
310        register_rpcs_fn: Some(closure),
311        ..
312    }) = crate::private::find_inherent_impl(T::class_name())
313    {
314        (closure.raw)(object);
315    }
316}
317
318/// Tries to convert a `Gd<T>` to a `DynGd<T, D>` for some class `T` and trait object `D`, where the trait may only be implemented for
319/// some subclass of `T`.
320///
321/// This works even when `T` doesn't implement `AsDyn<D>`, as long as the dynamic class of `object` implements `AsDyn<D>`.
322///
323/// This only looks for an `AsDyn<D>` implementation in the dynamic class; the conversion will fail if the dynamic class doesn't
324/// implement `AsDyn<D>`, even if there exists some superclass that does implement `AsDyn<D>`. This restriction could in theory be
325/// lifted, but would need quite a bit of extra machinery to work.
326pub(crate) fn try_dynify_object<T: GodotClass, D: ?Sized + 'static>(
327    mut object: Gd<T>,
328) -> Result<DynGd<T, D>, (FromGodotError, Gd<T>)> {
329    let typeid = any::TypeId::of::<D>();
330    let trait_name = sys::short_type_name::<D>();
331
332    // Iterate all classes that implement the trait.
333    let dyn_traits_by_typeid = global_dyn_traits_by_typeid();
334    let Some(relations) = dyn_traits_by_typeid.get(&typeid) else {
335        return Err((FromGodotError::UnregisteredDynTrait { trait_name }, object));
336    };
337
338    // TODO maybe use 2nd hashmap instead of linear search.
339    // (probably not pair of typeid/classname, as that wouldn't allow the above check).
340    for relation in relations {
341        match relation.get_dyn_gd(object) {
342            Ok(dyn_gd) => return Ok(dyn_gd),
343            Err(obj) => object = obj,
344        }
345    }
346
347    let error = FromGodotError::UnimplementedDynTrait {
348        trait_name,
349        class_name: object.dynamic_class_string().to_string(),
350    };
351
352    Err((error, object))
353}
354
355/// Responsible for creating hint_string for [`DynGd<T, D>`][crate::obj::DynGd] properties which works with [`PropertyHint::NODE_TYPE`][crate::global::PropertyHint::NODE_TYPE] or [`PropertyHint::RESOURCE_TYPE`][crate::global::PropertyHint::RESOURCE_TYPE].
356///
357/// Godot offers very limited capabilities when it comes to validating properties in the editor if given class isn't a tool.
358/// Proper hint string combined with `PropertyHint::RESOURCE_TYPE` allows to limit selection only to valid classes - those registered as implementors of given `DynGd<T, D>`'s `D` trait.
359/// Godot editor allows to export only one node type with `PropertyHint::NODE_TYPE` – therefore we are returning only the base class.
360///
361/// See also [Godot docs for PropertyHint](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#enum-globalscope-propertyhint).
362pub(crate) fn get_dyn_property_hint_string<T, D>() -> String
363where
364    T: GodotClass,
365    D: ?Sized + 'static,
366{
367    // Exporting multiple node types is not supported.
368    if T::inherits::<classes::Node>() {
369        return T::class_name().to_string();
370    }
371
372    let typeid = any::TypeId::of::<D>();
373    let dyn_traits_by_typeid = global_dyn_traits_by_typeid();
374
375    let Some(relations) = dyn_traits_by_typeid.get(&typeid) else {
376        let trait_name = sys::short_type_name::<D>();
377        godot_warn!(
378            "godot-rust: No class has been linked to trait {trait_name} with #[godot_dyn]."
379        );
380        return String::new();
381    };
382    assert!(
383        !relations.is_empty(),
384        "Trait {trait_name} has been registered as DynGd Trait \
385        despite no class being related to it \n\
386        **this is a bug, please report it**",
387        trait_name = sys::short_type_name::<D>()
388    );
389
390    // Include only implementors inheriting given T.
391    // For example – don't include Nodes or Objects while creating hint_string for Resource.
392    let relations_iter = relations.iter().filter_map(|implementor| {
393        // TODO – check if caching it (using is_derived_base_cached) yields any benefits.
394        if implementor.parent_class_name? == T::class_name()
395            || ClassDb::singleton().is_parent_class(
396                &implementor.parent_class_name?.to_string_name(),
397                &T::class_name().to_string_name(),
398            )
399        {
400            Some(implementor)
401        } else {
402            None
403        }
404    });
405
406    join_with(relations_iter, ", ", |dyn_trait| {
407        dyn_trait.class_name().to_cow_str()
408    })
409}
410
411/// Populate `c` with all the relevant data from `component` (depending on component type).
412fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
413    c.validate_unique(&item);
414
415    // out!("|   reg (before):    {c:?}");
416    // out!("|   comp:            {component:?}");
417    match item {
418        PluginItem::Struct(Struct {
419            base_class_name,
420            generated_create_fn,
421            generated_recreate_fn,
422            register_properties_fn,
423            free_fn,
424            default_get_virtual_fn,
425            is_tool,
426            is_editor_plugin,
427            is_internal,
428            is_instantiable,
429            #[cfg(all(since_api = "4.3", feature = "register-docs"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", feature = "register-docs"))))]
430                docs: _,
431            reference_fn,
432            unreference_fn,
433        }) => {
434            c.parent_class_name = Some(base_class_name);
435            c.default_virtual_fn = default_get_virtual_fn;
436            c.register_properties_fn = Some(register_properties_fn);
437            c.is_editor_plugin = is_editor_plugin;
438
439            // Classes marked #[class(no_init)] are translated to "abstract" in Godot. This disables their default constructor.
440            // "Abstract" is a misnomer -- it's not an abstract base class, but rather a "utility/static class" (although it can have instance
441            // methods). Examples are Input, IP, FileAccess, DisplayServer.
442            //
443            // Abstract base classes on the other hand are called "virtual" in Godot. Examples are Mesh, Material, Texture.
444            // For some reason, certain ABCs like PhysicsBody2D are not marked "virtual" but "abstract".
445            //
446            // See also: https://github.com/godotengine/godot/pull/58972
447            c.godot_params.is_abstract = sys::conv::bool_to_sys(!is_instantiable);
448            c.godot_params.free_instance_func = Some(free_fn);
449            c.godot_params.reference_func = reference_fn;
450            c.godot_params.unreference_func = unreference_fn;
451
452            fill_into(
453                &mut c.godot_params.create_instance_func,
454                generated_create_fn,
455            )
456            .expect("duplicate: create_instance_func (def)");
457
458            #[cfg(before_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.2")))]
459            let _ = is_internal; // mark used
460            #[cfg(since_api = "4.2")]
461            {
462                fill_into(
463                    &mut c.godot_params.recreate_instance_func,
464                    generated_recreate_fn,
465                )
466                .expect("duplicate: recreate_instance_func (def)");
467
468                c.godot_params.is_exposed = sys::conv::bool_to_sys(!is_internal);
469            }
470
471            #[cfg(before_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.2")))]
472            assert!(generated_recreate_fn.is_none()); // not used
473
474            #[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
475            let _ = is_tool; // mark used
476            #[cfg(since_api = "4.3")]
477            {
478                c.godot_params.is_runtime =
479                    sys::conv::bool_to_sys(crate::private::is_class_runtime(is_tool));
480            }
481        }
482
483        PluginItem::InherentImpl(InherentImpl {
484            register_methods_constants_fn,
485            register_rpcs_fn: _,
486            #[cfg(all(since_api = "4.3", feature = "register-docs"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", feature = "register-docs"))))]
487                docs: _,
488        }) => {
489            c.register_methods_constants_fn = Some(register_methods_constants_fn);
490        }
491
492        PluginItem::ITraitImpl(ITraitImpl {
493            user_register_fn,
494            user_create_fn,
495            user_recreate_fn,
496            user_to_string_fn,
497            user_on_notification_fn,
498            user_set_fn,
499            user_get_fn,
500            get_virtual_fn,
501            user_get_property_list_fn,
502            user_free_property_list_fn,
503            user_property_can_revert_fn,
504            user_property_get_revert_fn,
505            #[cfg(all(since_api = "4.3", feature = "register-docs"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", feature = "register-docs"))))]
506                virtual_method_docs: _,
507            #[cfg(since_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.2")))]
508            validate_property_fn,
509        }) => {
510            c.user_register_fn = user_register_fn;
511
512            // The following unwraps of fill_into() shouldn't panic, since rustc will error if there are
513            // multiple `impl I{Class} for Thing` definitions.
514
515            fill_into(&mut c.godot_params.create_instance_func, user_create_fn)
516                .expect("duplicate: create_instance_func (i)");
517
518            #[cfg(since_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.2")))]
519            fill_into(&mut c.godot_params.recreate_instance_func, user_recreate_fn)
520                .expect("duplicate: recreate_instance_func (i)");
521
522            #[cfg(before_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.2")))]
523            assert!(user_recreate_fn.is_none()); // not used
524
525            c.godot_params.to_string_func = user_to_string_fn;
526            c.godot_params.notification_func = user_on_notification_fn;
527            c.godot_params.set_func = user_set_fn;
528            c.godot_params.get_func = user_get_fn;
529            c.godot_params.get_property_list_func = user_get_property_list_fn;
530            c.godot_params.free_property_list_func = user_free_property_list_fn;
531            c.godot_params.property_can_revert_func = user_property_can_revert_fn;
532            c.godot_params.property_get_revert_func = user_property_get_revert_fn;
533            c.user_virtual_fn = get_virtual_fn;
534            #[cfg(since_api = "4.2")]
535            {
536                c.godot_params.validate_property_func = validate_property_fn;
537            }
538        }
539        PluginItem::DynTraitImpl(dyn_trait_impl) => {
540            let type_id = dyn_trait_impl.dyn_trait_typeid();
541
542            let prev = c.dynify_fns_by_trait.insert(type_id, dyn_trait_impl);
543
544            assert!(
545                prev.is_none(),
546                "Duplicate registration of {:?} for class {}",
547                type_id,
548                c.class_name
549            );
550        }
551    }
552    // out!("|   reg (after):     {c:?}");
553    // out!();
554}
555
556/// If `src` is occupied, it moves the value into `dst`, while ensuring that no previous value is present in `dst`.
557fn fill_into<T>(dst: &mut Option<T>, src: Option<T>) -> Result<(), ()> {
558    match (dst, src) {
559        (dst @ None, src) => *dst = src,
560        (Some(_), Some(_)) => return Err(()),
561        (Some(_), None) => { /* do nothing */ }
562    }
563    Ok(())
564}
565
566/// Registers a class with given the dynamic type information `info`.
567fn register_class_raw(mut info: ClassRegistrationInfo) {
568    // Some metadata like dynify fns are already emptied at this point. Only consider registrations for Godot.
569
570    // First register class...
571    validate_class_constraints(&info);
572
573    let class_name = info.class_name;
574    let parent_class_name = info
575        .parent_class_name
576        .expect("class defined (parent_class_name)");
577
578    // Register virtual functions -- if the user provided some via #[godot_api], take those; otherwise, use the
579    // ones generated alongside #[derive(GodotClass)]. The latter can also be null, if no OnReady is provided.
580    if info.godot_params.get_virtual_func.is_none() {
581        info.godot_params.get_virtual_func = info.user_virtual_fn.or(info.default_virtual_fn);
582    }
583
584    // The explicit () type notifies us if Godot API ever adds a return type.
585    let registration_failed = unsafe {
586        // Try to register class...
587
588        #[cfg(before_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.2")))]
589        let register_fn = interface_fn!(classdb_register_extension_class);
590
591        #[cfg(all(since_api = "4.2", before_api = "4.3"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.2", before_api = "4.3"))))]
592        let register_fn = interface_fn!(classdb_register_extension_class2);
593
594        #[cfg(all(since_api = "4.3", before_api = "4.4"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", before_api = "4.4"))))]
595        let register_fn = interface_fn!(classdb_register_extension_class3);
596
597        #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
598        let register_fn = interface_fn!(classdb_register_extension_class4);
599
600        let _: () = register_fn(
601            sys::get_library(),
602            class_name.string_sys(),
603            parent_class_name.string_sys(),
604            ptr::addr_of!(info.godot_params),
605        );
606
607        // ...then see if it worked.
608        // This is necessary because the above registration does not report errors (apart from console output).
609        let tag = interface_fn!(classdb_get_class_tag)(class_name.string_sys());
610        tag.is_null()
611    };
612
613    // Do not panic here; otherwise lock is poisoned and the whole extension becomes unusable.
614    // This can happen during hot reload if a class changes base type in an incompatible way (e.g. RefCounted -> Node).
615    if registration_failed {
616        godot_error!(
617            "Failed to register class `{class_name}`; check preceding Godot stderr messages."
618        );
619    }
620
621    // ...then custom symbols
622
623    //let mut class_builder = crate::builder::ClassBuilder::<?>::new();
624    let mut class_builder = 0; // TODO dummy argument; see callbacks
625
626    // Order of the following registrations is crucial:
627    // 1. Methods and constants.
628    // 2. Properties (they may depend on get/set methods).
629    // 3. User-defined registration function (intuitively, user expects their own code to run after proc-macro generated code).
630    if let Some(register_fn) = info.register_methods_constants_fn {
631        (register_fn.raw)(&mut class_builder);
632    }
633
634    if let Some(register_fn) = info.register_properties_fn {
635        (register_fn.raw)(&mut class_builder);
636    }
637
638    if let Some(register_fn) = info.user_register_fn {
639        (register_fn.raw)(&mut class_builder);
640    }
641}
642
643fn validate_class_constraints(_class: &ClassRegistrationInfo) {
644    // TODO: if we add builder API, the proc-macro checks in parse_struct_attributes() etc. should be duplicated here.
645}
646
647fn unregister_class_raw(class: LoadedClass) {
648    let class_name = class.name;
649    out!("Unregister class: {class_name}");
650
651    // If class is an editor plugin, unregister that first.
652    if class.is_editor_plugin {
653        unsafe {
654            interface_fn!(editor_remove_plugin)(class_name.string_sys());
655        }
656
657        out!("> Editor plugin removed");
658    }
659
660    #[allow(clippy::let_unit_value)]
661    let _: () = unsafe {
662        interface_fn!(classdb_unregister_extension_class)(
663            sys::get_library(),
664            class_name.string_sys(),
665        )
666    };
667
668    out!("Class {class_name} unloaded");
669}
670
671fn lock_or_panic<T>(global: &'static Global<T>, ctx: &str) -> GlobalGuard<'static, T> {
672    match global.try_lock() {
673        Ok(it) => it,
674        Err(err) => match err {
675            GlobalLockError::Poisoned { .. } => panic!(
676                "global lock for {ctx} poisoned; class registration or deregistration may have panicked"
677            ),
678            GlobalLockError::WouldBlock => panic!("unexpected concurrent access to global lock for {ctx}"),
679            GlobalLockError::InitFailed => unreachable!("global lock for {ctx} not initialized"),
680        },
681    }
682}
683
684// ----------------------------------------------------------------------------------------------------------------------------------------------
685// Substitutes for Default impl
686
687// Yes, bindgen can implement Default, but only for _all_ types (with single exceptions).
688// For FFI types, it's better to have explicit initialization in the general case though.
689fn default_registration_info(class_name: ClassName) -> ClassRegistrationInfo {
690    ClassRegistrationInfo {
691        class_name,
692        parent_class_name: None,
693        register_methods_constants_fn: None,
694        register_properties_fn: None,
695        user_register_fn: None,
696        default_virtual_fn: None,
697        user_virtual_fn: None,
698        godot_params: default_creation_info(),
699        init_level: InitLevel::Scene,
700        is_editor_plugin: false,
701        dynify_fns_by_trait: HashMap::new(),
702        component_already_filled: Default::default(), // [false; N]
703    }
704}
705
706#[cfg(before_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.2")))]
707fn default_creation_info() -> sys::GDExtensionClassCreationInfo {
708    sys::GDExtensionClassCreationInfo {
709        is_virtual: false as u8,
710        is_abstract: false as u8,
711        set_func: None,
712        get_func: None,
713        get_property_list_func: None,
714        free_property_list_func: None,
715        property_can_revert_func: None,
716        property_get_revert_func: None,
717        notification_func: None,
718        to_string_func: None,
719        reference_func: None,
720        unreference_func: None,
721        create_instance_func: None,
722        free_instance_func: None,
723        get_virtual_func: None,
724        get_rid_func: None,
725        class_userdata: ptr::null_mut(),
726    }
727}
728
729#[cfg(all(since_api = "4.2", before_api = "4.3"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.2", before_api = "4.3"))))]
730fn default_creation_info() -> sys::GDExtensionClassCreationInfo2 {
731    sys::GDExtensionClassCreationInfo2 {
732        is_virtual: false as u8,
733        is_abstract: false as u8,
734        is_exposed: sys::conv::SYS_TRUE,
735        set_func: None,
736        get_func: None,
737        get_property_list_func: None,
738        free_property_list_func: None,
739        property_can_revert_func: None,
740        property_get_revert_func: None,
741        validate_property_func: None,
742        notification_func: None,
743        to_string_func: None,
744        reference_func: None,
745        unreference_func: None,
746        create_instance_func: None,
747        free_instance_func: None,
748        recreate_instance_func: None,
749        get_virtual_func: None,
750        get_virtual_call_data_func: None,
751        call_virtual_with_data_func: None,
752        get_rid_func: None,
753        class_userdata: ptr::null_mut(),
754    }
755}
756
757#[cfg(all(since_api = "4.3", before_api = "4.4"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", before_api = "4.4"))))]
758fn default_creation_info() -> sys::GDExtensionClassCreationInfo3 {
759    sys::GDExtensionClassCreationInfo3 {
760        is_virtual: false as u8,
761        is_abstract: false as u8,
762        is_exposed: sys::conv::SYS_TRUE,
763        is_runtime: sys::conv::SYS_TRUE,
764        set_func: None,
765        get_func: None,
766        get_property_list_func: None,
767        free_property_list_func: None,
768        property_can_revert_func: None,
769        property_get_revert_func: None,
770        validate_property_func: None,
771        notification_func: None,
772        to_string_func: None,
773        reference_func: None,
774        unreference_func: None,
775        create_instance_func: None,
776        free_instance_func: None,
777        recreate_instance_func: None,
778        get_virtual_func: None,
779        get_virtual_call_data_func: None,
780        call_virtual_with_data_func: None,
781        get_rid_func: None,
782        class_userdata: ptr::null_mut(),
783    }
784}
785
786#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
787fn default_creation_info() -> sys::GDExtensionClassCreationInfo4 {
788    sys::GDExtensionClassCreationInfo4 {
789        is_virtual: false as u8,
790        is_abstract: false as u8,
791        is_exposed: sys::conv::SYS_TRUE,
792        is_runtime: sys::conv::SYS_TRUE,
793        icon_path: ptr::null(),
794        set_func: None,
795        get_func: None,
796        get_property_list_func: None,
797        free_property_list_func: None,
798        property_can_revert_func: None,
799        property_get_revert_func: None,
800        validate_property_func: None,
801        notification_func: None,
802        to_string_func: None,
803        reference_func: None,
804        unreference_func: None,
805        create_instance_func: None,
806        free_instance_func: None,
807        recreate_instance_func: None,
808        get_virtual_func: None,
809        get_virtual_call_data_func: None,
810        call_virtual_with_data_func: None,
811        class_userdata: ptr::null_mut(),
812    }
813}