Skip to content

Commit 4a4a8ce

Browse files
authored
Merge pull request #807 from Houtamelo/onready_attribute
Add `OnReady::node()` + `#[init(node = "...")]` attribute
2 parents e1cbb68 + 82ef4ad commit 4a4a8ce

File tree

4 files changed

+198
-41
lines changed

4 files changed

+198
-41
lines changed

godot-core/src/obj/onready.rs

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +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;
810
use crate::meta::GodotConvert;
11+
use crate::obj::{Gd, GodotClass, Inherits};
912
use crate::registry::property::{PropertyHintInfo, Var};
1013
use std::mem;
1114

@@ -17,9 +20,10 @@ use std::mem;
1720
///
1821
/// `OnReady<T>` should always be used as a field. There are two modes to use it:
1922
///
20-
/// 1. **Automatic mode, using [`new()`](Self::new).**<br>
21-
/// Before `ready()` is called, all `OnReady` fields constructed with `new()` are automatically initialized, in the order of
22-
/// declaration. This means that you can safely access them in `ready()`.<br><br>
23+
/// 1. **Automatic mode, using [`new()`](OnReady::new), [`from_base_fn()`](OnReady::from_base_fn) or
24+
/// [`node()`](OnReady::<Gd<T>>::node).**<br>
25+
/// Before `ready()` is called, all `OnReady` fields constructed with the above methods are automatically initialized,
26+
/// in the order of declaration. This means that you can safely access them in `ready()`.<br><br>
2327
/// 2. **Manual mode, using [`manual()`](Self::manual).**<br>
2428
/// These fields are left uninitialized until you call [`init()`][Self::init] on them. This is useful if you need more complex
2529
/// initialization scenarios than a closure allows. If you forget initialization, a panic will occur on first access.
@@ -36,21 +40,27 @@ use std::mem;
3640
/// [option]: std::option::Option
3741
/// [lazy]: https://docs.rs/once_cell/1/once_cell/unsync/struct.Lazy.html
3842
///
39-
/// # Example
43+
/// # Requirements
44+
/// - The class must have an explicit `Base` field (i.e. `base: Base<Node>`).
45+
/// - The class must inherit `Node` (otherwise `ready()` would not exist anyway).
46+
///
47+
/// # Example - user-defined `init`
4048
/// ```
4149
/// use godot::prelude::*;
4250
///
4351
/// #[derive(GodotClass)]
4452
/// #[class(base = Node)]
4553
/// struct MyClass {
54+
/// base: Base<Node>,
4655
/// auto: OnReady<i32>,
4756
/// manual: OnReady<i32>,
4857
/// }
4958
///
5059
/// #[godot_api]
5160
/// impl INode for MyClass {
52-
/// fn init(_base: Base<Node>) -> Self {
61+
/// fn init(base: Base<Node>) -> Self {
5362
/// Self {
63+
/// base,
5464
/// auto: OnReady::new(|| 11),
5565
/// manual: OnReady::manual(),
5666
/// }
@@ -65,10 +75,54 @@ use std::mem;
6575
/// assert_eq!(*self.manual, 22);
6676
/// }
6777
/// }
78+
/// ```
79+
///
80+
/// # Example - macro-generated `init`
81+
/// ```
82+
/// use godot::prelude::*;
83+
///
84+
/// #[derive(GodotClass)]
85+
/// #[class(init, base = Node)]
86+
/// struct MyClass {
87+
/// base: Base<Node>,
88+
/// #[init(node = "ChildPath")]
89+
/// auto: OnReady<Gd<Node2D>>,
90+
/// #[init(default = OnReady::manual())]
91+
/// manual: OnReady<i32>,
92+
/// }
93+
///
94+
/// #[godot_api]
95+
/// impl INode for MyClass {
96+
/// fn ready(&mut self) {
97+
/// // self.node is now ready with the node found at path `ChildPath`.
98+
/// assert_eq!(self.auto.get_name(), "ChildPath".into());
99+
///
100+
/// // self.manual needs to be initialized manually.
101+
/// self.manual.init(22);
102+
/// assert_eq!(*self.manual, 22);
103+
/// }
104+
/// }
105+
/// ```
68106
pub struct OnReady<T> {
69107
state: InitState<T>,
70108
}
71109

