@@ -61,6 +61,7 @@ fn global_dyn_traits_by_typeid() -> GlobalGuard<'static, HashMap<any::TypeId, Ve
6161pub struct LoadedClass {
6262 name : ClassId ,
6363 is_editor_plugin : bool ,
64+ unregister_singleton_fn : Option < fn ( ) > ,
6465}
6566
6667/// Represents a class which is currently loaded and retained in memory -- including metadata.
@@ -93,6 +94,8 @@ struct ClassRegistrationInfo {
9394 user_register_fn : Option < ErasedRegisterFn > ,
9495 default_virtual_fn : Option < GodotGetVirtual > , // Optional (set if there is at least one OnReady field)
9596 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 ( ) > ,
9699
97100 /// Godot low-level class creation parameters.
98101 godot_params : GodotCreationInfo ,
@@ -180,6 +183,8 @@ pub(crate) fn register_class<
180183 is_editor_plugin : false ,
181184 dynify_fns_by_trait : HashMap :: new ( ) ,
182185 component_already_filled : Default :: default ( ) , // [false; N]
186+ register_singleton_fn : None ,
187+ unregister_singleton_fn : None ,
183188 } ) ;
184189}
185190
@@ -215,10 +220,15 @@ pub fn auto_register_classes(init_level: InitLevel) {
215220 // but it is much slower and doesn't guarantee that all the dependent classes will be already loaded in most cases.
216221 register_classes_and_dyn_traits ( & mut map, init_level) ;
217222
223+ // Workaround for Godot before 4.4.1:
218224 // Editor plugins should be added to the editor AFTER all the classes has been registered.
219225 // Adding EditorPlugin to the Editor before registering all the classes it depends on might result in crash.
220226 let mut editor_plugins: Vec < ClassId > = Vec :: new ( ) ;
221227
228+ // User Singletons must be initialized only after all the other classes
229+ // (on which they might depend on in form of properties and whatnot).
230+ let mut singletons: Vec < fn ( ) > = Vec :: new ( ) ;
231+
222232 // Actually register all the classes.
223233 for info in map. into_values ( ) {
224234 #[ cfg( feature = "debug-log" ) ]
@@ -228,14 +238,22 @@ pub fn auto_register_classes(init_level: InitLevel) {
228238 editor_plugins. push ( info. class_name ) ;
229239 }
230240
241+ if let Some ( register_singleton_fn) = info. register_singleton_fn {
242+ singletons. push ( register_singleton_fn)
243+ }
244+
231245 register_class_raw ( info) ;
232246
233247 out ! ( "Class {class_name} loaded." ) ;
234248 }
235249
236- // Will imminently add given class to the editor.
250+ for register_singleton_fn in singletons {
251+ register_singleton_fn ( )
252+ }
253+
254+ // Will imminently add given class to the editor in Godot before 4.4.1.
237255 // 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.
256+ // but might lead to some issues during hot reload.
239257 // See also: (https://github.com/godot-rust/gdext/issues/1132)
240258 for editor_plugin_class_name in editor_plugins {
241259 unsafe { interface_fn ! ( editor_add_plugin) ( editor_plugin_class_name. string_sys ( ) ) } ;
@@ -259,6 +277,7 @@ fn register_classes_and_dyn_traits(
259277 let loaded_class = LoadedClass {
260278 name : class_name,
261279 is_editor_plugin : info. is_editor_plugin ,
280+ unregister_singleton_fn : info. unregister_singleton_fn ,
262281 } ;
263282 let metadata = ClassMetadata { } ;
264283
@@ -420,6 +439,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
420439 register_properties_fn,
421440 free_fn,
422441 default_get_virtual_fn,
442+ unregister_singleton_fn,
443+ register_singleton_fn,
423444 is_tool,
424445 is_editor_plugin,
425446 is_internal,
@@ -431,6 +452,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
431452 c. default_virtual_fn = default_get_virtual_fn;
432453 c. register_properties_fn = Some ( register_properties_fn) ;
433454 c. is_editor_plugin = is_editor_plugin;
455+ c. register_singleton_fn = register_singleton_fn;
456+ c. unregister_singleton_fn = unregister_singleton_fn;
434457
435458 // Classes marked #[class(no_init)] are translated to "abstract" in Godot. This disables their default constructor.
436459 // "Abstract" is a misnomer -- it's not an abstract base class, but rather a "utility/static class" (although it can have instance
@@ -632,6 +655,12 @@ fn unregister_class_raw(class: LoadedClass) {
632655 out ! ( "> Editor plugin removed" ) ;
633656 }
634657
658+ // Similarly to EditorPlugin – given instance is being freed and will not be recreated
659+ // during hot reload (a new, independent one will be created instead).
660+ if let Some ( unregister_singleton_fn) = class. unregister_singleton_fn {
661+ unregister_singleton_fn ( ) ;
662+ }
663+
635664 #[ allow( clippy:: let_unit_value) ]
636665 let _: ( ) = unsafe {
637666 interface_fn ! ( classdb_unregister_extension_class) (
@@ -670,6 +699,8 @@ fn default_registration_info(class_name: ClassId) -> ClassRegistrationInfo {
670699 user_register_fn : None ,
671700 default_virtual_fn : None ,
672701 user_virtual_fn : None ,
702+ register_singleton_fn : None ,
703+ unregister_singleton_fn : None ,
673704 godot_params : default_creation_info ( ) ,
674705 init_level : InitLevel :: Scene ,
675706 is_editor_plugin : false ,
0 commit comments