Crate bevy_defer
source ·Expand description
§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(async move {
// This is an `AsyncWorldMut`.
// like tokio::spawn() this only works in the async context.
let world = world();
// Wait for state to be `GameState::Animating`.
world.in_state(GameState::Animating).await;
// This function is async because we don't own the world,
// we send a query request and wait for the response.
let richard_entity = world.resource::<NamedEntities>()
.get(|res| *res.get("Richard").unwrap()).await?;
// Move to an entity's scope, does not verify the entity exists.
let richard = world.entity(richard_entity);
// We can also mutate the world asynchronously.
richard.component::<HP>().set(|hp| hp.set(500)).await?;
// Move to a component's scope, does not verify the entity exists.
let animator = richard.component::<Animator>();
// Implementing `AsyncComponentDeref` allows you to add extension methods to `AsyncComponent`.
animator.animate("Wave").await?;
// Spawn another future on the executor.
let audio = spawn(sound_routine(richard_entity));
// Dance for 5 seconds with `select`.
futures::select!(
_ = animator.animate("Dance").fuse() => (),
_ = world.sleep(Duration::from_secs(5)).fuse() => println!("Dance cancelled"),
);
// animate back to idle
richard.component::<Animator>().animate("Idle").await?;
// Wait for spawned future to complete
audio.await?;
// Tell the bevy App to quit.
world.quit().await;
Ok(())
});
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!(|recv: Receiver<PositionChanged>, transform: AsyncComponent<Transform>|{
let pos: Vec3 = recv.recv().await;
transform.set(|transform| transform.scale = pos).await?;
})
Then create a AsyncSystems
from it:
let systems = AsyncSystems::from_single(system);
// or
let systems = AsyncSystems::from_iter([a, b, c, ...]);
Add the associated Signal
:
let signal = Signals::from_receiver::<PositionChanged>(sig);
Spawn them as a Bundle
and that’s it! The async executor will
handle it from here.
§Let’s break it down
Signals::from_receiver::<PositionChanged>(sig)
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: Receiver<PositionChanged>, transform: AsyncComponent<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 {
let recv = from_context(context);
let transform = from_context(context);
{..};
return Ok(());
}
The body of the AsyncSystem
. Await will wait for queued queries to complete.
let pos: Vec3 = recv.recv().await;
transform.set(|transform| transform.scale = pos).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 (a, b, c, d, e, f) = futures::join! {
query1,
query2,
query3,
query4,
query5,
query6,
}.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.
Modules§
Macros§
- Macro for constructing an async system via the
AsyncEntityParam
abstraction. SystemConfigs
for running the async executor once.- Quickly construct multiple marker
SignalId
s at once.
Structs§
- An
AsyncSystemParam
that gets or sets a component on the currentEntity
. - Async version of
EntityMut
orEntityCommands
. - Async version of
Query
on a single entity. - Resource containing a reference to an async executor.
- Async version of
Query
- Queue for deferred queries applied on the
World
. - An
AsyncSystemParam
that gets or sets a resource on theWorld
. - An async system function.
- Async version of
SystemParam
. - A component containing an entity’s
AsyncSystem
s. SystemParam
for obtaining anAsyncWorldMut
.- A deferred query on a
World
. - A deferred parallelizable query on a
World
. - An
bevy_defer
plugin does not run its executor by default. - A resource containing named signals.
- A boxed type erased nullable dynamic object.
- Queue for deferred queries applied on the
World
. Command
for spawning a task.
Enums§
- Standard errors for the async runtime.
Traits§
- A type that can converted to and from
Object
. - Add method to
AsyncComponent
through deref. - A parameter of an
AsyncSystem
. - Add method to
AsyncEntityQuery
through deref. - Add method to
AsyncQuery
through deref. - Add method to
AsyncResource
through deref.
Functions§
- Creates a new one-shot channel for sending a single value across asynchronous tasks.
- Push inactive
AsyncSystems
to the executor. - Run
AsyncExecutor
- Try resolve queries sent to the queue.
- Spawn a
bevy_defer
compatible future. - Spawn a
bevy_defer
compatible future. - Obtain the
AsyncWorldMut
of the currently runningbevy_defer
executor.
Type Aliases§
- Result type of
AsyncSystemFunction
.