//! This example shows how to use the ECS and the [`AsyncComputeTaskPool`] //! to spawn, poll, and complete tasks across systems and system ticks. use bevy::{ prelude::*, tasks::{block_on, AsyncComputeTaskPool, Task}, }; use futures_lite::future; use rand::Rng; use std::time::{Duration, Instant}; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, (setup_env, add_assets, spawn_tasks)) .add_systems(Update, handle_tasks) .run(); } // Number of cubes to spawn across the x, y, and z axis const NUM_CUBES: u32 = 6; #[derive(Resource, Deref)] struct BoxMeshHandle(Handle); #[derive(Resource, Deref)] struct BoxMaterialHandle(Handle); /// Startup system which runs only once and generates our Box Mesh /// and Box Material assets, adds them to their respective Asset /// Resources, and stores their handles as resources so we can access /// them later when we're ready to render our Boxes fn add_assets( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { let box_mesh_handle = meshes.add(Mesh::from(shape::Cube { size: 0.25 })); commands.insert_resource(BoxMeshHandle(box_mesh_handle)); let box_material_handle = materials.add(Color::rgb(1.0, 0.2, 0.3).into()); commands.insert_resource(BoxMaterialHandle(box_material_handle)); } #[derive(Component)] struct ComputeTransform(Task); /// This system generates tasks simulating computationally intensive /// work that potentially spans multiple frames/ticks. A separate /// system, [`handle_tasks`], will poll the spawned tasks on subsequent /// frames/ticks, and use the results to spawn cubes fn spawn_tasks(mut commands: Commands) { let thread_pool = AsyncComputeTaskPool::get(); for x in 0..NUM_CUBES { for y in 0..NUM_CUBES { for z in 0..NUM_CUBES { // Spawn new task on the AsyncComputeTaskPool; the task will be // executed in the background, and the Task future returned by // spawn() can be used to poll for the result let task = thread_pool.spawn(async move { let mut rng = rand::thread_rng(); let start_time = Instant::now(); let duration = Duration::from_secs_f32(rng.gen_range(0.05..0.2)); while start_time.elapsed() < duration { // Spinning for 'duration', simulating doing hard // compute work generating translation coords! } // Such hard work, all done! Transform::from_xyz(x as f32, y as f32, z as f32) }); // Spawn new entity and add our new task as a component commands.spawn(ComputeTransform(task)); } } } } /// This system queries for entities that have our Task component. It polls the /// tasks to see if they're complete. If the task is complete it takes the result, adds a /// new [`PbrBundle`] of components to the entity using the result from the task's work, and /// removes the task component from the entity. fn handle_tasks( mut commands: Commands, mut transform_tasks: Query<(Entity, &mut ComputeTransform)>, box_mesh_handle: Res, box_material_handle: Res, ) { for (entity, mut task) in &mut transform_tasks { if let Some(transform) = block_on(future::poll_once(&mut task.0)) { // Add our new PbrBundle of components to our tagged entity commands.entity(entity).insert(PbrBundle { mesh: box_mesh_handle.clone(), material: box_material_handle.clone(), transform, ..default() }); // Task is complete, so remove task component from entity commands.entity(entity).remove::(); } } } /// This system is only used to setup light and camera for the environment fn setup_env(mut commands: Commands) { // Used to center camera on spawned cubes let offset = if NUM_CUBES % 2 == 0 { (NUM_CUBES / 2) as f32 - 0.5 } else { (NUM_CUBES / 2) as f32 }; // lights commands.spawn(PointLightBundle { transform: Transform::from_xyz(4.0, 12.0, 15.0), ..default() }); // camera commands.spawn(Camera3dBundle { transform: Transform::from_xyz(offset, offset, 15.0) .looking_at(Vec3::new(offset, offset, 0.0), Vec3::Y), ..default() }); }