Skip to content

Sync .tscn files with Rust code #1653

Description

@anup-the-magic

Edit bromeon: Rust tags for syntax highlighting

I love Godot (for a variety of reasons), but its reliance on non-typesafe scene files, and (similarly) runtime crashes caused by misconfiguration, is not one of them. I'd love to do away with this, and the easiest way to do this in my mind is to enshrine the .tscn files into my nice, typesafe, rust code.

Theoretical example that I have:

The Goal

I want this node hierarchy, where MyCustomType is a rust class, which needs a reference to Gd<Camera3D> and Gd<Sprite3d>:

MyCustomType
|- Camera3D
|- Sprite3D

The Options

The Bad

I could create a .tscn file, and then just load it everywhere (using OnEditor in the examples below to nab a reference automatically), but then any change of the root type of my tscn now needs a corresponding change in my rust code, otherwise the cast fails at runtime. Less than ideal.

The Ugly

I could generate the hierarchy manually...

// my_custom_type.rs
#[derive(GodotClass)]
#[class(base=Node3D)]
pub struct MyCustomType {
    camera: OnReady<Gd<Camera3D>>,
    sprite: OnReady<Gd<Sprite3D>>,
    base: Base<Node3D>,
}

#[godot_api]
impl INode3D for MyCustomType{
    fn init(base: Base<Node3D>) -> Self {
        Self {
            base,
            camera: OnReady::manual(),
            sprite: OnReady::manual(),
        }
    }

    fn ready(&mut self) -> () {
        let camera = Camera3D::new_alloc();
        self.camera.init(camera);

        let sprite = Sprite3D::new_alloc();
        self.sprite.init(sprite);

        let camera = self.camera.clone();
        let sprite = self.sprite.clone();

        {
            let mut base = self.base_mut();
            base.add_child(&camera);
            base.add_child(&sprite);
        }
    }
}

But this is a bit overly verbose, and if I forget to initialize sprite in ready? Well we're panicking again.

The alternative

(disclaimer, I am a rust novice, but I think something like this is theoretically doable?)

static Scene: Scene<MyCustomType> = scene!({
  Camera3D {
    transform: Transform { origin: Vector3 { x: 1, ..default() }, ..default() },
  }
  Sprite3D {
  }
});

#[derive(GodotClass)]
#[class(base=Node3D)]
pub struct MyCustomType {
    camera: InScene<Gd<Camera3D>>,
    sprite: InScene<Gd<Sprite3D>>,
    base: Base<Node3D>,
}

impl INode3D for MyCustomType {
    fn init(base: Base<Node3D>) -> Self {
        Self {
            base,
            camera: Scene.Camera3D,
            sprite: Scene.Sprite3D,
        }
    }
}

Notably, behind the scenes (heh), we'd have godot-rust run the machinery to add the children to the tree, with the specified overrides (here, a different position). Hell, if it's performant, we could even emit a tscn file and load behind the scenes, but I think that gets a bit wonky and is definitely out of scope.

InScene would work similarly to OnReady, except the initialization in init would take in the path in the scene hierarchy, and every further call would be already resolved and realized?

Closing remarks

This is notably incredibly handwavy, and doesn't take into account a few key problems (scene tree modification, nodes with different names, up casting, base types + specialized scenes), but I figured this was enough of a formed idea rolling around in my head to at least start the discussion here.

Would love to keep spitballing ideas / help with implementation as needed

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: toolingCI, automation, toolsfeatureAdds functionality to the library

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions