-
- Notifications
You must be signed in to change notification settings - Fork 688
Description
Godot version
4.1
godot-cpp version
4.1
System information
Windows 10 64 bits NVIDIA GeForce GTX 1060
Issue description
RenderingServer::get_singleton returns null in cases where it should not.
-
If you have code in your extension library initializer that tries to access
RenderingServer, it will crash because that singleton will benull, even in initialization levels that should definitely work in modules (likeMODULE_INITIALIZATION_LEVEL_SCENE). -
You can't even check that
get_singletonreturnsnullptr, because the getter itself is doing something that crashes when the singleton is null, in release builds:godot-cpp/binding_generator.py
Line 1882 in c8c25cd
result.append("#ifdef DEBUG_ENABLED") -
Similarly, if you have code in a class constructor (node, resource...) that tries to access
RenderingServer, it will also crash the editor on startup, becauseEditorHelpcreates temporary instances of every class inClassDBto access the default values of their properties (mentionned in There should be a way to register classes without exposing them (usually editor plugin internals) #1179). -
In fact, if you dare calling
RenderingServer::get_singleton()and it returnsnull, it will keep returning null forever, because in GodotCpp the wrapper caches the value once in astaticvariable and never runs again:godot-cpp/binding_generator.py
Line 1458 in 749b0b9
"\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton(_gde_class_name._native_ptr());"
This is also the case with other servers such as PhysicsServer.
In all the mentionned cases, the server singleton actually exists.
Apparently the cause during initialization is that singletons in GDExtension are accessed similarly to scripts, which is terrible because that API is initialized way too late and completely disregards MODULE_INITIALIZATION_ levels.
This is very confusing and impractical. GDExtension needs the same access API modules have.
I notably work on a big module project, which can also compile as a GDExtension, so finding workarounds gets more complicated.
Steps to reproduce
Access RenderingServer::get_singleton() inside your library initializer, in MODULE_INITIALIZATION_LEVEL_SCENE, or access it inside the constructor of a custom class.
Minimal reproduction project
N.A
Alternative (theory)
This came up in a meeting: in modules, Godot is currently mixing up registration (declaring that X is there, but not executing anything) and initialization in modules. And by extension... extensions too 😁 In an ideal world, these two steps should be very separate.
In my module, I don't actually need to initialize singletons in MODULE_INITIALIZATION_LEVEL_SCENE, I just do it there because in the present state of things, I can do it there. But in GDExtension there is nowhere I can do it which won't cause the present issue.
Though regardless of this idea, there would still be a need to be able to order each step, because registration can depend on other things (base classes?), and initialization can also depend on others (accessing a core singleton when initializing a custom singleton).
Workarounds
- Autoload: if this situations happens due to initializing a singleton, you could make it an autoload node. The downside is that users have to set it up.
- Lazy-init: when some logic runs at a time you know the server is available (like
_readyin a custom node), add a lazy-init checking a global boolean. If false, set it to true and do the initialization. If that initialization is heavy and the code runs a bit late in the app's cycle, it might cause a noticeable hitch. - Use
callable_mp_static(&my_setup_function).call_deferred();from[MODULE_INITIALIZATION_*callback. There is no guarantee when that will actually run, but there is a good chance it will be after singletons have been made accessible.