@@ -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,17 @@ 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
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.
223+ // Before Godot 4.4.1 editor plugins were being added to the editor imminently,
224+ // triggering their lifecycle methods – even before their dependencies (properties and whatnot) has been registered.
225+ // During hot-reload Godot changes all GDExtension class instances into their base classes.
226+ // These two behaviour combined were leading to crashes.
227+ // 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).
228+ // See also: (https://github.com/godot-rust/gdext/issues/1132)
220229 let mut editor_plugins: Vec < ClassId > = Vec :: new ( ) ;
221230
231+ // Similarly to EnginePlugins – freshly instantiated engine singleton might depend on some not-yet-registered classes.
232+ let mut singletons: Vec < fn ( ) > = Vec :: new ( ) ;
233+
222234 // Actually register all the classes.
223235 for info in map. into_values ( ) {
224236 #[ cfg( feature = "debug-log" ) ]
@@ -228,15 +240,19 @@ pub fn auto_register_classes(init_level: InitLevel) {
228240 editor_plugins. push ( info. class_name ) ;
229241 }
230242
243+ if let Some ( register_singleton_fn) = info. register_singleton_fn {
244+ singletons. push ( register_singleton_fn)
245+ }
246+
231247 register_class_raw ( info) ;
232248
233249 out ! ( "Class {class_name} loaded." ) ;
234250 }
235251
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)
252+ for register_singleton_fn in singletons {
253+ register_singleton_fn ( )
254+ }
255+
240256 for editor_plugin_class_name in editor_plugins {
241257 unsafe { interface_fn ! ( editor_add_plugin) ( editor_plugin_class_name. string_sys ( ) ) } ;
242258 }
@@ -259,6 +275,7 @@ fn register_classes_and_dyn_traits(
259275 let loaded_class = LoadedClass {
260276 name : class_name,
261277 is_editor_plugin : info. is_editor_plugin ,
278+ unregister_singleton_fn : info. unregister_singleton_fn ,
262279 } ;
263280 let metadata = ClassMetadata { } ;
264281
@@ -420,6 +437,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
420437 register_properties_fn,
421438 free_fn,
422439 default_get_virtual_fn,
440+ unregister_singleton_fn,
441+ register_singleton_fn,
423442 is_tool,
424443 is_editor_plugin,
425444 is_internal,
@@ -431,6 +450,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
431450 c. default_virtual_fn = default_get_virtual_fn;
432451 c. register_properties_fn = Some ( register_properties_fn) ;
433452 c. is_editor_plugin = is_editor_plugin;
453+ c. register_singleton_fn = register_singleton_fn;
454+ c. unregister_singleton_fn = unregister_singleton_fn;
434455
435456 // Classes marked #[class(no_init)] are translated to "abstract" in Godot. This disables their default constructor.
436457 // "Abstract" is a misnomer -- it's not an abstract base class, but rather a "utility/static class" (although it can have instance
@@ -632,6 +653,12 @@ fn unregister_class_raw(class: LoadedClass) {
632653 out ! ( "> Editor plugin removed" ) ;
633654 }
634655
656+ // Similarly to EditorPlugin – given instance is being freed and will not be recreated
657+ // during hot reload (a new, independent one will be created instead).
658+ if let Some ( unregister_singleton_fn) = class. unregister_singleton_fn {
659+ unregister_singleton_fn ( ) ;
660+ }
661+
635662 #[ allow( clippy:: let_unit_value) ]
636663 let _: ( ) = unsafe {
637664 interface_fn ! ( classdb_unregister_extension_class) (
@@ -670,6 +697,8 @@ fn default_registration_info(class_name: ClassId) -> ClassRegistrationInfo {
670697 user_register_fn : None ,
671698 default_virtual_fn : None ,
672699 user_virtual_fn : None ,
700+ register_singleton_fn : None ,
701+ unregister_singleton_fn : None ,
673702 godot_params : default_creation_info ( ) ,
674703 init_level : InitLevel :: Scene ,
675704 is_editor_plugin : false ,
0 commit comments