Skip to content

Commit 280ddae

Browse files
authored
Merge pull request #1083 from godot-rust/qol/onready-ctors
Add `OnReady::from_loaded()` + `#[init(load = "PATH")]`
2 parents ab08cd2 + f5fca0b commit 280ddae

File tree

7 files changed

+257
-132
lines changed

7 files changed

+257
-132
lines changed

godot-core/src/obj/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ mod dyn_gd;
1616
mod gd;
1717
mod guards;
1818
mod instance_id;
19-
mod oneditor;
20-
mod onready;
19+
mod on_editor;
20+
mod on_ready;
2121
mod raw_gd;
2222
mod traits;
2323

@@ -28,8 +28,8 @@ pub use dyn_gd::DynGd;
2828
pub use gd::*;
2929
pub use guards::{BaseMut, BaseRef, DynGdMut, DynGdRef, GdMut, GdRef};
3030
pub use instance_id::*;
31-
pub use oneditor::*;
32-
pub use onready::*;
31+
pub use on_editor::*;
32+
pub use on_ready::*;
3333
pub use raw_gd::*;
3434
pub use traits::*;
3535

godot-core/src/obj/oneditor.rs godot-core/src/obj/on_editor.rs

+67-47
Original file line numberDiff line numberDiff line change
@@ -10,63 +10,81 @@ use crate::registry::property::{BuiltinExport, Export, Var};
1010

