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

DynGd<T, D> smart pointer for Rust-side dynamic dispatch #953

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

Bromeon
Copy link
Member

@Bromeon Bromeon commented Nov 24, 2024

Closes #631.

This PR adds a type DynGd, which extends Gd with dynamic-dispatch functionality based on Rust traits.

Example

We have a Monster class which is registered in Godot, and which stores a hitpoints field.
There's also a Health trait with two methods.

#[derive(GodotClass)]
#[class(init)]
struct Monster {
    #[init(val = 100)]
    hitpoints: u16,
}

trait Health {
    fn is_alive(&self) -> bool;
    fn deal_damage(&mut self, damage: u16);
}

We can implement this trait the usual way 🙂
Note the attribute macro #[godot_dyn], which registers the relation with godot-rust. It doesn't modify the impl code.

#[godot_dyn]
impl Health for Monster {
    fn is_alive(&self) -> bool {
        self.hitpoints > 0
    }

    fn deal_damage(&mut self, damage: u16) {
        self.hitpoints = self.hitpoints.saturating_sub(damage);
    }
}

Now, we can create a Monster object inside Gd and convert it to DynGd<Monster, dyn Health>.

Afterwards, we abstract the Monster type away by upcasting it to its base class RefCounted. You can imagine that such a type DynGd<Monster, dyn Health> is capable of holding other ref-counted entities that have health.

let monster = Monster::new_gd();
let dyn_monster = monster.into_dyn::<dyn Health>();
// type: DynGd<Monster, dyn Health>

let mut dyn_monster = dyn_monster.upcast::<RefCounted>();
// type: DynGd<RefCounted, dyn Health>

Then, the trait can be accessed using dyn_bind().

assert!(dyn_monster.dyn_bind().is_alive());

// To mutate the object, call dyn_bind_mut(). Rust borrow rules apply.
let mut guard = dyn_monster.dyn_bind_mut();
guard.deal_damage(120);
assert!(!guard.is_alive());

Non-goals

This is purely a mechanism for Rust traits, to support Rust-side polymorphism and dynamic dispatch. When interacting with Godot, DynGd and Gd are equivalent. In particular, the trait methods aren't registered with Godot, and no extra class is registered. Other proposals (such as different approaches to inheritance) could probably address those needs in the future.

DynGd is also providing a relatively light facade on top of the existing Gd<T> machinery that doesn't reach deep into godot-rust core systems. Macro-wise, there's only #[godot_dyn] which avoids some boilerplate for an impl Trait, but there's no runtime registration of the trait (and as such also no introspection). AsDyn is the compile-time link between a class and a trait object.

Working state

This is a first draft with basic functionality, especially borrow guards and upcasting. If we merge this, I can add more functionality in follow-up PRs, such as:

  • Support as array elements
  • Integration with library traits (GodotType, ...)
  • Eq, Debug, Display, ...
  • Maybe DynGd constructors

No upcasting functionality yet, so limited usefulness until here.
…ef,Mut}

This is not fully working because there doesn't seem to be a generic trait that would allow conversions
 from T to dyn Trait. Type inference also plays against us with inferring D=T instead of D=dyn Trait.
# Conflicts:
#	godot-core/src/obj/traits.rs
Changes:
* Rename dbind() -> dyn_bind().
* DynGd::from_gd() should now be used as gd.into_dyn().
* Add test for Godot method call (through Deref/DerefMut).
@Bromeon Bromeon added feature Adds functionality to the library c: core Core components labels Nov 24, 2024
@Bromeon Bromeon added this to the 0.2.x milestone Nov 24, 2024
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-953

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: core Core components feature Adds functionality to the library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add GdDyn<dyn Trait> type
2 participants