110+
impl<T: GodotClass + Inherits<Node>> OnReady<Gd<T>> {
111+
/// Variant of [`OnReady::new()`], fetching the node located at `path` before `ready()`.
112+
///
113+
/// This is the functional equivalent of the GDScript pattern `@onready var node = $NodePath`.
114+
///
115+
/// # Panics
116+
/// - If `path` does not point to a valid node.
117+
///
118+
/// Note that the panic will only happen if and when the node enters the SceneTree for the first time
119+
/// (i.e.: it receives the `READY` notification).
120+
pub fn node(path: impl Into<NodePath>) -> Self {
121+
let path = path.into();
122+
Self::from_base_fn(|base| base.get_node_as(path))
123+
}
124+
}
125+
72126
impl<T> OnReady<T> {
73127
/// Schedule automatic initialization before `ready()`.
74128
///
@@ -82,6 +136,14 @@ impl<T> OnReady<T> {
82136
pub fn new<F>(init_fn: F) -> Self
83137
where
84138
F: FnOnce() -> T + 'static,
139+
{
140+
Self::from_base_fn(|_| init_fn())
141+
}
142+
143+
/// Variant of [`OnReady::new()`], allowing access to `Base` when initializing.
144+
pub fn from_base_fn<F>(init_fn: F) -> Self
145+
where
146+
F: FnOnce(&Gd<Node>) -> T + 'static,
85147
{
86148
Self {
87149
state: InitState::AutoPrepared {
@@ -126,7 +188,7 @@ impl<T> OnReady<T> {
126188
///
127189
/// # Panics
128190
/// If the value is already initialized.
129-
pub(crate) fn init_auto(&mut self) {
191+
pub(crate) fn init_auto(&mut self, base: &Gd<Node>) {
130192
// Two branches needed, because mem::replace() could accidentally overwrite an already initialized value.
131193
match &self.state {
132194
InitState::ManualUninitialized => return, // skipped
@@ -147,7 +209,7 @@ impl<T> OnReady<T> {
147209
};
148210

149211
self.state = InitState::Initialized {
150-
value: initializer(),
212+
value: initializer(base),
151213
};
152214
}
153215
}
@@ -214,9 +276,11 @@ impl<T: Var> Var for OnReady<T> {
214276
// ----------------------------------------------------------------------------------------------------------------------------------------------
215277
// Implementation
216278

279+
type InitFn<T> = dyn FnOnce(&Gd<Node>) -> T;
280+
217281
enum InitState<T> {
218282
ManualUninitialized,
219-
AutoPrepared { initializer: Box<dyn FnOnce() -> T> },
283+
AutoPrepared { initializer: Box<InitFn<T>> },
220284
AutoInitializing, // needed because state cannot be empty
221285
Initialized { value: T },
222286
}

godot-core/src/private.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ pub struct ClassConfig {
146146
// ----------------------------------------------------------------------------------------------------------------------------------------------
147147
// Capability queries and internal access
148148

149-
pub fn auto_init<T>(l: &mut crate::obj::OnReady<T>) {
150-
l.init_auto();
149+
pub fn auto_init<T>(l: &mut crate::obj::OnReady<T>, base: &crate::obj::Gd<crate::classes::Node>) {
150+
l.init_auto(base);
151151
}
152152

153153
#[cfg(since_api = "4.3")]

godot-macros/src/class/derive_godot_class.rs

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
6363
}
6464
}
6565
} else {
66-
quote! {}
66+
TokenStream::new()
6767
};
6868

6969
let (user_class_impl, has_default_virtual) =
@@ -229,15 +229,29 @@ fn make_user_class_impl(
229229
is_tool: bool,
230230
all_fields: &[Field],
231231
) -> (TokenStream, bool) {
232-
let onready_field_inits = all_fields
233-
.iter()
234-
.filter(|&field| field.is_onready)
235-
.map(|field| {
236-
let field = &field.name;
232+
let onready_inits = {
233+
let mut onready_fields = all_fields
234+
.iter()
235+
.filter(|&field| field.is_onready)
236+
.map(|field| {
237+
let field = &field.name;
238+
quote! {
239+
::godot::private::auto_init(&mut self.#field, &base);
240+
}
241+
});
242+
243+
if let Some(first) = onready_fields.next() {
237244
quote! {
238-
::godot::private::auto_init(&mut self.#field);
245+
{
246+
let base = <Self as godot::obj::WithBaseField>::to_gd(self).upcast();
247+
#first
248+
#( #onready_fields )*
249+
}
239250
}
240-
});
251+
} else {
252+
TokenStream::new()
253+
}
254+
};
241255

242256
let default_virtual_fn = if all_fields.iter().any(|field| field.is_onready) {
243257
let tool_check = util::make_virtual_tool_check();
@@ -270,7 +284,7 @@ fn make_user_class_impl(
270284
}
271285

272286
fn __before_ready(&mut self) {
273-
#( #onready_field_inits )*
287+
#onready_inits
274288
}
275289

276290
#default_virtual_fn
@@ -403,8 +417,37 @@ fn parse_fields(
403417
}
404418

405419
// #[init(default = expr)]
406-
let default = parser.handle_expr("default")?;
407-
field.default = default;
420+
if let Some(default) = parser.handle_expr("default")? {
421+
field.default = Some(default);
422+
}
423+
424+
// #[init(node = "NodePath")]
425+
if let Some(node_path) = parser.handle_expr("node")? {
426+
if !field.is_onready {
427+
return bail!(
428+
parser.span(),
429+
"The key `node` in attribute #[init] requires field of type `OnReady<T>`\n\
430+
Help: The syntax #[init(node = \"NodePath\")] is equivalent to \
431+
#[init(default = OnReady::node(\"NodePath\"))], \
432+
which can only be assigned to fields of type `OnReady<T>`"
433+
);
434+
}
435+
436+
if field.default.is_some() {
437+
return bail!(
438+
parser.span(),
439+
"The key `node` in attribute #[init] is mutually exclusive with the key `default`\n\
440+
Help: The syntax #[init(node = \"NodePath\")] is equivalent to \
441+
#[init(default = OnReady::node(\"NodePath\"))], \
442+
both aren't allowed since they would override each other"
443+
);
444+
}
445+
446+
field.default = Some(quote! {
447+
OnReady::node(#node_path)
448+
});
449+
}
450+
408451
parser.finish()?;
409452
}
410453

0 commit comments

Comments
 (0)