-
-
Notifications
You must be signed in to change notification settings - Fork 223
Allow custom receivers in virtual methods #359
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
Comments
This could also mitigate #302 to an extent |
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 I was also considering that |
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. |
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 |
Shouldn't it work if 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?
} |
Great observation! In fact, that's one of the main motivations for #131. I'll probably implement that soon. |
Maybe the user can optionally choose to impl #[godot_api]
impl NodeVirtual for Gd<MyClass> {
fn ready(self) {
...
}
} |
Interesting idea, but that would mean:
|
It doesn't address the case of existing interfaces, but I was able to throw together #396 to allow user defined methods to take |
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 |
Could we provide both methods in the trait and then have the macro handle the wiring? So for example: trait INode {
fn ready(&mut self,) { unimplemented !() }
fn ready_gdself(this: Gd<Self>) { unimplemented !() }
} And then have the macro generate the The biggest downside I can see with that is it makes auto-complete a bit messy, we could get around that by adding a second interface trait with both methods, something like |
@snakefangox sorry, I missed your comment earlier. While this is an option, it would generate double the number of functions across all interface traits. We already duplicate every base interface method in all its directly and indirectly derived interfaces, and this would further compound it -- with an effect on doc discoverability, compile times, IDE completion etc. Two separate traits would be worse though: the user could no longer decide on a per-method level whether they'd want a reference or Feature gates are an option, but they also contribute to combinatoric explosion of testing and things that can go wrong, so I'm very hesitant to make parts configurable that don't need to be. That said, I don't have a great solution either. One option would be to make all trait methods take Maybe worth noting that the double-borrow issue happens mostly in re-entrancy scenarios such as
And in such cases, it's possible to migitate the problem on the call site with an explicit Given that the issue has existed for quite a while, it would be interesting to hear how users have worked around this so far. |
Honestly, the longer I think about it, the more it makes sense to use a Gd receiver for just one virtual method inherited by all of Godot's Objects – notification. That said, this is entirely arbitrary.
In some cases, the workaround is obvious. For example, we can await the ready signal, which is emitted after the ready method (when our instance is no longer bound): fn ready(&mut self) {
let mut this = self.to_gd();
godot::task::spawn(async move {
let ready = this.signals().ready();
let _ = ready.into_future().await;
let mut state = Box::new(NewTurnState);
state.enter(&mut this);
this.bind_mut().state = Some(state);
});
} (we can also connect to it with one-shot flag) However, this isn’t an option for all virtual methods. Some cases can be handled with If strict execution order is necessary processing can be moved to a Godot singleton (be it autoload singleton or engine singleton). For example, an AIServer might wait until the end of the frame and then trigger signals that are received by methods with a Gd receiver. |
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.The text was updated successfully, but these errors were encountered: