1use 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
24fn 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>>, > = Global::default();
35
36 lock_or_panic(&LOADED_CLASSES_BY_INIT_LEVEL, "loaded classes")
37}
38
39fn 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
56pub struct LoadedClass {
62 name: ClassId,
63 is_editor_plugin: bool,
64 unregister_singleton_fn: Option<fn()>,
65}
66
67pub struct ClassMetadata {}
71
72#[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 register_methods_constants_fn: Option<ErasedRegisterFn>,
93 register_properties_fn: Option<ErasedRegisterFn>,
94 user_register_fn: Option<ErasedRegisterFn>,
95 default_virtual_fn: Option<GodotGetVirtual>, user_virtual_fn: Option<GodotGetVirtual>, register_singleton_fn: Option<fn()>,
98 unregister_singleton_fn: Option<fn()>,
99
100 godot_params: GodotCreationInfo,
102
103 #[allow(dead_code)] init_level: InitLevel,
105 is_editor_plugin: bool,
106
107 dynify_fns_by_trait: HashMap<any::TypeId, DynTraitImpl>,
109
110 component_already_filled: [bool; 4],
112}
113
114impl ClassRegistrationInfo {
115 fn validate_unique(&mut self, item: &PluginItem) {
116 let index = match item {
120 PluginItem::Struct { .. } => 0,
121 PluginItem::InherentImpl(_) => 1,
122 PluginItem::ITraitImpl { .. } => 2,
123
124 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#[expect(dead_code)] pub(crate) fn register_class<
143 T: cap::GodotDefault
144 + cap::ImplementsGodotVirtual
145 + cap::GodotToString
146 + cap::GodotNotification
147 + cap::GodotRegisterClass
148 + GodotClass,
149>() {
150 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(), ..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(), register_singleton_fn: None,
187 unregister_singleton_fn: None,
188 });
189}
190
191pub fn auto_register_classes(init_level: InitLevel) {
193 out!("Auto-register classes at level `{init_level:?}`...");
194
195 let mut map = HashMap::<ClassId, ClassRegistrationInfo>::new();
200
201 crate::private::iterate_plugins(|elem: &ClassPlugin| {
202 if elem.init_level != init_level {
204 return;
205 }
206
207 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 register_classes_and_dyn_traits(&mut map, init_level);
222
223 let mut editor_plugins: Vec<ClassId> = Vec::new();
231
232 let mut singletons: Vec<fn()> = Vec::new();
234
235 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 for (trait_type_id, mut dyn_trait_impl) in info.dynify_fns_by_trait.drain() {
285 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 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 loaded_classes_by_name.remove(&class.name);
316
317 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 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
334pub(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 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 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
371pub(crate) fn get_dyn_property_hint_string<T, D>() -> String
379where
380 T: GodotClass,
381 D: ?Sized + 'static,
382{
383 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 let relations_iter = relations.iter().filter_map(|implementor| {
409 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
427fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
429 c.validate_unique(&item);
430
431 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 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; #[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 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 }
555
556fn 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) => { }
562 }
563 Ok(())
564}
565
566fn register_class_raw(mut info: ClassRegistrationInfo) {
568 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 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 let registration_failed = unsafe {
586 #[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 let tag = interface_fn!(classdb_get_class_tag)(class_name.string_sys());
607 tag.is_null()
608 };
609
610 if registration_failed {
613 godot_error!(
614 "Failed to register class `{class_name}`; check preceding Godot stderr messages."
615 );
616 }
617
618 let mut class_builder = 0; 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 }
643
644fn unregister_class_raw(class: LoadedClass) {
645 let class_name = class.name;
646 out!("Unregister class: {class_name}");
647
648 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 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
687fn 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(), }
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}