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

Allow custom receivers in virtual methods #359

Open
lilizoey opened this issue Jul 24, 2023 · 10 comments
Open

Allow custom receivers in virtual methods #359

lilizoey opened this issue Jul 24, 2023 · 10 comments
Labels
c: register Register classes, functions and other symbols to GDScript feature Adds functionality to the library

Comments

@lilizoey
Copy link
Member

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 lilizoey added feature Adds functionality to the library c: register Register classes, functions and other symbols to GDScript labels Jul 24, 2023
@gg-yb
Copy link
Contributor

gg-yb commented Jul 24, 2023

This could also mitigate #302 to an extent

@Bromeon
Copy link
Member

Bromeon commented Jul 24, 2023

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
Copy link
Member Author

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
Copy link
Contributor

gg-yb commented Jul 25, 2023

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
Copy link

Ogeon commented Jul 25, 2023

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
Copy link
Member

Bromeon commented Jul 26, 2023

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 #131. I'll probably implement that soon.

@LeaoLuciano
Copy link
Contributor

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
Copy link
Member

Bromeon commented Aug 13, 2023

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 🤔

@mhoff12358
Copy link
Contributor

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

@andreymal
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: register Register classes, functions and other symbols to GDScript feature Adds functionality to the library
Projects
None yet
Development

No branches or pull requests

7 participants