//! 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) { 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, box_mesh: Res, box_material: Res, ) { 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, receiver: Receiver, } /// 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); /// Resource holding the material handle for the box (used for spawning cubes) #[derive(Resource, Deref)] struct BoxMaterialHandle(Handle); /// Sets up the shared mesh and material for the cubes. fn setup_assets( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { // 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>, mut materials: ResMut>, ) { // 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>, time: Res