1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
use crate::meta::GodotConvert;
use crate::registry::property::{PropertyHintInfo, Var};
use std::mem;
/// 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>`][option], 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:
///
/// 1. **Automatic mode, using [`new()`](Self::new).**<br>
/// Before `ready()` is called, all `OnReady` fields constructed with `new()` are automatically initialized, in the order of
/// declaration. This means that you can safely access them in `ready()`.<br><br>
/// 2. **Manual mode, using [`manual()`](Self::manual).**<br>
/// These fields are left uninitialized until you call [`init()`][Self::init] 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>`][lazy], 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.
///
/// [option]: std::option::Option
/// [lazy]: https://docs.rs/once_cell/1/once_cell/unsync/struct.Lazy.html
///
/// # 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);
/// }
/// }
pub struct OnReady<T> {
state: InitState<T>,
}
impl<T> OnReady<T> {
/// 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()`][OnReady::init]
/// call during `ready()`.
pub fn new<F>(init_fn: F) -> Self
where
F: FnOnce() -> T + 'static,
{
Self {
state: InitState::AutoPrepared {
initializer: Box::new(init_fn),
},
}
}
/// Leave uninitialized, expects manual initialization during `ready()`.
///
/// If you use this method, you _must_ call [`init()`][Self::init] during the `ready()` callback, otherwise a panic will occur.
pub fn manual() -> Self {
Self {
state: InitState::ManualUninitialized,
}
}
/// Runs manual initialization.
///
/// # Panics
/// - If `init()` was called before.
/// - If this object was already provided with a closure during construction, in [`Self::new()`].
pub fn init(&mut self, value: T) {
match &self.state {
InitState::ManualUninitialized { .. } => {
self.state = InitState::Initialized { value };
}
InitState::AutoPrepared { .. } => {
panic!("cannot call init() on auto-initialized OnReady objects")
}
InitState::AutoInitializing => {
// SAFETY: Loading is ephemeral state that is only set in init_auto() and immediately overwritten.
unsafe { std::hint::unreachable_unchecked() }
}
InitState::Initialized { .. } => {
panic!("already initialized; did you call init() more than once?")
}
};
}
/// Runs initialization.
///
/// # Panics
/// If the value is already initialized.
pub(crate) fn init_auto(&mut self) {
// Two branches needed, because mem::replace() could accidentally overwrite an already initialized value.
match &self.state {
InitState::ManualUninitialized => return, // skipped
InitState::AutoPrepared { .. } => {} // handled below
InitState::AutoInitializing => {
// SAFETY: Loading is ephemeral state that is only set below and immediately overwritten.
unsafe { std::hint::unreachable_unchecked() }
}
InitState::Initialized { .. } => panic!("OnReady object already initialized"),
};
// Temporarily replace with dummy state, as it's not possible to take ownership of the initializer closure otherwise.
let InitState::AutoPrepared { initializer } =
mem::replace(&mut self.state, InitState::AutoInitializing)
else {
// SAFETY: condition checked above.
unsafe { std::hint::unreachable_unchecked() }
};
self.state = InitState::Initialized {
value: initializer(),
};
}
}
// Panicking Deref is not best practice according to Rust, but constant get() calls are significantly less ergonomic and make it harder to
// migrate between T and LateInit<T>, because all the accesses need to change.
impl<T> std::ops::Deref for OnReady<T> {
type Target = T;
/// Returns a shared reference to the value.
///
/// # Panics
/// If the value is not yet initialized.
fn deref(&self) -> &Self::Target {
match &self.state {
InitState::ManualUninitialized => {
panic!("OnReady manual value uninitialized, did you call init()?")
}
InitState::AutoPrepared { .. } => {
panic!("OnReady automatic value uninitialized, is only available in ready()")
}
InitState::AutoInitializing => unreachable!(),
InitState::Initialized { value } => value,
}
}
}
impl<T> std::ops::DerefMut for OnReady<T> {
/// Returns an exclusive reference to the value.
///
/// # Panics
/// If the value is not yet initialized.
fn deref_mut(&mut self) -> &mut Self::Target {
match &mut self.state {
InitState::Initialized { value } => value,
InitState::ManualUninitialized { .. } | InitState::AutoPrepared { .. } => {
panic!("value not yet initialized")
}
InitState::AutoInitializing => unreachable!(),
}
}
}
impl<T: GodotConvert> GodotConvert for OnReady<T> {
type Via = T::Via;
}
impl<T: Var> Var for OnReady<T> {
fn get_property(&self) -> Self::Via {
let deref: &T = self;
deref.get_property()
}
fn set_property(&mut self, value: Self::Via) {
let deref: &mut T = self;
deref.set_property(value);
}
fn property_hint() -> PropertyHintInfo {
T::property_hint()
}
}
// ----------------------------------------------------------------------------------------------------------------------------------------------
// Implementation
enum InitState<T> {
ManualUninitialized,
AutoPrepared { initializer: Box<dyn FnOnce() -> T> },
AutoInitializing, // needed because state cannot be empty
Initialized { value: T },
}