Skip to content

Commit 82ef4ad

Browse files
committed
Implements the node key for attribute #[init]
- Meant for fields of type: OnReady<Gd<T>> where T: Inherits<Node> - This key accepts a single argument with the syntax: "NodePath" - This key is mutually exclusive with the key `default` - This key desugars into `default = OnReady::node("NodePath")` This feature required adding a `Gd<Node>` argument to the closure that initializes OnReady, which means that classes that use it must have an explicit `Base` field. (i.e. `base: Base<Node>`)
1 parent 79edae3 commit 82ef4ad

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
@@ -114,8 +114,8 @@ pub struct ClassConfig {
114114
// ----------------------------------------------------------------------------------------------------------------------------------------------
115115
// Capability queries and internal access
116116

117-
pub fn auto_init<T>(l: &mut crate::obj::OnReady<T>) {
118-
l.init_auto();
117+
pub fn auto_init<T>(l: &mut crate::obj::OnReady<T>, base: &crate::obj::Gd<crate::classes::Node>) {
118+
l.init_auto(base);
119119
}
120120

121121
#[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
@@ -55,7 +55,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
5555
}
5656
}
5757
} else {
58-
quote! {}
58+
TokenStream::new()
5959
};
6060

6161
let deprecated_base_warning = if fields.has_deprecated_base {
@@ -226,15 +226,29 @@ fn make_user_class_impl(
226226
is_tool: bool,
227227
all_fields: &[Field],
228228
) -> (TokenStream, bool) {
229-
let onready_field_inits = all_fields
230-
.iter()
231-
.filter(|&field| field.is_onready)
232-
.map(|field| {
233-
let field = &field.name;
229+
let onready_inits = {
230+
let mut onready_fields = all_fields
231+
.iter()
232+
.filter(|&field| field.is_onready)
233+
.map(|field| {
234+
let field = &field.name;
235+
quote! {
236+
::godot::private::auto_init(&mut self.#field, &base);
237+
}
238+
});
239+
240+
if let Some(first) = onready_fields.next() {
234241
quote! {
235-
::godot::private::auto_init(&mut self.#field);
242+
{
243+
let base = <Self as godot::obj::WithBaseField>::to_gd(self).upcast();
244+
#first
245+
#( #onready_fields )*
246+
}
236247
}
237-
});
248+
} else {
249+
TokenStream::new()
250+
}
251+
};
238252

239253
let default_virtual_fn = if all_fields.iter().any(|field| field.is_onready) {
240254
let tool_check = util::make_virtual_tool_check();
@@ -267,7 +281,7 @@ fn make_user_class_impl(
267281
}
268282

269283
fn __before_ready(&mut self) {
270-
#( #onready_field_inits )*
284+
#onready_inits
271285
}
272286

273287
#default_virtual_fn
@@ -409,8 +423,37 @@ fn parse_fields(
409423
}
410424

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

0 commit comments

Comments
 (0)