-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Discussion: System return life time escape hatch to enable async systems #17037
base: main
Are you sure you want to change the base?
Discussion: System return life time escape hatch to enable async systems #17037
Conversation
Holding a reference to anything in the world would be unsound as any exclusive access to &mut World could invalidate the reference. The only way to do this is to convert the references to pointers, but I don't think there's a way for bevy to provide a safe way of dereferencing those pointers for the reason above. |
It might be possible to make something sound if you made unsafe fn run_unsafe<'w>(&mut self, input, world: UnsafeWorldCell<'w>) -> Self::Out<'w> And from there you could make a variant of None of that would help with futures, though! If the future captures the |
Thinking about this some more, it might work to create a new smart pointer type that would store the raw pointer, entity, and EntityLocation and then that is checked to make sure the location hasn't changed when dereferenced. I think there's all the info needed in the EntityLocation to make sure the pointer hasn't moved. There would need to be some mechanism to make sure you can't hold the & and &mut references that are handed out across a yield either using lifetimes somehow or using a closure. Having said that I'm not sure there's that much value in holding a pointer across a yield point. I feel like an async system api will look a lot more like how exclusive systems work, where you hold something like a QueryState that doesn't have any references itself. You would then pass it a reference to the world to work with the data, but then those references would not be allowed to be held across the await. You might then provide a mechanism for pausing and resuming iteration, with some way of informing the user if data has been removed or added to the query. |
@hymm I see. That make a lot of sense. I'll experiment on this a bit more. |
e400d78
to
954aedd
Compare
Objective
This PR is more of a discussion than it is a PR.
I would like to create a system with a return value that captures the 'world lifetime. The system will then be wrapped in a system wrapper which takes the return value and do something fancy on it.
Specifically, my systems return a Future. The wrapping system stores the returned future and repeatedly call
poll
on it. It will yield the control back to the scheduler whenever [Future::poll
] returnsPending
using #17036.This means that the returned value will have to capture the lifetimes of the system. Intuitively this should be ok, because the wrapping system shares the same lifetimes as the inner system.
However, this can't be done because
SystemParamFunction
is only implemented for functions that are valid for all lifetimes.Solution
For potentially problematic cases:
We do have mechanisms to detect this at runtime. Bevy will print
system1 could not access system parameter ResMut<FooBar>
.The solution introduced in this PR forces Rust to forget about the lifetimes with a transmute. Without the transmute, rustc gives the following error message:
However, I'm not 100% happy about this. Are there any other methods that we can employ here to properly and safely teach Rust about our lifetime assumption when the system must return a value that captures lifetimes?