1111
/// Exported property that must be initialized in the editor (or associated code) before use.
1212
///
13-
/// Allows to use `Gd<T>`, which by itself never holds null objects, as an `#[export]` that should not be null during runtime.
14-
/// As such, it can be used as a more ergonomic version of `Option<Gd<T>>` which _assumes_ initialization.
13+
/// Use this type whenever your Rust code cannot provide a value for a field, but expects one to be specified in the Godot editor.
14+
///
15+
/// If you need automatic initialization during `ready()`, e.g. for loading nodes or resources, use [`OnReady<Gd<T>>`](crate::obj::OnReady)
16+
/// instead. As a general "maybe initialized" type, `Option<Gd<T>>` is always available, even if more verbose.
17+
///
18+
///
19+
/// # What consitutes "initialized"?
20+
///
21+
/// Whether a value is considered initialized or not depends on `T`.
22+
///
23+
/// - For objects, a value is initialized if it is not null. Exported object propreties in Godot are nullable, but `Gd<T>` and `DynGd<T, D>` do
24+
/// not support nullability and can thus not directly be exported with `#[export]`. `OnEditor` can bridge this gap, by expecting users
25+
/// to set a non-null value, and panicking if they don't.
26+
/// - For built-in types, a value is initialized if it is different from a user-selected sentinel value (e.g. `-1`).
27+
///
28+
/// More on this below (see also table-of-contents sidebar).
29+
///
30+
/// # Initialization semantics
31+
///
32+
/// Panics during access (`Deref/DerefMut` impls) if uninitialized.
1533
///
16-
/// Panics during access if uninitialized.
1734
/// When used inside a node class, `OnEditor` checks if a value has been set before `ready()` is run, and panics otherwise.
1835
/// This validation is performed for all `OnEditor` fields declared in a given `GodotClass`, regardless of whether they are `#[var]`, `#[export]`, or neither.
19-
/// Once initialized, it can be used almost as if it was a `T` value itself, due to `Deref`/`DerefMut` impls.
36+
/// Once initialized, `OnEditor` can be used almost as if it were a `T` value itself, due to `Deref`/`DerefMut` impls.
2037
///
21-
/// `OnEditor<T>` should always be used as a property, preferably in tandem with an `#[export]` or `#[var]`.
22-
/// Initializing `OnEditor` values via code before the first use is supported but should be limited to use cases involving builder or factory patterns.
38+
/// `OnEditor<T>` should always be used as a struct field, preferably in tandem with an `#[export]` or `#[var]`.
39+
/// Initializing `OnEditor` values via code before the first use is supported, but should be limited to use cases involving builder or factory patterns.
2340
///
24-
/// [`Option<Gd<T>>`](std::option) and [`OnReady<Gd<T>>`](crate::obj::onready::OnReady) should be used for any other late initialization logic.
2541
///
26-
/// # Using `OnEditor<T>` with `Gd<T>` and `DynGd<T, D>`
42+
/// # Using `OnEditor` with classes
2743
///
28-
/// ## Example - auto-generated init
44+
/// You can wrap class smart pointers `Gd<T>` and `DynGd<T, D>` inside `OnEditor`, to make them exportable.
45+
/// `Gd<T>` itself does not implement the `Export` trait.
2946
///
30-
/// ```
31-
/// use godot::prelude::*;
47+
/// ## Example: automatic init
3248
///
49+
/// This example uses the `Default` impl, which expects a non-null value to be provided.
50+
///
51+
/// ```
52+
/// # use godot::prelude::*;
3353
/// #[derive(GodotClass)]
3454
/// #[class(init, base = Node)]
35-
/// struct MyClass {
55+
/// struct ResourceHolder {
3656
/// #[export]
3757
/// editor_property: OnEditor<Gd<Resource>>,
3858
/// }
3959
///
4060
/// #[godot_api]
41-
/// impl INode for MyClass {
61+
/// impl INode for ResourceHolder {
4262
/// fn ready(&mut self) {
4363
/// // Will always be valid and **must** be set via editor.
44-
/// // Additional check is being run before ready()
64+
/// // Additional check is being run before ready(),
4565
/// // to ensure that given value can't be null.
4666
/// let some_variant = self.editor_property.get_meta("SomeName");
4767
/// }
4868
/// }
49-
///
5069
/// ```
5170
///
52-
/// ## Example - user-generated init
71+
/// ## Example: user-defined init
5372
///
54-
/// Uninitialized `OnEditor<Gd<T>>` and `OnEditor<DynGd<T, D>>` can be created with `OnEditor<...>::default()`.
73+
/// Uninitialized `OnEditor<Gd<T>>` and `OnEditor<DynGd<T, D>>` can be created with `OnEditor::default()`.
5574
///
5675
/// ```
57-
/// use godot::prelude::*;
58-
///
76+
/// # use godot::prelude::*;
5977
/// #[derive(GodotClass)]
6078
/// #[class(base = Node)]
61-
/// struct MyClass {
79+
/// struct NodeHolder {
6280
/// #[export]
6381
/// required_node: OnEditor<Gd<Node>>,
6482
///
6583
/// base: Base<Node>
6684
/// }
6785
///
6886
/// #[godot_api]
69-
/// impl INode for MyClass {
87+
/// impl INode for NodeHolder {
7088
/// fn init(base: Base<Node>) -> Self {
7189
/// Self {
7290
/// base,
@@ -76,14 +94,13 @@ use crate::registry::property::{BuiltinExport, Export, Var};
7694
/// }
7795
///```
7896
///
79-
/// ## Example - factory pattern
97+
/// ## Example: factory pattern
8098
///
8199
/// ```
82-
/// use godot::prelude::*;
83-
///
100+
/// # use godot::prelude::*;
84101
/// #[derive(GodotClass)]
85102
/// #[class(init, base = Node)]
86-
/// struct SomeClass {
103+
/// struct NodeHolder {
87104
/// #[export]
88105
/// required_node: OnEditor<Gd<Node>>,
89106
/// }
@@ -92,67 +109,70 @@ use crate::registry::property::{BuiltinExport, Export, Var};
92109
/// mut this: Gd<Node>,
93110
/// some_class_scene: Gd<PackedScene>,
94111
/// some_node: Gd<Node>,
95-
/// ) -> Gd<SomeClass> {
96-
/// let mut my_node = some_class_scene.instantiate_as::<SomeClass>();
112+
/// ) -> Gd<NodeHolder> {
113+
/// let mut my_node = some_class_scene.instantiate_as::<NodeHolder>();
97114
///
98-
/// // Would cause the panic:
115+
/// // Would cause a panic:
99116
/// // this.add_child(&my_node);
100117
///
101-
/// // Note: Remember that nodes are manually managed.
102-
/// // They will leak memory if not added to tree and/or pruned.
118+
/// // It's possible to initialize the value programmatically, although typically
119+
/// // it is set in the editor and stored in a .tscn file.
120+
/// // Note: nodes are manually managed and leak memory unless tree-attached or freed.
103121
/// my_node.bind_mut().required_node.init(some_node);
104122
///
105-
/// // Will not cause the panic.
123+
/// // Will not panic, since the node is initialized now.
106124
/// this.add_child(&my_node);
107125
///
108126
/// my_node
109127
/// }
110128
/// ```
111129
///
112-
/// # Using `OnEditor<T>` with other GodotTypes
130+
/// # Using `OnEditor` with built-in types
113131
///
114-
/// `OnEditor<T>` can be used with other built-ins to provide extra validation logic and making sure that given properties has been set.
115-
/// Example usage might be checking if entities has been granted properly generated id.
132+
/// `OnEditor<T>` can be used with any `#[export]`-enabled builtins, to provide domain-specific validation logic.
133+
/// An example might be to check whether a game entity has been granted a non-zero ID.
116134
///
117-
/// In such cases the value which will be deemed invalid **must** be specified with `#[init(sentinel = val)]`.
118-
/// Given `val` will be used to represent uninitialized `OnEditor<T>` in the Godot editor.
119-
/// Accessing uninitialized value will cause the panic.
135+
/// To detect whether a value has been set in the editor, `OnEditor<T>` uses a _sentinel value_. This is a special marker value for
136+
/// "uninitialized" and is selected by the user. For example, a sentinel value of `-1` or `0` might be used to represent an uninitialized `i32`.
120137
///
121-
/// ## Example - using `OnEditor` with primitives
138+
/// There is deliberately no `Default` implementation for `OnEditor` with builtins, as the sentinel is highly domain-specific.
139+
///
140+
/// ## Example
122141
///
123142
/// ```
124-
/// use godot::prelude::*;
143+
/// use godot::prelude::*;
125144
///
126145
/// #[derive(GodotClass)]
127146
/// #[class(init, base = Node)]
128-
/// struct SomeClassThatCanBeInstantiatedInCode {
147+
/// struct IntHolder {
129148
/// // Uninitialized value will be represented by `42` in the editor.
130149
/// // Will cause panic if not set via the editor or code before use.
131150
/// #[export]
132151
/// #[init(sentinel = 42)]
133152
/// some_primitive: OnEditor<i64>,
134153
/// }
135154
///
136-
/// fn create_and_add(mut this: Gd<Node>, val: i64) -> Gd<SomeClassThatCanBeInstantiatedInCode> {
137-
/// let mut my_node = SomeClassThatCanBeInstantiatedInCode::new_alloc();
155+
/// fn create_and_add(mut this: Gd<Node>, val: i64) -> Gd<IntHolder> {
156+
/// let mut my_node = IntHolder::new_alloc();
138157
///
139-
/// // Would cause the panic:
158+
/// // Would cause a panic:
140159
/// // this.add_child(&my_node);
141160
///
161+
/// // It's possible to initialize the value programmatically, although typically
162+
/// // it is set in the editor and stored in a .tscn file.
142163
/// my_node.bind_mut().some_primitive.init(val);
143164
///
144-
/// // Will not cause the panic.
165+
/// // Will not panic, since the node is initialized now.
145166
/// this.add_child(&my_node);
146167
///
147168
/// my_node
148169
/// }
149170
/// ```
150171
///
151-
/// # Using `OnEditor<T>` with `#[class(tool)]`
172+
/// # Using `OnEditor` with `#[class(tool)]`
152173
///
153174
/// When used with `#[class(tool)]`, the before-ready checks are omitted.
154-
/// Otherwise, `OnEditor<T>` behaves the same — accessing an uninitialized value
155-
/// will cause a panic.
175+
/// Otherwise, `OnEditor<T>` behaves the same — accessing an uninitialized value will cause a panic.
156176
pub struct OnEditor<T> {
157177
inner: OnEditorState<T>,
158178
}

