Open
Description
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.
Metadata
Metadata
Assignees
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
gg-yb commentedon Jul 24, 2023
This could also mitigate #302 to an extent
Bromeon commentedon Jul 24, 2023
It can be argued that since bad runtime borrows are a common source of confusion, having an explicit
would be clearer. But I don't fully like this, as it makes code less ergonomic and punishes all the common cases like
ready()
andprocess()
, 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 tothis.bind()
andthis.bind_mut()
. Problem is that it's not possible to use theself
modifier, so even the function body would need to be rewritten, which would likely be very involved with weird edge cases... 🤔lilizoey commentedon Jul 24, 2023
We could just have it desugar to something like
Idk the exact syntax to make the name resolution work properly here but yeah.
gg-yb commentedon Jul 25, 2023
Regarding #338, I'm not sure if this would allow you to workaround the situation described therein.
on_notification would still be required to not touch this, as far as I can see
Ogeon commentedon Jul 25, 2023
Shouldn't it work if
this
is upcast toNode
first? It's not pretty or obvious, though...Bromeon commentedon Jul 26, 2023
Great observation! In fact, that's one of the main motivations for #131. I'll probably implement that soon.
LeaoLuciano commentedon Aug 12, 2023
Maybe the user can optionally choose to impl
Gd<MyClass>
instead of implMyClass
:Bromeon commentedon Aug 13, 2023
Interesting idea, but that would mean:
#[godot_api]
blocks, but that comes with its own issues&self
/&mut self
(not the valueself
), this would need to be done here too, which is a bit weird 🤔mhoff12358 commentedon Sep 4, 2023
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 commentedon Sep 3, 2024
Just ran into this issue when I tried to test #883
So I have to make an (unnecessary?) "inner" object just to handle signals
snakefangox commentedon Mar 13, 2025
Could we provide both methods in the trait and then have the macro handle the wiring? So for example:
And then have the macro generate the
__virtual_call
function to use the one the user implemented (or error if both were implemented).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
INodeWithGdself
and keepingINode
unchanged. Then we can auto-implINodeWithGdself
for allINode
to make generics easier and so that implementing both is a compile error. In terms of issues there, I guess there is function name resolution when both traits are in scope but given the interface traits are really only for implementing that should be fine. Also code size, we could probably put this behind a feature gate fairly easily if that's a problem?Bromeon commentedon May 6, 2025
@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
Gd
toself
, and adding one "gdself" method would require refactoring the wholeimpl
.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
this: Gd<Self>
and let the macro desugar&mut self
into an initiallet this = this.bind_mut()
statement, similar to this but in the same body. However that's possibly also confusing for IDEs and would mean that IDE-generated methods would all have theGd
signature instead of the reference one. But it might still be worth trying.Maybe worth noting that the double-borrow issue happens mostly in re-entrancy scenarios such as
user_fn
calls an engine API, which triggers a notificationon_notification
, which attempts to borrow already-borrowed&mut self
and failsAnd in such cases, it's possible to migitate the problem on the call site with an explicit
self.base_mut()
lock held across the call -- even in situations where it's not necessary, see e.g. #1157 (comment).Given that the issue has existed for quite a while, it would be interesting to hear how users have worked around this so far.
Yarwin commentedon May 8, 2025
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):
(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
call_deferred
, which postpones method execution until the end of the frame (e.g.,self.base().call_deferred("the_real_physics_process", &[])
).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.