Path: blob/main/examples/async_tasks/async_channel_pattern.rs
9348 views
//! A minimal example showing how to perform asynchronous work in Bevy1//! using [`AsyncComputeTaskPool`] for parallel task execution and a crossbeam channel2//! to communicate between async tasks and the main ECS thread.3//!4//! This example demonstrates how to spawn detached async tasks, send completion messages via channels,5//! and dynamically spawn ECS entities (cubes) as results from these tasks. The system processes6//! async task results in the main game loop, all without blocking or polling the main thread.78use bevy::{9math::ops::{cos, sin},10prelude::*,11tasks::AsyncComputeTaskPool,12};13use crossbeam_channel::{Receiver, Sender};14use futures_timer::Delay;15use rand::Rng;16use std::time::Duration;1718const NUM_CUBES: i32 = 6;19const LIGHT_RADIUS: f32 = 8.0;2021fn main() {22App::new()23.add_plugins(DefaultPlugins)24.add_systems(25Startup,26(27setup_env,28setup_assets,29setup_channel,30// Ensure the channel is set up before spawning tasks.31spawn_tasks.after(setup_channel),32),33)34.add_systems(Update, (handle_finished_cubes, rotate_light))35.run();36}3738/// Spawns async tasks on the compute task pool to simulate delayed cube creation.39///40/// Each task is executed on a separate thread and sends the result (cube position)41/// back through the `CubeChannel` once completed. The tasks are detached to42/// run asynchronously without blocking the main thread.43///44/// In this example, we don't implement task tracking or proper error handling.45fn spawn_tasks(channel: Res<CubeChannel>) {46let pool = AsyncComputeTaskPool::get();4748for x in -NUM_CUBES..NUM_CUBES {49for z in -NUM_CUBES..NUM_CUBES {50let sender = channel.sender.clone();51// Spawn a task on the async compute pool52pool.spawn(async move {53let delay = Duration::from_secs_f32(rand::rng().random_range(2.0..8.0));54// Simulate a delay before task completion55Delay::new(delay).await;56let _ = sender.send(CubeFinished {57transform: Transform::from_xyz(x as f32, 0.5, z as f32),58});59})60.detach();61}62}63}6465/// Handles the completion of async tasks and spawns ECS entities (cubes)66/// based on the received data. The function reads from the `CubeChannel`'s67/// receiver to get the results (cube positions) and spawns cubes accordingly.68fn handle_finished_cubes(69mut commands: Commands,70channel: Res<CubeChannel>,71box_mesh: Res<BoxMeshHandle>,72box_material: Res<BoxMaterialHandle>,73) {74for msg in channel.receiver.try_iter() {75// Spawn cube entity76commands.spawn((77Mesh3d(box_mesh.clone()),78MeshMaterial3d(box_material.clone()),79msg.transform,80));81}82}8384/// Sets up a communication channel (`CubeChannel`) to send data between85/// async tasks and the main ECS thread. The sender is used by async tasks86/// to send the result (cube position), while the receiver is used by the87/// main thread to retrieve and process the completed data.88fn setup_channel(mut commands: Commands) {89let (sender, receiver) = crossbeam_channel::unbounded();90commands.insert_resource(CubeChannel { sender, receiver });91}9293/// A channel for communicating between async tasks and the main thread.94#[derive(Resource)]95struct CubeChannel {96sender: Sender<CubeFinished>,97receiver: Receiver<CubeFinished>,98}99100/// Represents the completion of a cube task, containing the cube's transform101#[derive(Debug)]102struct CubeFinished {103transform: Transform,104}105106/// Resource holding the mesh handle for the box (used for spawning cubes)107#[derive(Resource, Deref)]108struct BoxMeshHandle(Handle<Mesh>);109110/// Resource holding the material handle for the box (used for spawning cubes)111#[derive(Resource, Deref)]112struct BoxMaterialHandle(Handle<StandardMaterial>);113114/// Sets up the shared mesh and material for the cubes.115fn setup_assets(116mut commands: Commands,117mut meshes: ResMut<Assets<Mesh>>,118mut materials: ResMut<Assets<StandardMaterial>>,119) {120// Create and store a cube mesh121let box_mesh_handle = meshes.add(Cuboid::new(0.4, 0.4, 0.4));122commands.insert_resource(BoxMeshHandle(box_mesh_handle));123124// Create and store a red material125let box_material_handle = materials.add(Color::srgb(1.0, 0.2, 0.3));126commands.insert_resource(BoxMaterialHandle(box_material_handle));127}128129/// Sets up the environment by spawning the ground, light, and camera.130fn setup_env(131mut commands: Commands,132mut meshes: ResMut<Assets<Mesh>>,133mut materials: ResMut<Assets<StandardMaterial>>,134) {135// Spawn a circular ground plane136commands.spawn((137Mesh3d(meshes.add(Circle::new(1.618 * NUM_CUBES as f32))),138MeshMaterial3d(materials.add(Color::WHITE)),139Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),140));141142// Spawn a point light with shadows enabled143commands.spawn((144PointLight {145shadow_maps_enabled: true,146..default()147},148Transform::from_xyz(0.0, LIGHT_RADIUS, 4.0),149));150151// Spawn a camera looking at the origin152commands.spawn((153Camera3d::default(),154Transform::from_xyz(-6.5, 5.5, 12.0).looking_at(Vec3::ZERO, Vec3::Y),155));156}157158/// Rotates the point light around the origin (0, 0, 0)159fn rotate_light(mut query: Query<&mut Transform, With<PointLight>>, time: Res<Time>) {160for mut transform in query.iter_mut() {161let angle = 1.618 * time.elapsed_secs();162let x = LIGHT_RADIUS * cos(angle);163let z = LIGHT_RADIUS * sin(angle);164165// Update the light's position to rotate around the origin166transform.translation = Vec3::new(x, LIGHT_RADIUS, z);167}168}169170171