godot-core/src/obj/onready.rs godot-core/src/obj/on_ready.rs

+51-14
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use crate::builtin::NodePath;
9-
use crate::classes::Node;
8+
use crate::builtin::{GString, NodePath};
9+
use crate::classes::{Node, Resource};
1010
use crate::meta::{arg_into_owned, AsArg, GodotConvert};
11-
use crate::obj::{Gd, GodotClass, Inherits};
11+
use crate::obj::{Gd, Inherits};
1212
use crate::registry::property::Var;
1313
use std::fmt::{self, Debug, Formatter};
1414
use std::mem;
@@ -19,12 +19,17 @@ use std::mem;
1919
/// Godot in particular encourages initialization inside `ready()`, e.g. to access the scene tree after a node is inserted into it.
2020
/// The alternative to using this pattern is [`Option<T>`][option], which needs to be explicitly unwrapped with `unwrap()` or `expect()` each time.
2121
///
22-
/// `OnReady<T>` should always be used as a field. There are two modes to use it:
22+
/// If you have a value that you expect to be initialized in the Godot editor, use [`OnEditor<T>`][crate::obj::OnEditor] instead.
23+
/// As a general "maybe initialized" type, `Option<Gd<T>>` is always available, even if more verbose.
2324
///
24-
/// 1. **Automatic mode, using [`new()`](OnReady::new), [`from_base_fn()`](OnReady::from_base_fn) or
25-
/// [`node()`](OnReady::<Gd<T>>::node).**<br>
25+
/// # Late-init semantics
26+
///
27+
/// `OnReady<T>` should always be used as a struct field. There are two modes to use it:
28+
///
29+
/// 1. **Automatic mode, using [`new()`](OnReady::new), [`from_base_fn()`](OnReady::from_base_fn),
30+
/// [`from_node()`][Self::from_node] or [`from_loaded()`][Self::from_loaded].**<br>
2631
/// Before `ready()` is called, all `OnReady` fields constructed with the above methods are automatically initialized,
27-
/// in the order of declaration. This means that you can safely access them in `ready()`.<br><br>
32+
/// in the order of declaration. This means that you can safely access them in `ready()`.<br>
2833
/// 2. **Manual mode, using [`manual()`](Self::manual).**<br>
2934
/// These fields are left uninitialized until you call [`init()`][Self::init] on them. This is useful if you need more complex
3035
/// initialization scenarios than a closure allows. If you forget initialization, a panic will occur on first access.
@@ -86,8 +91,10 @@ use std::mem;
8691
/// #[class(init, base = Node)]
8792
/// struct MyClass {
8893
/// base: Base<Node>,
94+
///
8995
/// #[init(node = "ChildPath")]
9096
/// auto: OnReady<Gd<Node2D>>,
97+
///
9198
/// #[init(val = OnReady::manual())]
9299
/// manual: OnReady<i32>,
93100
/// }
@@ -109,21 +116,51 @@ pub struct OnReady<T> {
109116
state: InitState<T>,
110117
}
111118

