-
-
Notifications
You must be signed in to change notification settings - Fork 287
Provide easy way to register user singletons/autoloads #1060
Description
Continuation of: godot-rust/book#79 (comment)
Long story short, godot-rust could provide mechanism to simplify registering and using user-declared GDExtension singletons. Given mechanism must work both with editor/godot autloads and "pure" engine singletons; they must be retrievable via custom GameLoop as well
The user-facing API could look like that:
#[derive(GodotClass)]
#[class(init, base=Object)]
struct MyEditorSingleton {
base: Base<Object>,
}
#[godot_api]
impl MyEditorSingleton {
#[func]
fn foo(&mut self) {
godot_print!("foo");
}
}
#[gd_singleton] // macro that creates®isters this object with the library. Works only for objects!
#[gd_singleton(init_level = ... )] // macro that creates®isters this object with the library on given non-default init level
impl UserSingleton for MyEditorSingleton {} // blanket implementation provides everythingor, for autoloads:
#[derive(GodotClass)]
#[class(init, base=Node)]
struct MyEditorSingleton {
base: Base<Node>,
}
#[godot_api]
impl MyEditorSingleton {
#[func]
fn foo(&mut self) {
godot_print!("foo");
}
}
#[godot_api]
impl INode for MyEditorSingleton {
fn enter_tree(&mut self) {
self.register();
}
fn exit_tree(...) {
self.unregister();
}
}
}
// adding #[gd_singleton] would result in compile error
impl UserSingleton for MyEditorSingleton {} // blanket implementation provides everythingAfterwards one could use their singleton just like:
// Not very ergonomic, but magic auto-binding should be avoided
MyEditorSingleton::singleton().bind_mut()…UserSingleton trait could look like that:
UserSingleton trait
pub trait UserSingleton: GodotClass + Bounds<Declarer = bounds::DeclUser> + NewAlloc + Inherits<Object> + WithBaseField
{
fn singleton() -> Gd<Self> {
Engine::singleton()
.get_singleton(&Self::class_name().to_string_name())
.unwrap()
.cast::<Self>()
}
fn register(&self) {
Engine::singleton().register_singleton(&Self::class_name().to_string_name(), &self.to_gd());
}
fn unregister(&mut self) {
Engine::singleton().unregister_singleton(&Self::class_name().to_string_name());
}
}Possible issues:
- I had some crashes related to hot-reload while initializing singletons with the library in the past, testing is needed if this issue persist in G4.3 and 4.4
Namespace clashes – Godot 4.3 handles them nicely and even prevents creating new instances of given engine singleton. No further action is needed.

User Editor/Godot Autoload which is also registered as Engine Singleton will not be available via gdscript as an Engine Singleton (because it is not registered with the library itself, but later on during runtime) but only as an Autoload – thus avoiding the namespace clashes as well.
Additional notes:
Autoloads are global constants exposed to given script server as Variants and must exists in scene tree - in short, they must inherit nodes. They are persistent and not pruned in-between scene changes, unless user explicitly declare to do so. Rust GDExtension autoloads must be declared as scenes (scene with our node, duh) and then added as a godot autoload: https://docs.godotengine.org/en/latest/tutorials/scripting/singletons_autoload.html.
The Engine Singletons are almost the same thing – Godot keeps pointers to given singletons in hashmap<StringName, Object *>; engine singleton can be anything that inherits object (in godot context – just anything.).
In the past I did some digging and noted down implementation details & differences between Editor/Godot Autoload and Engine singleton: https://discord.com/channels/723850269347283004/1259547468379455601/1259664201022967828 (warning: might be a little incoherent)