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