Description
While looking at #977 I realized that it can be cumbersome to access a fixed scene tree with many moving parts. The situation is similar with what happens with Pin
in low level async code. There, the answer is projection as seen in the pin_project
crate.
Similarly, we can implement a macro that allows a similar pattern, with projection being the default for convenience:
#[gdnative::project]
struct SubNodes {
foo: Ref<Node>,
bar: Ref<Node>,
}
fn do_thing_with_sub_nodes(sub_nodes: &SubNodes) {
let sub_nodes = unsafe { sub_nodes.assume_safe() };
let foo: TRef<Node> = sub_nodes.foo;
let bar: TRef<Node> = sub_nodes.bar;
// ...
}
All the TRef
s generated will have the same lifetime, but this should rarely be an issue for the intended use case of static sub-trees.
Later, this can also be used as a building block for something like #758 (comment)
#[gdnative::fetch] // Unsure about the naming
#[gdnative::project]
struct SubNodes {
#[fetch(path = "foo")]
foo: Ref<Node>,
#[fetch(path = "foo/bar")]
bar: Ref<Node>,
}
fn do_thing_with_root_node(node: TRef<Node>) {
let sub_nodes = SubNodes.fetch(node).expect("all nodes are there");
let sub_nodes = unsafe { sub_nodes.assume_safe() };
let foo: TRef<Node> = sub_nodes.foo; // ${node.get_path()}/foo
let bar: TRef<Node> = sub_nodes.bar; // ${node.get_path()}/foo/bar
// ...
}
I believe this to be a better approach than putting get_node
attributes directly in the NativeClass
for the flexibility it provides: the API in this proposal allows access to known sub trees with non-Rust roots, while the other one does not. This might not sound hugely important, but considering the use cases of:
- Fetching autoloads (GDScript singletons) from the absolute root node.
- Using in a modding interface where the sub-trees come from third parties.
- Re-using the same structures and composing them for different types, for lack of inheritance.
I think it's pretty obvious that separate types work better.