User singletons
It is important for you to understand the Singleton pattern to properly utilize this system.
The "Singleton pattern" is often referred to as an anti-pattern, because it violates several good practices for clean, modular code. However, it is also a tool that can be used to solve certain design problems. As such, it is used internally by Godot, and is available to godot-rust users as well.
Read more about criticisms here.
Custom singletons in Godot:
- are
Objecttypes - always run in the editor (implied
#[class(tool)]) - are always accessible to GDScript and GDExtension languages
- must be registered and unregistered in the
InitStage::Scenestep
Godot provides many built-in singletons in its API. You can find a full list here.
Table of contents
- Registering custom singleton
- Using a singleton with
on_main_loop_frame - Registering custom singleton without proc macro
- Singletons and the
SceneTree
Registering custom singleton
You can register given class as a Singleton with #[class(singleton)].
#![allow(unused)] fn main() { #[derive(GodotClass)] #[class(init, singleton)] struct MySingleton { // For `#[class(singleton)]`, the default base is Object, not RefCounted. base: Base<Object>, } // Can be accessed like any other singleton from the main thread. let val = MySingleton::singleton().bind().foo(); }
Now that your singleton is available (and once you've recompiled and reloaded), you should be able to access it from GDScript like so:
extends Node
func _ready() -> void:
MySingleton.foo()
Using a singleton with on_main_loop_frame
Since Godot4.5+ on_main_loop_frame can be used to invoke an user singleton:
fn global_delta() -> f64 {
let ticks = ProjectSettings::singleton()
.get("physics/common/physics_ticks_per_second")
.to::<i64>();
1.0 / (ticks as f64)
}
#[derive(GodotClass)]
#[class(init, singleton)]
struct MySingleton {
#[init(val = global_delta())]
delta: f64,
#[init(val = true)]
paused: bool,
#[init(val = Instant::now())]
time: Instant,
base: Base<Object>,
}
#[gdextension]
unsafe impl ExtensionLibrary for MyExtension {
fn on_main_loop_frame() {
if Engine::singleton().is_editor_hint() {
return;
}
MySingleton::singleton().bind_mut().frame();
}
}
impl MySingleton {
pub fn frame(&mut self) {
if self.paused {
return;
}
let elapsed = self.time.elapsed().as_secs_f64();
self.elapsed += elapsed;
if self.elapsed >= self.delta {
let time_scale = Engine::singleton().get_time_scale();
self.run_simulation(self.elapsed * time_scale);
self.elapsed = 0.0;
}
self.time = Instant::now();
}
}
Registering custom singleton without proc macro
Custom singleton can be registered through godot::classes::Engine.
Additionally, implementing UserSingleton allows accessing a registered singleton instance through singleton().
User singletons should be registered under their class name – otherwise some Godot components (for example GDScript before 4.4)
might have trouble handling them, and the editor might crash when using T::singleton().
There should be only one instance of a given singleton class in the engine, valid as long as the library is loaded. Therefore, user singletons are limited to classes with manual memory management (ones not inheriting from RefCounted).
#[derive(GodotClass)]
#[class(init, base = Object)]
struct MySingleton {}
// Provides blanket implementation allowing to use MySingleton::singleton().
// Ensures that `MySingleton` is a valid singleton
// (i.e., a non-refcounted GodotClass).
impl UserSingleton for MySingleton {}
struct MyExtension;
#[gdextension]
unsafe impl ExtensionLibrary for MyExtension {
fn on_stage_init(stage: InitStage) {
// Singleton should be registered before the MainLoop startup;
// otherwise it won't be recognized by the GDScriptParser.
if stage == InitStage::Scene {
let obj = MySingleton::new_alloc();
Engine::singleton()
.register_singleton(
&MySingleton::class_id().to_string_name(),
&obj
);
}
}
fn on_stage_deinit(stage: InitStage) {
if stage == InitStage::Scene {
let obj = MySingleton::singleton();
Engine::singleton()
.unregister_singleton(
&MySingleton::class_id().to_string_name()
);
obj.free();
}
}
}
Singletons and the SceneTree
Singletons cannot safely access the scene tree. At any given moment, they may exist without a scene tree being active.
While it is technically possible to access the tree through hacky methods, it is highly recommended to use a
custom EditorPlugin for this purpose. Creating an EditorPlugin allows for registering an "autoload singleton" which is a Node (or
derived) type and is automatically loaded into the SceneTree by Godot when the game starts.