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
Transform
component. - Wait for the write query to complete.
- End and repeat step
1
on 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
Signal
is 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.