pub struct OnReady<T> { /* private fields */ }
Expand description
Ergonomic late-initialization container with ready()
support.
While deferred initialization is generally seen as bad practice, it is often inevitable in game development.
Godot in particular encourages initialization inside ready()
, e.g. to access the scene tree after a node is inserted into it.
The alternative to using this pattern is Option<T>
, which needs to be explicitly unwrapped with unwrap()
or expect()
each time.
OnReady<T>
should always be used as a field. There are two modes to use it:
- Automatic mode, using
new()
.
Beforeready()
is called, allOnReady
fields constructed withnew()
are automatically initialized, in the order of declaration. This means that you can safely access them inready()
. - Manual mode, using
manual()
.
These fields are left uninitialized until you callinit()
on them. This is useful if you need more complex initialization scenarios than a closure allows. If you forget initialization, a panic will occur on first access.
Conceptually, OnReady<T>
is very close to once_cell’s Lazy<T>
, with additional hooks into the Godot lifecycle.
The absence of methods to check initialization state is deliberate: you don’t need them if you follow the above two patterns.
This container is not designed as a general late-initialization solution, but tailored to the ready()
semantics of Godot.
OnReady<T>
cannot be used with #[export]
fields, because ready()
is typically not called in the editor (unless #[class(tool)]
is specified). You can however use it with #[var]
– just make sure to access the fields in GDScript after ready()
.
This type is not thread-safe. ready()
runs on the main thread, and you are expected to access its value on the main thread, as well.
§Example
use godot::prelude::*;
#[derive(GodotClass)]
#[class(base = Node)]
struct MyClass {
auto: OnReady<i32>,
manual: OnReady<i32>,
}
#[godot_api]
impl INode for MyClass {
fn init(_base: Base<Node>) -> Self {
Self {
auto: OnReady::new(|| 11),
manual: OnReady::manual(),
}
}
fn ready(&mut self) {
// self.auto is now ready with value 11.
assert_eq!(*self.auto, 11);
// self.manual needs to be initialized manually.
self.manual.init(22);
assert_eq!(*self.manual, 22);
}
}
Implementations§
§impl<T> OnReady<T>
impl<T> OnReady<T>
pub fn new<F>(init_fn: F) -> OnReady<T>where
F: FnOnce() -> T + 'static,
pub fn new<F>(init_fn: F) -> OnReady<T>where
F: FnOnce() -> T + 'static,
Schedule automatic initialization before ready()
.
This guarantees that the value is initialized once ready()
starts running.
Until then, accessing the object may panic. In particular, the object is not initialized on first use.
The value is also initialized when you don’t override ready()
.
For more control over initialization, use the OnReady::manual()
constructor, followed by a self.init()
call during ready()
.
pub fn manual() -> OnReady<T>
pub fn manual() -> OnReady<T>
Leave uninitialized, expects manual initialization during ready()
.
If you use this method, you must call init()
during the ready()
callback, otherwise a panic will occur.
pub fn init(&mut self, value: T)
pub fn init(&mut self, value: T)
Runs manual initialization.
§Panics
- If
init()
was called before. - If this object was already provided with a closure during construction, in
Self::new()
.
Trait Implementations§
§impl<T> GodotConvert for OnReady<T>where
T: GodotConvert,
impl<T> GodotConvert for OnReady<T>where
T: GodotConvert,
§type Via = <T as GodotConvert>::Via
type Via = <T as GodotConvert>::Via
Self
is represented in Godot.