-
-
Notifications
You must be signed in to change notification settings - Fork 218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add OnReady::node()
+ #[init(node = "...")]
attribute
#807
Conversation
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-807 |
Thanks a lot! 👍 Is there a way to use this without proc macros, e.g. Also, do we really need the proc macros? I'd like to keep the proc-macro DSL limited, because there's a "magic budget" we shouldn't use carelessly. Especially the Does having an Could you keep this separate from the changes in #795? Otherwise we need to review that code twice... |
The only way I can think of is by using a pattern you already shared previously: pub fn onready_node<O, T>(
this: &Base<O>,
path: impl Into<NodePath> + 'static,
) -> OnReady<Gd<T>>
where
T: Inherits<Node>,
O: Inherits<Node>,
{
let self_obj = this.to_gd();
OnReady::new(move || self_obj.upcast().get_node_as(path))
} Which would have to be invoked in a manual init, defeating the purpose of the feature.
Yes, otherwise we can't get a reference to the class's node. This could be worked around by putting this functionality in a different type (e.g: OnReadyNode)
Yeah, I'll make sure the next commit is rebased in My main argument in favor of this feature is that fetching nodes this way is much more ergonomic than using #[export] and having every field be Option<Gd>. The $NodePath syntax is very popular in GdScript, having some way of mimicking it would increase the ergonomics of the library by a substantial margin. |
Why does it defeat the purpose? We can consider a proc-macro API, but it should happen on top of a regular Regarding my suggestion on Discord, what's the problem with it? It could be an associated function (constructor) of impl<T> OnReady<Gd<T>>
where T: Inherits<Node>
{
pub fn node(path: impl Into<NodePath> + 'static) -> OnReady<Gd<T>> {
let path = path.into();
OnReady::new(move |node| node.get_node_as(path))
}
} That would also mean that the proc-macro no longer needs to have hardcoded magic. Instead, we could extend the #[derive(GodotClass)]
#[class(init, base = Node)]
struct MyClass {
base: Base<Node>,
#[init(node = "child")]
auto_node: OnReady<Gd<Node>>,
} Which would be equivalent to a user-defined #[godot_api]
impl INode for MyClass {
fn init(base: Base<Node>) -> Self {
Self {
base,
auto_node: OnReady::node("child"),
}
}
} The reason why I prefer It's also easier to understand for the user what the attribute does behind the scenes, and the above example might even be part of the documentation. What do you think? I would wait with the |
impl<T> OnReady<Gd<T>>
where T: Inherits<Node>
{
pub fn node(path: impl Into<NodePath> + 'static) -> OnReady<Gd<T>> {
let path = path.into();
OnReady::new(move |node| node.get_node_as(path))
}
} I totally misunderstood what you meant, in fact, this commit already has the method you mentioned above, it also has an additional one impl<T: GodotClass + Inherits<Node>> OnReady<Gd<T>> {
pub fn node(path: impl Into<NodePath>) -> Self {
let path = path.into();
Self {
state: InitState::AutoPrepared {
initializer: Box::new(move |base| base.get_node_as(path)),
},
}
}
}
impl<T> OnReady<T> {
pub fn with_base<F>(init_fn: F) -> Self
where
F: FnOnce(&Gd<Node>) -> T + 'static,
{
Self {
state: InitState::AutoPrepared {
initializer: Box::new(init_fn),
},
}
}
}
#[derive(GodotClass)]
#[class(init, base = Node)]
struct MyClass {
base: Base<Node>,
#[init(node = "child")]
auto_node: OnReady<Gd<Node>>,
}
#[godot_api]
impl INode for MyClass {
fn init(base: Base<Node>) -> Self {
Self {
base,
auto_node: OnReady::node("child"),
}
}
}
I agree, #[init(node = "NodePath")] seems like the right way to express this, while still keeping the intended ergonomics.
I don't think it's necessary, I added it because it was simple enough. Personally I've never used @onready for anything but node-fetching. Also, if the user wants to access base in #[init], they can instead: #[init( default = OnReady::with_base(|b| b.get_name()) )]
node_name: OnReady<GString>, It's slight more verbose, but reasonable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good! Then we could converge on a #[init(node = "path")]
syntax that would internally call OnReady::node("path")
in the init
constructor.
Regarding with_base
, maybe rename it to from_base_fn
? Then it's more consistent with Callable::from_fn
or Gd::from_init_fn
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot! Here are the synced docs of your PR 🙂
https://godot-rust.github.io/docs/gdext/pr-807/godot/obj/struct.OnReady.html
Could you reduce the number of commits as per guidelines?
There are also some CI jobs currently failing. You could use check.sh
which helps with local development, see book.
godot-core/src/obj/onready.rs
Outdated
impl<T: GodotClass + Inherits<Node>> OnReady<Option<Gd<T>>> { | ||
/// Variant of [OnReady::new], except this is hard-coded to fetch the node at `path` before ready. | ||
/// | ||
/// This is the functional equivalent of the GdScript pattern: `@onready var node = $NodePath` | ||
/// | ||
/// # Does not panic | ||
/// If path does not point to a valid node, this will simply leave the field as `None`. | ||
/// | ||
/// If you're certain `path` is valid, and you're willing to panic if it isn't, | ||
/// consider changing the field type to `OnReady<Gd<T>>`, then use [OnReady<Gd<T>>::node] instead. | ||
pub fn try_node(path: impl Into<NodePath>) -> Self { | ||
let path = path.into(); | ||
Self::from_base_fn(|base| base.try_get_node_as(path)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, did you encounter a use cases where this was helpful?
Wondering because OnReady
is generally used to have ergonomic access to late-initialized fields, and Option
requires again explicit .unwrap()
. In GDScript, @onready var node = $NodePath
followed by null
checks is not a typical pattern, or is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Houtamelo what are your thoughts on this? 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When writing that function, I had thought about a few use cases but after your question I re-considered it and realized the use cases were all redundant.
OnReady::node()
+ #[init(node = "...")]
attribute
- 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>`)
@Bromeon Should be all good now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, thanks! Just the question about OnReady<Option<...>>
is still there, but I can already merge the current version anyway 🙂
Merging currently blocked by upstream regression godotengine/godot#94755, PR is already on the way... |
This feature aims to mimic GDScript's
@onready
annotation in a more ergonomic way.It still uses the type
OnReady<T>
, but the initialization closure now accepts an argument of typeGd<Node>
, which makes it easier to express the "classic" GDScript pattern of fetching a node reference before_ready
(var node = $NodePath
).This is achieved by adding the
#[onready]
attribute to the pool of possible ways the user can customize their Rust "class"'s fields.The attribute supports the following arguments, only one must be provided at a time:
node
= "NodePath"fn
= |base| -> T { .. }Example
Restrictions
base
field.#[init(default)]
Base<T>
, it's alwaysGd<Node>
Caveats
Since the attribute essentially de-sugars into
#[init(default)]
, it only works with the macro-generatedinit
, the#[onready]
attribute does nothing if the user manually creates the class (by overridingINode::init
, or callingGd::with_init_fn
, etc).