//! This example demonstrates how to use Bevy's ECS and the [`AsyncComputeTaskPool`]1//! to offload computationally intensive tasks to a background thread pool, process them2//! asynchronously, and apply the results across systems and ticks.3//!4//! Unlike the channel-based approach (where tasks send results directly via a communication5//! channel), this example uses the `AsyncComputeTaskPool` to run tasks in the background,6//! check for their completion, and handle results when the task is ready. This method allows7//! tasks to be processed in parallel without blocking the main thread, but requires periodically8//! checking the status of each task.9//!10//! The channel-based approach, on the other hand, detaches tasks and communicates results11//! through a channel, avoiding the need to check task statuses manually.1213use bevy::{14ecs::{system::SystemState, world::CommandQueue},15prelude::*,16tasks::{futures::check_ready, AsyncComputeTaskPool, Task},17};18use futures_timer::Delay;19use rand::Rng;20use std::time::Duration;2122fn main() {23App::new()24.add_plugins(DefaultPlugins)25.add_systems(Startup, (setup_env, add_assets, spawn_tasks))26.add_systems(Update, handle_tasks)27.run();28}2930// Number of cubes to spawn across the x, y, and z axis31const NUM_CUBES: u32 = 6;3233#[derive(Resource, Deref)]34struct BoxMeshHandle(Handle<Mesh>);3536#[derive(Resource, Deref)]37struct BoxMaterialHandle(Handle<StandardMaterial>);3839/// Startup system which runs only once and generates our Box Mesh40/// and Box Material assets, adds them to their respective Asset41/// Resources, and stores their handles as resources so we can access42/// them later when we're ready to render our Boxes43fn add_assets(44mut commands: Commands,45mut meshes: ResMut<Assets<Mesh>>,46mut materials: ResMut<Assets<StandardMaterial>>,47) {48let box_mesh_handle = meshes.add(Cuboid::new(0.25, 0.25, 0.25));49commands.insert_resource(BoxMeshHandle(box_mesh_handle));5051let box_material_handle = materials.add(Color::srgb(1.0, 0.2, 0.3));52commands.insert_resource(BoxMaterialHandle(box_material_handle));53}5455#[derive(Component)]56struct ComputeTransform(Task<CommandQueue>);5758/// This system generates tasks simulating computationally intensive59/// work that potentially spans multiple frames/ticks. A separate60/// system, [`handle_tasks`], will track the spawned tasks on subsequent61/// frames/ticks, and use the results to spawn cubes.62///63/// The task is offloaded to the `AsyncComputeTaskPool`, allowing heavy computation64/// to be handled asynchronously, without blocking the main game thread.65fn spawn_tasks(mut commands: Commands) {66let thread_pool = AsyncComputeTaskPool::get();67for x in 0..NUM_CUBES {68for y in 0..NUM_CUBES {69for z in 0..NUM_CUBES {70// Spawn new task on the AsyncComputeTaskPool; the task will be71// executed in the background, and the Task future returned by72// spawn() can be used to poll for the result73let entity = commands.spawn_empty().id();74let task = thread_pool.spawn(async move {75let duration = Duration::from_secs_f32(rand::rng().random_range(0.05..5.0));7677// Pretend this is a time-intensive function. :)78Delay::new(duration).await;7980// Such hard work, all done!81let transform = Transform::from_xyz(x as f32, y as f32, z as f32);82let mut command_queue = CommandQueue::default();8384// we use a raw command queue to pass a FnOnce(&mut World) back to be85// applied in a deferred manner.86command_queue.push(move |world: &mut World| {87let (box_mesh_handle, box_material_handle) = {88let mut system_state = SystemState::<(89Res<BoxMeshHandle>,90Res<BoxMaterialHandle>,91)>::new(world);92let (box_mesh_handle, box_material_handle) =93system_state.get_mut(world);9495(box_mesh_handle.clone(), box_material_handle.clone())96};9798world99.entity_mut(entity)100// Add our new `Mesh3d` and `MeshMaterial3d` to our tagged entity101.insert((102Mesh3d(box_mesh_handle),103MeshMaterial3d(box_material_handle),104transform,105));106});107108command_queue109});110111// Add our new task as a component112commands.entity(entity).insert(ComputeTransform(task));113}114}115}116}117118/// This system queries for entities that have the `ComputeTransform` component.119/// It checks if the tasks associated with those entities are complete.120/// If the task is complete, it extracts the result, adds a new [`Mesh3d`] and [`MeshMaterial3d`]121/// to the entity using the result from the task, and removes the task component from the entity.122///123/// **Important Note:**124/// - Don't use `future::block_on(poll_once)` to check if tasks are completed, as it is expensive and125/// can block the main thread. Also, it leaves around a `Task<T>` which will panic if awaited again.126/// - Instead, use `check_ready` for efficient polling, which does not block the main thread.127fn handle_tasks(128mut commands: Commands,129mut transform_tasks: Query<(Entity, &mut ComputeTransform)>,130) {131for (entity, mut task) in &mut transform_tasks {132// Use `check_ready` to efficiently poll the task without blocking the main thread.133if let Some(mut commands_queue) = check_ready(&mut task.0) {134// Append the returned command queue to execute it later.135commands.append(&mut commands_queue);136// Task is complete, so remove the task component from the entity.137commands.entity(entity).remove::<ComputeTransform>();138}139}140}141142/// This system is only used to setup light and camera for the environment143fn setup_env(mut commands: Commands) {144// Used to center camera on spawned cubes145let offset = if NUM_CUBES.is_multiple_of(2) {146(NUM_CUBES / 2) as f32 - 0.5147} else {148(NUM_CUBES / 2) as f32149};150151// lights152commands.spawn((PointLight::default(), Transform::from_xyz(4.0, 12.0, 15.0)));153154// camera155commands.spawn((156Camera3d::default(),157Transform::from_xyz(offset, offset, 15.0)158.looking_at(Vec3::new(offset, offset, 0.0), Vec3::Y),159));160}161162163