bevy_malek_async
Runtime‑agnostic async ECS access for Bevy. This crate lets you run async work on any executor (Tokio, Bevy's task pools, or another runtime), and then safely hop into Bevy's world to read or mutate ECS state using normal SystemParams.
This is an experimental, stop‑gap crate — a simplified version of functionality I'd like to eventually ( potentially ) upstream into Bevy, where it could run in parallel with other systems. Today, world access is serialized into short "access windows" inside Bevy's schedule. See Limitations for details.
Features
- Runtime‑agnostic: use with Tokio, Bevy task pools, or any async executor
- Familiar ECS access: acquire
Res,ResMut,Query, and otherSystemParams - Minimal plumbing: add one plugin and
awaita single helper future
How It Works
AsyncPlugin installs a small system in multiple schedule stages (Pre/Startup/Update/Post). Each time that system runs, it temporarily exposes the ECS world to pending async tasks, wakes them, lets their closures run to completion, applies their system state, then closes access again. This guarantees that world access does not overlap with other systems.
Your async code calls async_access(world_id, |params| { /* ECS work */ }).await;. The closure runs during the next access window, with params being whatever SystemParam (or tuple of them) you requested.
Install
In your Cargo.toml:
[]
= "0.17"
= "0.1"
# If you want to use the Tokio example below
= { = "1", = ["full"] }
Quick Start (Tokio)
Below is a minimal example that spawns a Tokio task on its own thread and then mutates a Bevy resource from that task using async_access.
use *;
use ;
;
Run the included example from this repo instead:
Other Runtimes (Bevy task pools, async-std, smol, …)
async_access is just a Future. You can await it on any executor:
-
Bevy task pools (names vary by Bevy version):
use *; use IoTaskPool; // or AsyncComputeTaskPool/ComputeTaskPool use ; ; -
Any other runtime: spawn a task as usual and
await async_accessthe same way.
API Overview
-
AsyncPlugin- Registers access windows in
PreStartup,Startup,PostStartup,PreUpdate,Update, andPostUpdatestages. - Also initializes
WorldIdResand an internal counter resource.
- Registers access windows in
-
async_access<P, F, Out>(world_id, f) -> impl Future<Output = Out>P: SystemParam(tuples work too). Examples:Res<T>,ResMut<T>,Query<...>,Commands, etc.f: FnOnce(P::Item<'_, '_>) -> Outruns during the next access window.- Returns
Outto your async context; often()is fine.
-
WorldIdRes- A resource containing the current
WorldId. You can also obtain it withWorld::id()inside a regular system.
- A resource containing the current
Example of multiple params in one access:
use *;
use async_access;
async
Limitations
- Not parallel with other systems: ECS access from async tasks is serialized into short access windows and does not run in parallel with Bevy systems.
- Keep closures short: do only the minimal world reads/writes inside
async_access. Perform heavy work before/after, off the world unless you're willing to eat the cost on both your async pool and your bevy world. - Experimental/unsafe internals: relies on carefully‑scoped unsafe world access. Expect sharp edges; not production‑ready.
- Per‑world: you must pass the correct
WorldIdtoasync_access.
Motivation and Future Work
The goal is to offer a simple bridge between external async work and Bevy ECS without committing to a specific runtime. Long‑term, the intention is a first‑class Bevy solution that can safely interleave with system parallelism and scheduling.