-
-
Notifications
You must be signed in to change notification settings - Fork 224
Async/Await for Signals #261
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
I implemented a POC for a It still has two issues which I did not bother to resolve yet:
|
@TitanNano I think your implementation doesn't work correctly if the jrb0001@74afb5b is a variant without the thread-local, but it needs some unsafe and panics when called from another thread. It should also fix recursively creating tasks, but I didn't test it. jrb0001@a27d0bc is a third, more complex variant which has both
|
@jrb0001 yes that is true. I ignored that so far. Have you encountered a case were the callable was called on an other thread?
I don't see the benefit of this if it still panics.
|
No, I spotted it while reading through it.
The panic is guaranteed while your version can run another future which was assigned the same ID on the thread of the signal.
|
This can easily be solved by adding the
But it adds a lot of complexity without solving any problems (at least as far as I'm aware). Correctly, panicking can be solved like I mentioned above.
Even with I have been thinking about the stated concern of signals being emitted on other threads, and I believe the correct solution would be to redirect the wakers future polling into the correct thread. So the alternatives for now that I can see are: a) limit async tasks to the main thread by panicking when trying to spawn a task on another thread. With this limitation in place, we can connect to signals in deferred mode inside the Signal Future and reliably run the callable on the main-thread. b) properly panic when the callable runs on a different thread than the future and leave the rest up to the user. This is more flexible but much less predictable, so I think a) is probably preferred. Once there is a thread-safe version of |
There are seem to be some issues related to emitting a signal from a callable connected to itself. This will call the I also tried implementing So if deferred mode signals always run on the main thread, then that's probably the best option. I also ran into an issue with the I ended up using an Autoload with a |
I think this was changed in 4.3. In 4.2 it's disconnected after calling and in 4.3 it's disconnected first. |
I think I found another two issues that affect all our implementations:
Disconnecting requires the |
We can use a
We might want to connect to the signal in the normal way and schedule a deferred callable to actually poll the future. But I haven't tried it yet. |
Isn't that only for
I had that idea too but didn't find a generic way to schedule it. There is |
Yeah right I missed that.
Why do you believe it will never be dropped? As soon as the future is dropped, the callable should be dropped as well, shouldn't it?
Looks like |
I added the handling of the following cases to my implementation:
EDIT: added handing for nested godot_tasks and clean-up when deinitializing. |
If the Future is dropped, then yes, it will work as intended. But if the signal is disconnected (freed object / through code), then the Future will wait for a wake-up which will never come. |
Yes, if someone goes out of their way to disconnect the callable, then the future will never complete. I don't see how this can be avoided. I wonder how this behaves in GDScript. |
If something intentionally disconnect a callable it didn't connect, then we can blame that. But can we expect every code that calls It's possible to detect the freed object case by polling I did a quick test and it looks like gdscript |
@jrb0001 I pretty much had the same ideas and came to the same conclusion. Getting a callback on object deletion is just not possible, and checking all pending futures every frame is excessive. A middle ground solution that came to me could be: If the Alternatively, we could always check for dead futures during insert, since we already scan the buffer for empty slots anyway. |
I don't think we can know when a future is dead, though. If we want to keep this runtime agnostic (and I strongy believe we should) we have no information when a future will stop making progress. Best we can do is to return a |
It looks like it is possible to use a new Signal disconnected or object freed:
Future dropped:
|
How do you do this?
I already implemented this. |
You probably need to change the future to return |
Let's assume we can create a custom callable which wakes the future both on invocation and on New references to the callable can still be created via Or do you see another way to detect when the object is freed? |
True, as long as some other code keeps the reference it got from We can't guarantee much in presence of malicious code (except the basic rust guarantees), but in my opinion best-effort detection of stuck futures is better than not having anything at all. The guaranteed way to detect a freed object is through something like this: let object: InstanceId = signal.object_id().unwrap();
...;
let is_freed: bool = Gd::try_from_instance_id(object).is_err(); It requires polling which can get expensive if there are hundreds/thousands of waiting futures and it doesn't help in the |
I have now added:
Let's see how well this all works. I'm also open to suggestions for a better name, for the second future. @Bromeon are you interested in including this into the project? I will start testing this in my project now, but I happy to promote it to a PR. |
Very cool to see progress on this front! 👍 @TitanNano I'm trying to catch up, can you maybe summarize how the user API of this might look? Any notable trade-offs/downsides? Can it be feature-gated? |
@Bromeon sure, so far, we have the following: User-facing APIs
Trade-offs
Feature GateAll changes are self-contained, so feature gating them should not be a problem. |
@Bromeon let me know if you got any questions 😃 |
It should be possible to implement
Future
for signals, and add support forasync
functions in exported methods, with them returning a signal.These two things combined should allow us to use async/await syntax when calling methods both in rust and gdscript.
This is going to require a lot of upfront work before it's feasible to start working on this feature. First an actual implementation of signals are needed, but we'll also likely need to create some godot-objects directly in our bindings that will handle the awaiting and execution of these futures, and that is not something we have planned/thought out how to do yet.
The text was updated successfully, but these errors were encountered: