gdext icon indicating copy to clipboard operation
gdext copied to clipboard

Allow custom receivers in virtual methods

Open lilizoey opened this issue 1 year ago • 10 comments

Currently most virtual methods take &mut self as the receiver, this can be problematic if you want to call multiple virtual methods at once from godot. We could loosen this restriction.

One way to do this would be to make all virtual methods take Gd<Self> instead as the first argument, and have the #[godot_api] macro rewrite the function slightly to take the appropriate reference.

This would also allow you to workaround #338. Especially if we allow the user to specify Gd<Self> as the receiver.

lilizoey avatar Jul 24 '23 13:07 lilizoey

This could also mitigate #302 to an extent

gg-yb avatar Jul 24 '23 14:07 gg-yb

One way to do this would be to make all virtual methods take Gd<Self> instead as the first argument, and have the #[godot_api] macro rewrite the function slightly to take the appropriate reference.

It can be argued that since bad runtime borrows are a common source of confusion, having an explicit

fn ready(this: Gd<Self>) {
   let this = this.bind_mut();
   ...
}

would be clearer. But I don't fully like this, as it makes code less ergonomic and punishes all the common cases like ready() and process(), which are usually perfectly fine.

I was also considering that &self or &mut self receivers could still be valid, but would be desugared by the proc-macro to this.bind() and this.bind_mut(). Problem is that it's not possible to use the self modifier, so even the function body would need to be rewritten, which would likely be very involved with weird edge cases... 🤔

Bromeon avatar Jul 24 '23 15:07 Bromeon

I was also considering that &self or &mut self receivers could still be valid, but would be desugared by the proc-macro to this.bind() and this.bind_mut(). Problem is that it's not possible to use the self modifier, so even the function body would need to be rewritten, which would likely be very involved with weird edge cases... 🤔

We could just have it desugar to something like

impl NodeVirtual for MyClass {
  fn ready(this: Gd<Self>) {
    Self::ready(this.bind())
  }
}

impl MyClass {
  fn ready(&self) {
    // code
  }
}

Idk the exact syntax to make the name resolution work properly here but yeah.

lilizoey avatar Jul 24 '23 16:07 lilizoey

Regarding #338, I'm not sure if this would allow you to workaround the situation described therein.

fn ready(this: Gd<Self>) {
  this.bind_mut().add_child(...); // Mutable borrow of Gd<Self>
}

fn on_notification(this: Gd<Self>, n: ...) {
  // do something with this, either bind / bind_mut -> panic due to aliasing
}

on_notification would still be required to not touch this, as far as I can see

gg-yb avatar Jul 25 '23 04:07 gg-yb

Shouldn't it work if this is upcast to Node first? It's not pretty or obvious, though...

fn ready(this: Gd<Self>) {
  this.upcast::<Node>().add_child(...); // Uses DerefMut, since Node is an engine class
}

fn on_notification(this: Gd<Self>, n: ...) {
  // do something with this, either bind / bind_mut -> no panic?
}

Ogeon avatar Jul 25 '23 11:07 Ogeon

Shouldn't it work if this is upcast to Node first? It's not pretty or obvious, though...

Great observation! In fact, that's one of the main motivations for https://github.com/godot-rust/gdext/issues/131. I'll probably implement that soon.

Bromeon avatar Jul 26 '23 19:07 Bromeon

Maybe the user can optionally choose to impl Gd<MyClass> instead of impl MyClass:

#[godot_api]
impl NodeVirtual for Gd<MyClass> {
  fn ready(self) {
    ...
  }
}

LeaoLuciano avatar Aug 12 '23 16:08 LeaoLuciano

Maybe the user can optionally choose to impl Gd<MyClass> instead of impl MyClass:

Interesting idea, but that would mean:

  • all functions need to take the same receiver type, instead of deciding it per-function
    • unless we allow multiple #[godot_api] blocks, but that comes with its own issues
  • since the trait itself has methods defined on &self/&mut self (not the value self), this would need to be done here too, which is a bit weird 🤔

Bromeon avatar Aug 13 '23 07:08 Bromeon

It doesn't address the case of existing interfaces, but I was able to throw together https://github.com/godot-rust/gdext/pull/396 to allow user defined methods to take this: Gd<Self> instead of &self as the first argument to a non-static method.

mhoff12358 avatar Sep 04 '23 03:09 mhoff12358

Just ran into this issue when I tried to test #883

#[derive(GodotClass)]
#[class(editor_plugin, tool, init, base=EditorPlugin)]
pub struct MySuperEditorPlugin {}

#[godot_api]
impl IEditorPlugin for MySuperEditorPlugin {
    fn enter_tree(&mut self) {
        let mut editor_button = Button::new_alloc();
        editor_button.connect(
            "pressed".into(),
            Callable::from_object_method(
                self, // ???????
                "editor_button_pressed_event",
            ),
        );
    }
}

So I have to make an (unnecessary?) "inner" object just to handle signals

andreymal avatar Sep 03 '24 13:09 andreymal