-
-
Notifications
You must be signed in to change notification settings - Fork 198
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
Add GdDyn<dyn Trait> type #631
Comments
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 trait Health {
fn hitpoints(&self) -> u16;
}
// Use polymorphically here
fn is_dead(entity: &dyn Health): bool {
entity.hitpoints() == 0
} 1) impl on
|
@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 Few remarks:
struct 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 In short what I need is:
|
Continuing with the above I think that
|
Unfortunately, RTTI with trait object ( EDIT: Let's say i got a
You see, |
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 Gd (object, resource, node, anything) to 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 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). |
Took far too long, but I finally wrapped up my proof-of-concept of |
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 require
Self: 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
The text was updated successfully, but these errors were encountered: