A simple asynchronous runtime for executing async coroutines in the bevy engine.

Overview

Bevy Defer

Crates.io Docs Bevy tracking

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 for 4 seconds.

  • AsyncWorld.sleep_frames(4) pauses the future for 4 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.

You might also like...
Crossterm plugin for the bevy game engine
Crossterm plugin for the bevy game engine

What is bevy_crossterm? bevy_crossterm is a Bevy plugin that uses crossterm as a renderer. It provides custom components and events which allow users

Concise Reference Book for the Bevy Game Engine

Unofficial Bevy Cheat Book Click here to read the book! Concise reference to programming in the Bevy game engine. Covers useful syntax, features, prog

Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions
Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions

Introduction Proof-of-concept of getting OpenXR rendering support for Bevy game engine using gfx-rs abstractions. (hand interaction with boxes missing

A physics lib for the bevy game engine based on physme

physimple Physimple aims to be the simplest(and capable) physics engine(currently for bevy) WARNING Beware for breaking changes with each update for n

An immediate mode 2D drawing API for Bevy game engine

Bevy Canvas API prototype An unofficial immediate mode 2D drawing API for Bevy game engine. Check the documentation or the examples to know how to use

A highly customizable snake clone made in Rust with the Bevy engine, named after the Japanese word for snake, 蛇.
A highly customizable snake clone made in Rust with the Bevy engine, named after the Japanese word for snake, 蛇.

Hebi 🐍 A highly customizable snake clone made in Rust with the Bevy engine, named after the Japanese word for snake, 蛇(へび). Configuration One of the

2d Endless Runner Game made with Bevy Game Engine
2d Endless Runner Game made with Bevy Game Engine

Cute-runner A 2d Endless Runner Game made with Bevy Game Engine. Table of contents Project Infos Usage Screenshots Disclaimer Project Infos Date: Sept

A Bevy Engine plugin for making 2D paths, smooth animations with Bezier curves
A Bevy Engine plugin for making 2D paths, smooth animations with Bezier curves

bevy_pen_tool A Bevy Engine plugin for making 2D paths and smooth animations with Bezier curves TODO: Mesh-making functionality for building 2D shapes

A vdom-free reactive UI lib for the bevy game engine

ui4 ui4 is my fourth major attempt at making a UI dataflow library for the Bevy game engine. More specifically, it's a vdom-less UI library which uses

Comments
  • RPITIT always

    RPITIT always "captures" self even if result is 'static.

    This function can no longer be written even though the result is 'static, since it captures AsyncComponent. This is a regression.

    fn() -> impl Future {
        world().component::<Transform>().get(..)
    }
    

    Maybe always return a channel Receiver instead?

    opened by mintlu8 1
  • Nicer ergonomics for animating materials

    Nicer ergonomics for animating materials

    Hey, thanks for this awesome crate!

    I've been animating some materials with this, however it's rather cumbersome because of having to turn the Handle<Material>> into the actual material by using the Assets<Material> resource.

    I've tried to implement an AsyncMaterialHandle by following the dance example to make this more ergonomic, but it's not working out:

    
    #[derive(Component)]
    pub struct MyColorMaterial(Handle<ColorMaterial>);
    
    impl AsyncComponentDeref for MyColorMaterial {
        type Target = AsyncColorMaterial;
    
        fn async_deref(this: &AsyncComponent<Self>) -> &Self::Target {
            AsyncColorMaterial::ref_cast(this)
        }
    }
    
    #[derive(RefCast)]
    #[repr(transparent)]
    pub struct AsyncColorMaterial(AsyncComponent<MyColorMaterial>);
    
    impl AsyncColorMaterial {
        pub async fn animate(
            &self,
            target_col: Color,
            curve: impl FnMut(f32) -> f32 + Send + 'static,
            duration: impl bevy_defer::tween::AsSeconds,
        ) -> Result<(), AccessError> {
            Ok(())
        }
    }
    

    When I do smth like this in an async task:

    let mat_handle = world.entity(entity).component::<Handle<ColorMaterial>>();
    mat_handle.animate(...);
    

    The animate method sadly does not show up.

    Do you have any examples of ergonomically animating a Materials properties? I'm probably missing something fundamental here..

    opened by LennyPenny 2
Releases(v0.11.0)
  • v0.11.0(May 21, 2024)

    Features

    • Direct World Access

    World is now a mutable thread local using scoped_tls_hkt, this mean all (non-watch) access are now immediate and not deferred. Because of this default_settings now only runs once per frame on update. This makes bevy_defer significantly more consistent but less parallelizable with other systems. Use bevy_tasks if blocking/cpu intensive operations are needed.

    Before:

    entity.component<Transform>().set(|x| x.translation = v).await?
    

    After:

    entity.component<Transform>().set(|x| x.translation = v)?
    
    • Derive AsyncComponent and AsyncResource.

    Traits for extension methods can now be derived.

    • Attribute macro async_access.

    A macro that mirrors methods with 'static results to async accessors.

    #[async_access]
    impl MyTransform {
        fn get_translation(&self) -> Vec3 { ... }
    }
    
    async_entity.component::<MyTransform>().get_translation()?
    
    • BeforeAsyncExecutor schedule

    We now have a dedicated schedule to run reactors in. Extension methods react_to_* exist on App to add these reactors to Event, State and Component change. In addition, react_to_scene_load, react_to_animation and react_to_ui are automatically added with their features enabled.

    • Better Error Handling

    Added a mini anyhow::Error called CustomError that works nicely with the standard AccessError and bevy_log.

    Deprecations

    • world() has been replaced by AsyncWorld. Most top level methods like spawn() are deprecated and moved to AsyncWorld. This can help prevent confusing errors if you have dependencies like smol or tokio with their own spawns.

    • AsyncFailure has been replace by AccessError and CustomError.

    • bevy_mod_picking support now lives in a separate crate.

    • The ability to create thread locals have been removed, since the world is now mutably accessible.

    Source code(tar.gz)
    Source code(zip)
  • v0.9.0(Apr 12, 2024)

    What's New

    • Replaced futures_executor with async_executor

    This is the same executor as bevy_tasks, which would fit nicely in the bevy ecosystem. The AsyncExecutor can now be cloned to spawn futures. Additionally, the dependency on futures has been minimized to not include the macros.

    • Id for AsyncSystem

    You can now add an id to an AsyncSystem to allow targeted removal from the AsyncSystems component.

    • The AsyncAccess trait.

    Methods like get, set, watch etc are now provided by the AsyncAccess trait. This makes access methods in this crate more consistent. get for example will never wait for something to be loaded. get_on_load can now be used instead to wait for an item to be loaded before accessing it.

    • AsyncQuerySingle

    Added a specialized accessor for the single() query.

    • Stream support

    Signal is now a Stream, API regarding States and Event are now stream based.

    • Reactors

    Add systems react_to_* to react to changes in the world.

    • react_to_state allows you to react to state change asynchronously
    • react_to_event allows you to subscribe to events asynchronously

    Breaking Changes

    • get, cloned on AsyncAsset no longer waits for the asset to be loaded, use the *_on_load methods instead.

    • The old AsyncEvent is scrapped and replaced with EventStream.

    • Renames

      • extension is moved to access::deref
      • picking is moved to ext::picking
    Source code(tar.gz)
    Source code(zip)
  • v0.8.0(Mar 26, 2024)

    What's Changed

    • Better plugin modularity.

    We can now add schedule and system sets via the new AsyncPlugin.

    • Non-Send internals

    Most of this crate now runs on Rc and RefCell, this means we can drop some lifetimes around not cloning Arcs, you would find chaining methods much easier!

    • Thread locals!

      • We can push &World, Resource and NonSend to thread local storage, enabling more non-deferred access.

      • AssetServer, NamedSignals, CommandQueue are accessible via thread locals by default.

      • With the optional &World thread local access, all get access are immediate, at the cost of some parallelization.

    • AsyncEventReader, AsyncAsset and better named signal support

      • AsyncEventReader is now a persistent event reader that behaves properly.

      • AsyncAsset, or rather Handle can be accessed by AssetPath without deferring.

      • NamedSignals can be accessed by name without deferring.

    • apply_command and apply_command_queue

    apply_command uses a thread local CommandQueue and is no longer async.

    • Tweening

    We can run cancellable and awaitable tweening functions on FixedUpdate.

    • bevy_mod_picking integration

    We now have a reactor for bevy_mod_picking.

    • Signals rewrite

    Signals is no longer type erased, therefore Object is no longer a part of this crate (see dyn_object). Internals of signals are more encapsulated.

    • spawn rename

    spawn is now the unbounded spawn method, since dropping futures can cause unexpected behaviors and should be opt-in.

    Source code(tar.gz)
    Source code(zip)
Owner
Mincong Lu
Crablang enjoyer.
Mincong Lu
Bevy Simple Portals is a Bevy game engine plugin aimed to create portals.

Portals for Bevy Bevy Simple Portals is a Bevy game engine plugin aimed to create portals. Those portals are (for now) purely visual and can be used t

Sélène Amanita 11 May 28, 2023
A simple extension for `bevy-editor-pls` to support tilemap editing right inside the bevy app.

What is this This is a simple tilemap editor plugin, that hooks right into bevy_editor_pls to work with bevy_ecs_tilemap. It works completely within i

null 3 May 8, 2023
Simple RUST game with the Bevy Engine

Simple RUST Game using the Bevy Engine YouTube videos for this code base: Episode 1 - Rust Game Development tutorial from Scratch with Bevy Engine Epi

null 150 Jan 7, 2023
Simple island generator written in rust using bevy engine

Getting Started Easy enough to run cargo run --release Change generation speed Find the system set that looks like this .add_system_set(

Kris 3 Apr 21, 2022
Bevy is a refreshingly simple data-driven game engine built in Rust

What is Bevy? Bevy is a refreshingly simple data-driven game engine built in Rust. It is free and open-source forever! WARNING Bevy is still in the ve

John Winston 3 Jun 3, 2022
A simple-to-use input manager for the Bevy game engine that empowers players and makes accessibility easy.

Bevy Ineffable A simple-to-use input manager for the Bevy game engine that empowers players and makes accessibility easy. Core tenets Make accessibili

Jazarro 10 Mar 2, 2024
bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy

Bevy Voxel Cone Tracing bevy-hikari is an implementation of voxel cone tracing global illumination with anisotropic mip-mapping in Bevy. Bevy Version

研究社交 208 Dec 27, 2022
Minecraft using Bevy and Bevy-Meshem

minecraft_bevy Minecraft_bevy was built to showcase bevy_meshem. After a week of developing it has: Chunk loading / unloading each chunk's mesh is bei

Adam 7 Oct 4, 2023
Basic first-person fly camera for the Bevy game engine

bevy_flycam A basic first-person fly camera for Bevy 0.4 Controls WASD to move horizontally SPACE to ascend LSHIFT to descend ESC to grab/release curs

Spencer Burris 85 Dec 23, 2022
Inspector plugin for the bevy game engine

bevy-inspector-egui This crate provides the ability to annotate structs with a #[derive(Inspectable)], which opens a debug interface using egui where

Jakob Hellermann 517 Dec 31, 2022