Files
bevy/examples/async_tasks/async_channel_pattern.rs
Gonçalo Rica Pais da Silva f255b8e57a Upgrade glam, hexasphere, rand & uuid to latest versions (#22928)
# Objective

- `glam`, `hexasphere` & `rand` have released their latest versions,
update Bevy to support them.

## Solution

- The above have been updated to their compatible versions. `rand_distr`
updated as well to match `rand` v0.10 support.
- `rand_chacha` is soft deprecated and no longer used by `rand`, so its
usage has been changed to `chacha20` to match `rand` dep tree.
- `uuid` is in the process of updating to `getrandom` v0.4, which `rand`
v0.10 supports. This PR remains in draft until a new `uuid` release hits
crates.io.
- `RngCore` is now `Rng`, and `Rng` is now `RngExt`, so this required
updating across many files.
- `choose_multiple` method is deprecated, changed to `sample`.

## Testing

- Chase all compiler errors, since this should not regress any already
existing behaviour.
- This must pass CI without regressions.

## Additional Notes

`getrandom` v0.4 doesn't add anything new for Web WASM support, so the
same `wasm_js` feature is used.
2026-02-19 22:17:25 +00:00

170 lines
5.8 KiB
Rust

//! A minimal example showing how to perform asynchronous work in Bevy
//! using [`AsyncComputeTaskPool`] for parallel task execution and a crossbeam channel
//! to communicate between async tasks and the main ECS thread.
//!
//! This example demonstrates how to spawn detached async tasks, send completion messages via channels,
//! and dynamically spawn ECS entities (cubes) as results from these tasks. The system processes
//! async task results in the main game loop, all without blocking or polling the main thread.
use bevy::{
math::ops::{cos, sin},
prelude::*,
tasks::AsyncComputeTaskPool,
};
use crossbeam_channel::{Receiver, Sender};
use futures_timer::Delay;
use rand::RngExt;
use std::time::Duration;
const NUM_CUBES: i32 = 6;
const LIGHT_RADIUS: f32 = 8.0;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(
Startup,
(
setup_env,
setup_assets,
setup_channel,
// Ensure the channel is set up before spawning tasks.
spawn_tasks.after(setup_channel),
),
)
.add_systems(Update, (handle_finished_cubes, rotate_light))
.run();
}
/// Spawns async tasks on the compute task pool to simulate delayed cube creation.
///
/// Each task is executed on a separate thread and sends the result (cube position)
/// back through the `CubeChannel` once completed. The tasks are detached to
/// run asynchronously without blocking the main thread.
///
/// In this example, we don't implement task tracking or proper error handling.
fn spawn_tasks(channel: Res<CubeChannel>) {
let pool = AsyncComputeTaskPool::get();
for x in -NUM_CUBES..NUM_CUBES {
for z in -NUM_CUBES..NUM_CUBES {
let sender = channel.sender.clone();
// Spawn a task on the async compute pool
pool.spawn(async move {
let delay = Duration::from_secs_f32(rand::rng().random_range(2.0..8.0));
// Simulate a delay before task completion
Delay::new(delay).await;
let _ = sender.send(CubeFinished {
transform: Transform::from_xyz(x as f32, 0.5, z as f32),
});
})
.detach();
}
}
}
/// Handles the completion of async tasks and spawns ECS entities (cubes)
/// based on the received data. The function reads from the `CubeChannel`'s
/// receiver to get the results (cube positions) and spawns cubes accordingly.
fn handle_finished_cubes(
mut commands: Commands,
channel: Res<CubeChannel>,
box_mesh: Res<BoxMeshHandle>,
box_material: Res<BoxMaterialHandle>,
) {
for msg in channel.receiver.try_iter() {
// Spawn cube entity
commands.spawn((
Mesh3d(box_mesh.clone()),
MeshMaterial3d(box_material.clone()),
msg.transform,
));
}
}
/// Sets up a communication channel (`CubeChannel`) to send data between
/// async tasks and the main ECS thread. The sender is used by async tasks
/// to send the result (cube position), while the receiver is used by the
/// main thread to retrieve and process the completed data.
fn setup_channel(mut commands: Commands) {
let (sender, receiver) = crossbeam_channel::unbounded();
commands.insert_resource(CubeChannel { sender, receiver });
}
/// A channel for communicating between async tasks and the main thread.
#[derive(Resource)]
struct CubeChannel {
sender: Sender<CubeFinished>,
receiver: Receiver<CubeFinished>,
}
/// Represents the completion of a cube task, containing the cube's transform
#[derive(Debug)]
struct CubeFinished {
transform: Transform,
}
/// Resource holding the mesh handle for the box (used for spawning cubes)
#[derive(Resource, Deref)]
struct BoxMeshHandle(Handle<Mesh>);
/// Resource holding the material handle for the box (used for spawning cubes)
#[derive(Resource, Deref)]
struct BoxMaterialHandle(Handle<StandardMaterial>);
/// Sets up the shared mesh and material for the cubes.
fn setup_assets(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Create and store a cube mesh
let box_mesh_handle = meshes.add(Cuboid::new(0.4, 0.4, 0.4));
commands.insert_resource(BoxMeshHandle(box_mesh_handle));
// Create and store a red material
let box_material_handle = materials.add(Color::srgb(1.0, 0.2, 0.3));
commands.insert_resource(BoxMaterialHandle(box_material_handle));
}
/// Sets up the environment by spawning the ground, light, and camera.
fn setup_env(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Spawn a circular ground plane
commands.spawn((
Mesh3d(meshes.add(Circle::new(1.618 * NUM_CUBES as f32))),
MeshMaterial3d(materials.add(Color::WHITE)),
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
));
// Spawn a point light with shadows enabled
commands.spawn((
PointLight {
shadow_maps_enabled: true,
..default()
},
Transform::from_xyz(0.0, LIGHT_RADIUS, 4.0),
));
// Spawn a camera looking at the origin
commands.spawn((
Camera3d::default(),
Transform::from_xyz(-6.5, 5.5, 12.0).looking_at(Vec3::ZERO, Vec3::Y),
));
}
/// Rotates the point light around the origin (0, 0, 0)
fn rotate_light(mut query: Query<&mut Transform, With<PointLight>>, time: Res<Time>) {
for mut transform in query.iter_mut() {
let angle = 1.618 * time.elapsed_secs();
let x = LIGHT_RADIUS * cos(angle);
let z = LIGHT_RADIUS * sin(angle);
// Update the light's position to rotate around the origin
transform.translation = Vec3::new(x, LIGHT_RADIUS, z);
}
}