Crate bevy_defer
source ·Expand description
§Bevy Defer
A simple asynchronous runtime for executing async coroutines.
§Motivation
Async rust is incredible for modelling wait centric tasks like coroutines. Not utilizing async in game development is a huge waste of potential.
Imagine we want to model a rapid sword attack animation, in async rust this is straightforward:
swing_animation().await;
show_damage_number().await;
damage_vfx().await;
swing_animation().await;
show_damage_number().await;
damage_vfx().await;
At each await
point we wait for something to complete, without wasting resources
spin looping a thread or defining a complex state machine in a system.
What if we want damage number and damage vfx to run concurrently and wait for both
before our next attack? It’s simple with async
semantics!
futures::join! {
show_damage_number(),
damage_vfx()
};
swing_animation().await;
§Why not bevy_tasks
?
bevy_tasks
has no direct world access, which makes it difficult to write game
logic in it.
The core idea behind bevy_defer
is simple:
// Pseudocode
static WORLD_CELL: Mutex<&mut World>;
fn run_async_executor(world: &mut World) {
let executor = world.get_executor();
WORLD_CELL.set(world);
executor.run();
WORLD_CELL.remove(world);
}
Futures spawned onto the executor can access the World
via access functions, similar to how database transaction works:
WORLD_CELL.with(|world: &mut World| {
world.entity(entity).get::<Transform>().clone()
})
As long as no references can be borrowed from the world, and the executor is single threaded, this is perfectly sound!
§Spawning a Task
You can spawn a task onto bevy_defer
from World
, App
, Commands
, AsyncWorld
or AsyncExecutor
.
Here is an example:
commands.spawn_task(|| async move {
// Wait for state to be `GameState::Animating`.
AsyncWorld.state_stream::<GameState>().filter(|x| x == &GameState::Animating).next().await;
// Obtain info from a resource.
// Since the `World` stored as a thread local,
// a closure is the preferable syntax to access it.
let richard_entity = AsyncWorld.resource::<NamedEntities>()
.get(|res| *res.get("Richard").unwrap())?;
// Move to an entity's scope, does not verify the entity exists.
let richard = AsyncWorld.entity(richard_entity);
// We can also mutate the world directly.
richard.component::<HP>().set(|hp| hp.set(500))?;
// Move to a component's scope, does not verify the entity or component exists.
let animator = AsyncWorld.component::<Animator>();
// Implementing `AsyncComponentDeref` allows you to add extension methods to `AsyncComponent`.
animator.animate("Wave").await?;
// Spawn another future on the executor.
let audio = AsyncWorld.spawn(sound_routine(richard_entity));
// Dance for 5 seconds with `select`.
futures::select!(
_ = animator.animate("Dance").fuse() => (),
_ = AsyncWorld.sleep(Duration::from_secs(5)) => 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.
AsyncWorld.quit();
Ok(())
});
§World Accessors
The entry point of all world access is AsyncWorld
,
for example a Component
can be accessed by
let translation = AsyncWorld
.entity(entity)
.component::<Transform>()
.get(|t| {
t.translation
}
)
This works for all the bevy things you expect, Resource
, Query
, etc.
See the access
module and the AsyncAccess
trait for more detail.
You can add extension methods to these accessors via Deref
if you own the
underlying types. See the access::deref
module for more detail. The
async_access
derive macro can be useful for adding method to
async accessors.
We do not provide a AsyncSystemParam
, instead you should use
one-shot system based API on AsyncWorld
.
They can cover all uses cases where you need to running systems in bevy_defer
.
§Async Basics
Here are some common utilities you might find useful from an async ecosystem.
-
AsyncWorld.spawn()
spawns a future. -
AsyncWorld.spawn_scoped()
spawns a future with a handle to get result from. -
AsyncWorld.yield_now()
yields execution for the current frame, similar to how coroutines work. -
AsyncWorld.sleep(4.0)
pauses the future for4
seconds. -
AsyncWorld.sleep_frames(4)
pauses the future for4
frames.
§Bridging Sync and Async
Communicating between sync and async can be daunting for new users. See this amazing tokio article: https://tokio.rs/tokio/topics/bridging.
Communicating from sync to async is simple, async code can provide channels
to sync code and await
on them, pausing the task.
Once sync code sends data through the channel, it will
wake and resume the corresponding task.
Communicating from async to sync require more thought. This usually means mutating the world in an async function, then a system can listen for that particular change in sync code.
async {
entity.component::<IsJumping>().set(|j| *j == true);
}
pub fn jump_system(query: Query<Name, Changed<IsJumping>>) {
for name in &query {
println!("{} is jumping!", name);
}
}
The core principle is async code should help sync code to do less work, and vice versa!
§Signals and AsyncSystems
AsyncSystems
and Signals
provides per-entity reactivity for user interfaces.
Checkout their respective modules for more information.
§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 simple game logic, wait heavy or IO heavy tasks, but CPU heavy tasks should not be run in bevy_defer
.
The AsyncComputeTaskPool
in bevy_tasks
is ideal for this use case.
We can use AsyncComputeTaskPool::get().spawn()
to spawn a future on task pool and call await
in bevy_defer
.
§Usage Tips
The futures
and/or futures_lite
crate has excellent tools to for us to use.
For example futures::join!
can be used to run tasks concurrently, and
futures::select!
can be used to cancel tasks, for example despawning a task
if a level has finished.
§Versions
bevy | bevy_defer |
---|---|
0.12 | 0.1 |
0.13 | 0.2-0.11 |
0.14 | 0.12-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.
Re-exports§
pub use access::traits::AsyncAccess;
pub use access::AsyncWorld;
pub use crate::sync::oneshot::channel;
Modules§
- Asynchronous accessors to the
World
. - Per-entity repeatable async functions.
- Cancellation handles for
bevy_defer
. - Extensions to various components.
- Signals and synchronization primitives for reacting to standard bevy events.
- Signals for
bevy_defer
. !Send
synchronization primitives forbevy_defer
.- Systems in
bevy_defer
. - Tweening support for
bevy_defer
.
Macros§
- Construct an async system via the
AsyncEntityParam
abstraction. - Try run a potentially async block of arguments with result type
AccessError
, always discard the result and does not log the error. - Quickly construct multiple marker
SignalId
s at once.
Structs§
- A set that can wait for multiple assets to finish loading.
!Send
resource containing a reference to an async executor, this resource can be cloned to spawn futures.- An
bevy_defer
plugin that can run the executor through user configuration. - A schedule that runs before
run_async_executor
. - The core
bevy_defer
plugin that does not run its executors. - An alternative
AccessError
with a custom error message. All types implementingError
can propagate toCustomError
via?
. UseError::source
to specify the associatedAccessError
, otherwiseAccessError::Custom
. If propagated toAccessError
via?
, will log the error message viabevy_log
. - A
String
error. - A
World
reference with a cachedQueryState
. - Queue for deferred
!Send
queries applied on theWorld
. Command
for spawning a task.
Enums§
- Standard errors for the async runtime.
Traits§
Functions§
- Returns
true
if in async context, for diagnostics purpose only. - Runs the
BeforeAsyncExecutor
schedule. - spawnDeprecatedSpawn a
bevy_defer
compatible future. - spawn_scopedDeprecatedSpawn a
bevy_defer
compatible future with a handle. - worldDeprecatedObtain the
AsyncWorld
of the currently runningbevy_defer
executor.
Type Aliases§
- Result type of spawned tasks.
- AsyncFailureDeprecatedDeprecated access error.
- AsyncResultDeprecatedDeprecated access result.
Attribute Macros§
- Mirror an
impl
block to an async access component.