Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/async_tasks/async_channel_pattern.rs
9348 views
1
//! A minimal example showing how to perform asynchronous work in Bevy
2
//! using [`AsyncComputeTaskPool`] for parallel task execution and a crossbeam channel
3
//! to communicate between async tasks and the main ECS thread.
4
//!
5
//! This example demonstrates how to spawn detached async tasks, send completion messages via channels,
6
//! and dynamically spawn ECS entities (cubes) as results from these tasks. The system processes
7
//! async task results in the main game loop, all without blocking or polling the main thread.
8
9
use bevy::{
10
math::ops::{cos, sin},
11
prelude::*,
12
tasks::AsyncComputeTaskPool,
13
};
14
use crossbeam_channel::{Receiver, Sender};
15
use futures_timer::Delay;
16
use rand::Rng;
17
use std::time::Duration;
18
19
const NUM_CUBES: i32 = 6;
20
const LIGHT_RADIUS: f32 = 8.0;
21
22
fn main() {
23
App::new()
24
.add_plugins(DefaultPlugins)
25
.add_systems(
26
Startup,
27
(
28
setup_env,
29
setup_assets,
30
setup_channel,
31
// Ensure the channel is set up before spawning tasks.
32
spawn_tasks.after(setup_channel),
33
),
34
)
35
.add_systems(Update, (handle_finished_cubes, rotate_light))
36
.run();
37
}
38
39
/// Spawns async tasks on the compute task pool to simulate delayed cube creation.
40
///
41
/// Each task is executed on a separate thread and sends the result (cube position)
42
/// back through the `CubeChannel` once completed. The tasks are detached to
43
/// run asynchronously without blocking the main thread.
44
///
45
/// In this example, we don't implement task tracking or proper error handling.
46
fn spawn_tasks(channel: Res<CubeChannel>) {
47
let pool = AsyncComputeTaskPool::get();
48
49
for x in -NUM_CUBES..NUM_CUBES {
50
for z in -NUM_CUBES..NUM_CUBES {
51
let sender = channel.sender.clone();
52
// Spawn a task on the async compute pool
53
pool.spawn(async move {
54
let delay = Duration::from_secs_f32(rand::rng().random_range(2.0..8.0));
55
// Simulate a delay before task completion
56
Delay::new(delay).await;
57
let _ = sender.send(CubeFinished {
58
transform: Transform::from_xyz(x as f32, 0.5, z as f32),
59
});
60
})
61
.detach();
62
}
63
}
64
}
65
66
/// Handles the completion of async tasks and spawns ECS entities (cubes)
67
/// based on the received data. The function reads from the `CubeChannel`'s
68
/// receiver to get the results (cube positions) and spawns cubes accordingly.
69
fn handle_finished_cubes(
70
mut commands: Commands,
71
channel: Res<CubeChannel>,
72
box_mesh: Res<BoxMeshHandle>,
73
box_material: Res<BoxMaterialHandle>,
74
) {
75
for msg in channel.receiver.try_iter() {
76
// Spawn cube entity
77
commands.spawn((
78
Mesh3d(box_mesh.clone()),
79
MeshMaterial3d(box_material.clone()),
80
msg.transform,
81
));
82
}
83
}
84
85
/// Sets up a communication channel (`CubeChannel`) to send data between
86
/// async tasks and the main ECS thread. The sender is used by async tasks
87
/// to send the result (cube position), while the receiver is used by the
88
/// main thread to retrieve and process the completed data.
89
fn setup_channel(mut commands: Commands) {
90
let (sender, receiver) = crossbeam_channel::unbounded();
91
commands.insert_resource(CubeChannel { sender, receiver });
92
}
93
94
/// A channel for communicating between async tasks and the main thread.
95
#[derive(Resource)]
96
struct CubeChannel {
97
sender: Sender<CubeFinished>,
98
receiver: Receiver<CubeFinished>,
99
}
100
101
/// Represents the completion of a cube task, containing the cube's transform
102
#[derive(Debug)]
103
struct CubeFinished {
104
transform: Transform,
105
}
106
107
/// Resource holding the mesh handle for the box (used for spawning cubes)
108
#[derive(Resource, Deref)]
109
struct BoxMeshHandle(Handle<Mesh>);
110
111
/// Resource holding the material handle for the box (used for spawning cubes)
112
#[derive(Resource, Deref)]
113
struct BoxMaterialHandle(Handle<StandardMaterial>);
114
115
/// Sets up the shared mesh and material for the cubes.
116
fn setup_assets(
117
mut commands: Commands,
118
mut meshes: ResMut<Assets<Mesh>>,
119
mut materials: ResMut<Assets<StandardMaterial>>,
120
) {
121
// Create and store a cube mesh
122
let box_mesh_handle = meshes.add(Cuboid::new(0.4, 0.4, 0.4));
123
commands.insert_resource(BoxMeshHandle(box_mesh_handle));
124
125
// Create and store a red material
126
let box_material_handle = materials.add(Color::srgb(1.0, 0.2, 0.3));
127
commands.insert_resource(BoxMaterialHandle(box_material_handle));
128
}
129
130
/// Sets up the environment by spawning the ground, light, and camera.
131
fn setup_env(
132
mut commands: Commands,
133
mut meshes: ResMut<Assets<Mesh>>,
134
mut materials: ResMut<Assets<StandardMaterial>>,
135
) {
136
// Spawn a circular ground plane
137
commands.spawn((
138
Mesh3d(meshes.add(Circle::new(1.618 * NUM_CUBES as f32))),
139
MeshMaterial3d(materials.add(Color::WHITE)),
140
Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
141
));
142
143
// Spawn a point light with shadows enabled
144
commands.spawn((
145
PointLight {
146
shadow_maps_enabled: true,
147
..default()
148
},
149
Transform::from_xyz(0.0, LIGHT_RADIUS, 4.0),
150
));
151
152
// Spawn a camera looking at the origin
153
commands.spawn((
154
Camera3d::default(),
155
Transform::from_xyz(-6.5, 5.5, 12.0).looking_at(Vec3::ZERO, Vec3::Y),
156
));
157
}
158
159
/// Rotates the point light around the origin (0, 0, 0)
160
fn rotate_light(mut query: Query<&mut Transform, With<PointLight>>, time: Res<Time>) {
161
for mut transform in query.iter_mut() {
162
let angle = 1.618 * time.elapsed_secs();
163
let x = LIGHT_RADIUS * cos(angle);
164
let z = LIGHT_RADIUS * sin(angle);
165
166
// Update the light's position to rotate around the origin
167
transform.translation = Vec3::new(x, LIGHT_RADIUS, z);
168
}
169
}
170
171