Open MatrixDev opened 6 months ago
Don't know if exact duplicate, maybe just a different perspective on #426
Don't know if exact duplicate, maybe just a different perspective on #426
I saw that issue but it talks about abstract classes from the Godot's point of view. I'm asking about rust specific implementation. It is much more limited and should be easier to implement.
Let's say you have two types that you want to treat polymorphically:
#[derive(GodotClass)]
#[class(init)]
struct Monster {
hp: u16,
}
#[derive(GodotClass)]
#[class(init)]
struct Bullet {
is_alive: bool,
}
You can use a Health
trait to abstract over them, which could then be used as follows:
trait Health {
fn hitpoints(&self) -> u16;
}
// Use polymorphically here
fn is_dead(entity: &dyn Health): bool {
entity.hitpoints() == 0
}
Gd<T>
To achieve that, you can implement Health
directly for the Gd
pointers:
impl Health for Gd<Monster> {
fn hitpoints(&self) -> u16 {
self.bind().hp
}
}
impl Health for Gd<Bullet> {
fn hitpoints(&self) -> u16 {
if self.bind().is_alive { 1 } else { 0 }
}
}
fn test_health() {
let monster = Monster::new_gd();
let bullet = Bullet::new_gd();
let a = is_dead(&monster);
let b = is_dead(&bullet);
}
T
But you can also implement it on the types directly, moving the bind()
call to the use site:
impl Health for Monster {
fn hitpoints(&self) -> u16 {
self.hp
}
}
impl Health for Bullet {
fn hitpoints(&self) -> u16 {
if self.is_alive { 1 } else { 0 }
}
}
fn test_health() {
let monster = Monster::new_gd();
let bullet = Bullet::new_gd();
let a = is_dead(&*monster.bind());
let b = is_dead(&*bullet.bind());
}
Given both of the above are possible, it would be good to describe the problem we want to solve more clearly.
For example, what would GdDyn<dyn T>
solve that Box<T>
or Box<Gd<T>>
wouldn't? It's a bit more ergonomic and may avoid double indirection, but conceptually it doesn't unlock new features, does it?
Where would the link to Godot be? Even getting a common base could be abstracted via extra method from the trait 🤔
@Bromeon, sometimes I want to store objects based on their functionality, not concrete implementations.
Just as an example I have a platform (parent object) with multiple types of turrets. All turrets have a common functionality (for example shoot
). Now I want to iterate all turrets and shoot with each of those.
Few remarks:
#[func]
-friendly arguments#[func]
-friendly argumentsstruct World {
...
}
struct TurretStats {
...
}
// both function have non godot-friendly parameters/results
trait Turret {
fn get_stats_mut(&mut self) -> &mut TurretStats;
fn shoot(&mut self, world: &mut World);
}
#[derive(GodotClass)]
#[class(base = Node3D)]
struct TurretType1 {
}
impl Turret for TurretType1 {
...
}
#[derive(GodotClass)]
#[class(base = Node3D)]
struct TurretType2 {
}
impl Turret for TurretType2 {
...
}
#[derive(GodotClass)]
#[class(base = Node3D)]
struct Platform {
world: World,
turrets: Vec<Gd<...>>, // what do I put here?
}
I can have Vec<Gd<Node3D>>
but there is no way to get &dyn Trait
from Gd<Node3D>
.
I can have Vec<Box<dyn Trait>>
where impl Trait on Gd<T>
but I can't return references from it because bind
creates a temporary.
In short what I need is:
GdDyn<T>
GdDyn<dyn Trait>
where T: Trait
bind_mut
to receive impl DerefMut<Target=dyn Trait>
Continuing with the above I think that GdDyn<T>
should have following requirements / limitations:
Gd<T> where T: Bounds<Declarer=DeclUser>
Gd<T>
Manual
/ RefCounted
or what else it might need to support drop
, bind
, bind_mut
bind
and bind_mut
Gd<T>
(something similar toAny
)Gd<B> where T: Inherits<B>
Unfortunately, RTTI with trait object (donwcast(...) -> T where T: Trait
) is currently impossible (excluding mopa
and such). Even if there is a GdDyn<dyn Trait>
, it can't guarantee the pointed value does implement that trait. There is a nightly feature CoerceUnsized
that may allow for that, and builtin types (Box
/Rc
/Arc
) use it to ensure it's okay to do so (Arc<T> -> Arc<dyn Trait>
).
EDIT:
Oh you want cast from Gd<T>
to GdDyn<dyn Trait>
. I guess that's one way to guarantee that value implement trait. But my point still stands.
Let's say i got a Gd<Node>
for example, how is it going to be cast to GdDyn
? There are 2 possible route:
GdDyn
. I guess that works, but it's very unergonomic.GdDyn
with multiple dispatch 🤷♂️. I am not sure how it's done and i don't think it's the right approach.You see, Gd<T>
points to any type that is T
or it's decendants. So in a sense, T
is a trait, even though it's not obvious. The only exception is user-defined types, which can be bind()
to obtain non-DST borrow.
Since this issue lags on I'll share my code for handling dynamic dispatch in godot-rust context:
long story short; given trait:
pub trait MyTrait {
fn method_s(&mut self);
fn method_a(&mut self, arg: Arg);
fn method_b(&mut self, arg_b: ArgB, arg_c: ArgC);
}
one can create wrapper that allows to cast Gddyn MyTrait
. The boilerplate is:
type MyTraitGdDispatchSelf = fn(Gd<GType>, fn(&mut dyn MyTrait));
type MyTraitGdDispatchA = fn(Gd<GType>, Arg, fn(&mut dyn MyTrait, Arg));
type MyTraitGdDispatchB = fn(Gd<GType>, ArgB, ArgC, fn(&mut dyn MyTrait, ArgB, ArgC));
pub struct MyTraitGdDispatch {
dispatch_self: MyTraitGdDispatchSelf,
dispatch_a: MyTraitGdDispatchA,
dispatch_b: MyTraitGdDispatchB,
}
impl MyTraitGdDispatch {
fn new<T>() -> Self
where
T: Inherits<GType> + GodotClass + Bounds<Declarer = DeclUser> + MyTrait
{
Self {
dispatch_self: |base, closure| {
let mut instance = base.cast::<T>();
let mut guard: GdMut<T> = instance.bind_mut();
closure(&mut *guard)
},
dispatch_a: |base, arg, closure| {
let mut instance = base.cast::<T>();
let mut guard: GdMut<T> = instance.bind_mut();
closure(&mut *guard, arg)
},
dispatch_b: |base, arg_b, arg_c, closure| {
let mut instance = base.cast::<T>();
let mut guard: GdMut<T> = instance.bind_mut();
closure(&mut *guard, arg_b, arg_c)
},
}
}
}
static mut DISPATCH_REGISTRY: Option<HashMap<GString, MyTraitGdDispatch>> = None;
pub fn dispatch_registry() -> &'static HashMap<GString, MyTraitGdDispatch> {
unsafe {
if DISPATCH_REGISTRY.is_none() {
DISPATCH_REGISTRY = Some(HashMap::new());
}
DISPATCH_REGISTRY.as_ref().unwrap()
}
}
pub fn register_dispatch<T>(name: GString)
where
T: Inherits<GType> + GodotClass + Bounds<Declarer = DeclUser> + MyTrait
{
unsafe {
if DISPATCH_REGISTRY.is_none() {
DISPATCH_REGISTRY = Some(HashMap::new());
}
DISPATCH_REGISTRY.as_mut().unwrap().entry(name).or_insert_with(
|| MyTraitGdDispatch::new::<T>()
);
}
}
pub struct MyTraitGdDyn {
pub base: Gd<GType>,
dispatch: *const MyTraitCursedGdDispatch
}
impl MyTraitGdDyn {
pub fn new(base: Gd<GType>) -> Self {
unsafe {
let dispatch = &dispatch_registry()[&base.get_class()] as *const MyTraitGdDispatch;
Self {
base: base.clone(),
dispatch
}
}
}
}
impl MyTrait for MyTraitGdDyn {
fn method_s(&mut self) {
unsafe { ((*self.dispatch).dispatch_self)(self.base.clone(), |d: &mut dyn MyTrait| { d.method_s() }) }
}
fn method_a(&mut self, arg: Arg) {
unsafe { ((*self.dispatch).dispatch_a)(self.base.clone(), arg, |d: &mut dyn MyTrait, arg| { d.method_a(arg) }) }
}
fn method_b(&mut self, arg_b: ArgB, arg_c: ArgC) {
unsafe { ((*self.dispatch).dispatch_b)(self.base.clone(), arg, |d: &mut dyn MyTrait, arg| { d.method_b(arg_b, arg_c) }) }
}
}
Afterwards one just needs to register given dispatch using https://godot-rust.github.io/docs/gdext/master/godot/init/trait.ExtensionLibrary.html#method.on_level_init or in their main loop or even the instance's _init using something along the lines of register_dispatch<MyClass>(MyClass::class_name().to_gstring())
. Additionally ToGodot/FromGodot/GodotConvert can be implemented.
I can make proc-macro for that if anybody is interested, albeit registering given dispatch will still be an user responsibility.
In my project I'm using such abstraction mostly for various Command Objects (that can be passed to executor from my gdext library or gdscript).
Request
Currently it is impossible to make
GodotClass
object-safe because of the multiple Rust limitations. Maybe it is possible to create a lighter alternative that doesn't requireSelf: Sized
with limited functionality that allows to use trait-objects.Use-case:
I have multiple different objects that have a common trait. At this moment I don't see any rust-safe way to call anything common except
#[func]
(which only takes variants and also a big footgun for safety).Example