godot-rust / gdext

Rust bindings for Godot 4
https://mastodon.gamedev.place/@GodotRust
Mozilla Public License 2.0
3.1k stars 197 forks source link

Allow custom receivers in virtual methods #359

Open lilizoey opened 1 year ago

lilizoey commented 1 year ago

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.

gg-yb commented 1 year ago

This could also mitigate #302 to an extent

Bromeon commented 1 year ago

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... 🤔

lilizoey commented 1 year ago

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.

gg-yb commented 1 year ago

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

Ogeon commented 1 year ago

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?
}
Bromeon commented 1 year ago

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.

LeaoLuciano commented 1 year ago

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

#[godot_api]
impl NodeVirtual for Gd<MyClass> {
  fn ready(self) {
    ...
  }
}
Bromeon commented 1 year ago

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

Interesting idea, but that would mean:

mhoff12358 commented 1 year ago

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.

andreymal commented 2 months ago

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