Bevy Defer
A simple asynchronous runtime for executing deferred queries.
Getting Started
There are two main ways to utilize this crate, spawn_task and AsyncSystems.
Spawning in a Sync Context
This is a straightforward way to implement some logic that can wait for signals, animations or other events to complete. In fact you can create your game logic entirely in async rust!
commands.spawn_task;
You can call spawn on Commands, World or App.
AsyncSystems
AsyncSystems is a system-like async function capable of driving reactive UIs and other similar use cases.
The concept behind AsyncSystems is straightforward: by adding components Signals and AsyncSystems,
we enable the execution of deferred queries through asynchronous semantics.
This functionality can be implemented directly at entity creation site (via Commands) without the need for world access.
Signals provide robust inter-entity communication, when used in conjunction with AsyncSystems.
Example
To create an AsyncSystems, create an AsyncSystem first via a macro:
// Set scale based on received position
let system = async_system!
Then create a AsyncSystems from it:
let systems = from_single;
// or
let systems = from_iter;
Add the associated Signal:
let signal = ;
Spawn them as a Bundle and that's it! The async executor will
handle it from here.
Let's break it down
PositionChanged is a SignalId, or a discriminant + an associated type.
In this case the type of the signal is Vec3. We can have multiple signals
on an Entity with type Vec3, but not with the same SignalId.
|recv: , transform: |
Receiver receives the signal, AsyncComponent allows us to get or set data
on a component within the same entity.
Notice the function signature is a bit weird, since the macro roughly expands to
move | context | async move
The body of the AsyncSystem. Await will wait for queued queries to complete.
let pos: Vec3 = recv.recv.await;
transform.set.await?;
How does this AsyncSystem run?
You can treat this system like a loop
- At the start of the frame, run this function if not already running.
- Wait until something sends the signal.
- Write received position to the
Transformcomponent. - Wait for the write query to complete.
- End and repeat step
1on the next frame.
Supported System Params
| Query Type | Corresponding Bevy/Sync Type |
|---|---|
AsyncWorldMut |
World / Commands |
AsyncEntityMut |
EntityMut / EntityCommands |
AsyncQuery |
WorldQuery |
AsyncEntityQuery |
WorldQuery on Entity |
AsyncSystemParam |
SystemParam |
AsyncComponent |
Component |
AsyncResource |
Resource |
Sender |
Signals |
Receiver |
Signals |
You can create your own AsyncEntityParam by implementing it.
Signals
Here are the guarantees of signals:
- A
Signalis read at most once per write for every reader. - Values are not guaranteed to be read if updated in rapid succession.
- Value prior to reader creation will not be read by a new reader.
Implementation Details
bevy_defer uses a single threaded runtime that always runs on bevy's main thread inside the main schedule,
this is ideal for wait heavy or IO heavy tasks, but CPU heavy tasks should not be run here.
The executor runs synchronously as a part of the schedule. At each execution point, we will poll our futures until no progress can be made.
Imagine DefaultAsyncPlugin is used, which means we have 3 execution points per frame, this code:
let a = query1.await;
let b = query2.await;
let c = query3.await;
let d = query4.await;
let e = query5.await;
let f = query6.await;
takes at least 2 frames to complete, since queries are deferred and cannot resolve immediately.
To complete the task faster, try use futures::join! or futures_lite::future::zip to
run these queries concurrently.
let = join! .await;
Since most methods on AsyncWorldMut queue the query
immediately without needing to be polled,
let a = query1;
let b = query2;
let c = query3;
let a = a.await;
let b = b.await;
let c = c.await;
is likely to work as well.
Versions
| bevy | bevy_defer |
|---|---|
| 0.12 | 0.1 |
| 0.13 | 0.2-latest |
License
License under either of
Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option.
Contribution
Contributions are welcome!
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.