Skip to content
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

Ergonomics of instantiating PackedScene #296

Closed
Brocktho opened this issue Jun 5, 2023 · 3 comments
Closed

Ergonomics of instantiating PackedScene #296

Brocktho opened this issue Jun 5, 2023 · 3 comments
Labels
c: engine Godot classes (nodes, resources, ...) quality-of-life No new functionality, but improves ergonomics/internals

Comments

@Brocktho
Copy link

Brocktho commented Jun 5, 2023

Problem:

The current ergonomics of PackedScene are really unwieldy to the typical user.
Take for example a basic custom element that needs extra initialization before being added to a scene.

#[derive(GodotClass)]
#[class(base=ColorRect)]
pub struct Tile {
   #[base]
   base: Base<ColorRect>,
}
#[godot_api]
impl Tile  {
     #[func]
   pub fn create(&mut self, position: Vector2, _color: Color) {
       self.base.set_position(position * 32.0, true);
   }
}

No problems yet. But now I want to call the create method directly after instantiating the tile and then finally add it to the parent/self:

  //...
     #[func]
   fn create_tile(&mut self, position: Vector2, color: Color) {
       let mut tile = self
           .tile
           .instantiate(GenEditState::GEN_EDIT_STATE_DISABLED)
           .expect("scene instantiated")
           .cast::<Tile>();
       let mut mut_tile = tile.bind_mut();
       mut_tile.create(position, color);
       self.add_child(
           tile.share().upcast(),
           false,
           InternalMode::INTERNAL_MODE_DISABLED,
       );
   }
//...

The main issue here, I have 3 steps to take before I can even get access to the create method I want. (Excluding the instantiate itself)
instantiate() -> unwrap/expect/whatever you prefer -> cast -> bind_mut() -> finally I can call my create method
3 steps aren't impossible, but there's also some "magic" sprinkled in of you know it or you don't that isn't present in GDScript's basic instantiate. (GenEditState::GEN_EDIT_STATE_DISABLED is the magic word, and is a mouthful)

Suggested Solution:

I brought this up on the discord and lIlIZ was really helpful and had a wonderful suggestion for an update to help ease usability.
Expose an instantiate_as() or try_instantiate_as()
It could look like:

impl Gd<crate::engine::PackedScene> {
   pub fn try_instantiate_as<T: GodotClass + Inherits<Node>>(
       &self,
       edit_state: Option<GenEditState>,
   ) -> Option<Gd<T>> {
       self.instantiate(edit_state.unwrap_or(GenEditState::GEN_EDIT_STATE_DISABLED))
           .map(|gd| gd.cast())
   }

   pub fn instantiate_as<T: GodotClass + Inherits<Node>>(
       &self,
       edit_state: Option<GenEditState>,
   ) -> Gd<T> {
       self.try_instantiate_as(edit_state).unwrap()
   }
}

With a usage like:

  //...
     #[func]
   fn create_tile(&mut self, position: Vector2, color: Color) {
       let mut tile = self.tile.instantiate_as::<Tile>(None);
       let mut mut_tile = tile.bind_mut();
       mut_tile.create(position, color);
       self.add_child(
           tile.share().upcast(),
           false,
           InternalMode::INTERNAL_MODE_DISABLED,
       );
   }
//...

This would lessen the api for a typical user and make adoption of godot-rust that much easier. I'm especially a fan of the Option. Being able to pass None makes it about as close as you can get to the native GDScript feel.

@Brocktho
Copy link
Author

Brocktho commented Jun 5, 2023

I should also mention, this would be very desirable to have a similar approach for arguments in things like add_child. Sane Defaults are so much more developer friendly than expecting new users to find the ancient runes they must recite for each function.

I understand the constraints of rust, but perhaps any "optional" arguments as Option could be good with default unwrap_or providing the most typical use case for a function. As a user that has only briefly dabbled in godot with GDScript, it feels like an entirely different beast coming to rust where you don't have the defaults GDScript was providing that I was previously oblivious to.

@Bromeon Bromeon added quality-of-life No new functionality, but improves ergonomics/internals c: engine Godot classes (nodes, resources, ...) labels Jun 5, 2023
@Bromeon
Copy link
Member

Bromeon commented Jun 5, 2023

Inherits<T> already has GodotClass as a supertrait, so that bound can be simplified.


3 steps aren't impossible, but there's also some "magic" sprinkled in of you know it or you don't that isn't present in GDScript's basic instantiate. (GenEditState::GEN_EDIT_STATE_DISABLED is the magic word, and is a mouthful)

On that topic, we are likely going to rename these enums, so it would just be GenEditState::Disabled. Unfortunately this has to be done in a largely manual fashion due to godotengine/godot-proposals#5624, which is not going to be addressed in Godot 4.


I should also mention, this would be very desirable to have a similar approach for arguments in things like add_child. Sane Defaults are so much more developer friendly than expecting new users to find the ancient runes they must recite for each function.

Sure! Default arguments are planned, but currently not the highest priority. There are still bugs in FFI (foreign function interface) we need to address as a result of the 4.0 -> 4.1 breaking GDExtension changes, and possibly more things to adjust very soon (e.g. godotengine/godot#77410).

@Brocktho
Copy link
Author

Brocktho commented Jun 8, 2023

Should have been answered by a PR #299

@Brocktho Brocktho closed this as completed Jun 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: engine Godot classes (nodes, resources, ...) quality-of-life No new functionality, but improves ergonomics/internals
Projects
None yet
Development

No branches or pull requests

2 participants