Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/async_tasks/async_compute.rs
9308 views
1
//! This example demonstrates how to use Bevy's ECS and the [`AsyncComputeTaskPool`]
2
//! to offload computationally intensive tasks to a background thread pool, process them
3
//! asynchronously, and apply the results across systems and ticks.
4
//!
5
//! Unlike the channel-based approach (where tasks send results directly via a communication
6
//! channel), this example uses the `AsyncComputeTaskPool` to run tasks in the background,
7
//! check for their completion, and handle results when the task is ready. This method allows
8
//! tasks to be processed in parallel without blocking the main thread, but requires periodically
9
//! checking the status of each task.
10
//!
11
//! The channel-based approach, on the other hand, detaches tasks and communicates results
12
//! through a channel, avoiding the need to check task statuses manually.
13
14
use bevy::{
15
ecs::{system::SystemState, world::CommandQueue},
16
prelude::*,
17
tasks::{futures::check_ready, AsyncComputeTaskPool, Task},
18
};
19
use futures_timer::Delay;
20
use rand::Rng;
21
use std::time::Duration;
22
23
fn main() {
24
App::new()
25
.add_plugins(DefaultPlugins)
26
.add_systems(Startup, (setup_env, add_assets, spawn_tasks))
27
.add_systems(Update, handle_tasks)
28
.run();
29
}
30
31
// Number of cubes to spawn across the x, y, and z axis
32
const NUM_CUBES: u32 = 6;
33
34
#[derive(Resource, Deref)]
35
struct BoxMeshHandle(Handle<Mesh>);
36
37
#[derive(Resource, Deref)]
38
struct BoxMaterialHandle(Handle<StandardMaterial>);
39
40
/// Startup system which runs only once and generates our Box Mesh
41
/// and Box Material assets, adds them to their respective Asset
42
/// Resources, and stores their handles as resources so we can access
43
/// them later when we're ready to render our Boxes
44
fn add_assets(
45
mut commands: Commands,
46
mut meshes: ResMut<Assets<Mesh>>,
47
mut materials: ResMut<Assets<StandardMaterial>>,
48
) {
49
let box_mesh_handle = meshes.add(Cuboid::new(0.25, 0.25, 0.25));
50
commands.insert_resource(BoxMeshHandle(box_mesh_handle));
51
52
let box_material_handle = materials.add(Color::srgb(1.0, 0.2, 0.3));
53
commands.insert_resource(BoxMaterialHandle(box_material_handle));
54
}
55
56
#[derive(Component)]
57
struct ComputeTransform(Task<CommandQueue>);
58
59
/// This system generates tasks simulating computationally intensive
60
/// work that potentially spans multiple frames/ticks. A separate
61
/// system, [`handle_tasks`], will track the spawned tasks on subsequent
62
/// frames/ticks, and use the results to spawn cubes.
63
///
64
/// The task is offloaded to the `AsyncComputeTaskPool`, allowing heavy computation
65
/// to be handled asynchronously, without blocking the main game thread.
66
fn spawn_tasks(mut commands: Commands) {
67
let thread_pool = AsyncComputeTaskPool::get();
68
for x in 0..NUM_CUBES {
69
for y in 0..NUM_CUBES {
70
for z in 0..NUM_CUBES {
71
// Spawn new task on the AsyncComputeTaskPool; the task will be
72
// executed in the background, and the Task future returned by
73
// spawn() can be used to poll for the result
74
let entity = commands.spawn_empty().id();
75
let task = thread_pool.spawn(async move {
76
let duration = Duration::from_secs_f32(rand::rng().random_range(0.05..5.0));
77
78
// Pretend this is a time-intensive function. :)
79
Delay::new(duration).await;
80
81
// Such hard work, all done!
82
let transform = Transform::from_xyz(x as f32, y as f32, z as f32);
83
let mut command_queue = CommandQueue::default();
84
85
// we use a raw command queue to pass a FnOnce(&mut World) back to be
86
// applied in a deferred manner.
87
command_queue.push(move |world: &mut World| {
88
let (box_mesh_handle, box_material_handle) = {
89
let mut system_state = SystemState::<(
90
Res<BoxMeshHandle>,
91
Res<BoxMaterialHandle>,
92
)>::new(world);
93
let (box_mesh_handle, box_material_handle) =
94
system_state.get_mut(world);
95
96
(box_mesh_handle.clone(), box_material_handle.clone())
97
};
98
99
world
100
.entity_mut(entity)
101
// Add our new `Mesh3d` and `MeshMaterial3d` to our tagged entity
102
.insert((
103
Mesh3d(box_mesh_handle),
104
MeshMaterial3d(box_material_handle),
105
transform,
106
));
107
});
108
109
command_queue
110
});
111
112
// Add our new task as a component
113
commands.entity(entity).insert(ComputeTransform(task));
114
}
115
}
116
}
117
}
118
119
/// This system queries for entities that have the `ComputeTransform` component.
120
/// It checks if the tasks associated with those entities are complete.
121
/// If the task is complete, it extracts the result, adds a new [`Mesh3d`] and [`MeshMaterial3d`]
122
/// to the entity using the result from the task, and removes the task component from the entity.
123
///
124
/// **Important Note:**
125
/// - Don't use `future::block_on(poll_once)` to check if tasks are completed, as it is expensive and
126
/// can block the main thread. Also, it leaves around a `Task<T>` which will panic if awaited again.
127
/// - Instead, use `check_ready` for efficient polling, which does not block the main thread.
128
fn handle_tasks(
129
mut commands: Commands,
130
mut transform_tasks: Query<(Entity, &mut ComputeTransform)>,
131
) {
132
for (entity, mut task) in &mut transform_tasks {
133
// Use `check_ready` to efficiently poll the task without blocking the main thread.
134
if let Some(mut commands_queue) = check_ready(&mut task.0) {
135
// Append the returned command queue to execute it later.
136
commands.append(&mut commands_queue);
137
// Task is complete, so remove the task component from the entity.
138
commands.entity(entity).remove::<ComputeTransform>();
139
}
140
}
141
}
142
143
/// This system is only used to setup light and camera for the environment
144
fn setup_env(mut commands: Commands) {
145
// Used to center camera on spawned cubes
146
let offset = if NUM_CUBES.is_multiple_of(2) {
147
(NUM_CUBES / 2) as f32 - 0.5
148
} else {
149
(NUM_CUBES / 2) as f32
150
};
151
152
// lights
153
commands.spawn((PointLight::default(), Transform::from_xyz(4.0, 12.0, 15.0)));
154
155
// camera
156
commands.spawn((
157
Camera3d::default(),
158
Transform::from_xyz(offset, offset, 15.0)
159
.looking_at(Vec3::new(offset, offset, 0.0), Vec3::Y),
160
));
161
}
162
163