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}
65
66pub struct ClassMetadata {}
70
71#[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 register_methods_constants_fn: Option<ErasedRegisterFn>,
92 register_properties_fn: Option<ErasedRegisterFn>,
93 user_register_fn: Option<ErasedRegisterFn>,
94 default_virtual_fn: Option<GodotGetVirtual>, user_virtual_fn: Option<GodotGetVirtual>, godot_params: GodotCreationInfo,
99
100 #[allow(dead_code)] init_level: InitLevel,
102 is_editor_plugin: bool,
103
104 dynify_fns_by_trait: HashMap<any::TypeId, DynTraitImpl>,
106
107 component_already_filled: [bool; 4],
109}
110
111impl ClassRegistrationInfo {
112 fn validate_unique(&mut self, item: &PluginItem) {
113 let index = match item {
117 PluginItem::Struct { .. } => 0,
118 PluginItem::InherentImpl(_) => 1,
119 PluginItem::ITraitImpl { .. } => 2,
120
121 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#[expect(dead_code)] pub(crate) fn register_class<
140 T: cap::GodotDefault
141 + cap::ImplementsGodotVirtual
142 + cap::GodotToString
143 + cap::GodotNotification
144 + cap::GodotRegisterClass
145 + GodotClass,
146>() {
147 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(), ..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(), });
184}
185
186pub fn auto_register_classes(init_level: InitLevel) {
188 out!("Auto-register classes at level `{init_level:?}`...");
189
190 let mut map = HashMap::<ClassId, ClassRegistrationInfo>::new();
195
196 crate::private::iterate_plugins(|elem: &ClassPlugin| {
197 if elem.init_level != init_level {
199 return;
200 }
201
202 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 register_classes_and_dyn_traits(&mut map, init_level);
217
218 let mut editor_plugins: Vec<ClassId> = Vec::new();
221
222 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 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 for (trait_type_id, mut dyn_trait_impl) in info.dynify_fns_by_trait.drain() {
267 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 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 loaded_classes_by_name.remove(&class.name);
298
299 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 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
316pub(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 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 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
353pub(crate) fn get_dyn_property_hint_string<T, D>() -> String
361where
362 T: GodotClass,
363 D: ?Sized + 'static,
364{
365 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 let relations_iter = relations.iter().filter_map(|implementor| {
391 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
409fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
411 c.validate_unique(&item);
412
413 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 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; #[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 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 }
533
534fn 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) => { }
540 }
541 Ok(())
542}
543
544fn register_class_raw(mut info: ClassRegistrationInfo) {
546 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 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 let registration_failed = unsafe {
564 #[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 let tag = interface_fn!(classdb_get_class_tag)(class_name.string_sys());
585 tag.is_null()
586 };
587
588 if registration_failed {
591 godot_error!(
592 "Failed to register class `{class_name}`; check preceding Godot stderr messages."
593 );
594 }
595
596 let mut class_builder = 0; 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 }
621
622fn unregister_class_raw(class: LoadedClass) {
623 let class_name = class.name;
624 out!("Unregister class: {class_name}");
625
626 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
659fn 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(), }
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}