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