112-
impl<T: GodotClass + Inherits<Node>> OnReady<Gd<T>> {
119+
impl<T: Inherits<Node>> OnReady<Gd<T>> {
113120
/// Variant of [`OnReady::new()`], fetching the node located at `path` before `ready()`.
114121
///
115-
/// This is the functional equivalent of the GDScript pattern `@onready var node = $NodePath`.
122+
/// This is the functional equivalent of:
123+
/// - the GDScript pattern `@onready var node = $NODE_PATH`.
124+
/// - the Rust method [`Node::get_node_as()`].
116125
///
117-
/// # Panics
118-
/// - If `path` does not point to a valid node.
126+
/// When used with `#[class(init)]`, the field can be annotated with `#[init(node = "NODE_PATH")]` to call this constructor.
127+
///
128+
/// # Panics (deferred)
129+
/// - If `path` does not point to a valid node, or its type is not a `T` or a subclass.
119130
///
120131
/// Note that the panic will only happen if and when the node enters the SceneTree for the first time
121-
/// (i.e.: it receives the `READY` notification).
122-
pub fn node(path: impl AsArg<NodePath>) -> Self {
132+
/// (i.e. it receives the `READY` notification).
133+
pub fn from_node(path: impl AsArg<NodePath>) -> Self {
123134
arg_into_owned!(path);
124135

125136
Self::from_base_fn(move |base| base.get_node_as(&path))
126137
}
138+
139+
#[deprecated = "Renamed to `from_node`."]
140+
pub fn node(path: impl AsArg<NodePath>) -> Self {
141+
Self::from_node(path)
142+
}
143+
}
144+
145+
impl<T: Inherits<Resource>> OnReady<Gd<T>> {
146+
/// Variant of [`OnReady::new()`], loading the resource stored at `path` before `ready()`.
147+
///
148+
/// This is the functional equivalent of:
149+
/// - the GDScript pattern `@onready var res = load(...)`.
150+
/// - the Rust function [`tools::load()`][crate::tools::load].
151+
///
152+
/// When used with `#[class(init)]`, the field can be annotated with `#[init(load = "FILE_PATH")]` to call this constructor.
153+
///
154+
/// # Panics (deferred)
155+
/// - If the resource does not exist at `path`, cannot be loaded or is not compatible with type `T`.
156+
///
157+
/// Note that the panic will only happen if and when the node enters the SceneTree for the first time
158+
/// (i.e. it receives the `READY` notification).
159+
pub fn from_loaded(path: impl AsArg<GString>) -> Self {
160+
arg_into_owned!(path);
161+
162+
Self::new(move || crate::tools::load(&path))
163+
}
127164
}
128165

129166
impl<T> OnReady<T> {
@@ -168,7 +205,7 @@ impl<T> OnReady<T> {
168205
///
169206
/// # Panics
170207
/// - If `init()` was called before.
171-
/// - If this object was already provided with a closure during construction, in [`Self::new()`].
208+
/// - If this object was already provided with a closure during construction, in [`Self::new()`] or any other automatic constructor.
172209
pub fn init(&mut self, value: T) {
173210
match &self.state {
174211
InitState::ManualUninitialized { .. } => {

0 commit comments

Comments
 (0)