Skip to content

assume_safe projection macro for convenient access to sub trees #980

Open
@chitoyuu

Description

@chitoyuu

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 TRefs 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureAdds functionality to the library

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions