forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathasync_channel_pattern.rs
More file actions
169 lines (151 loc) · 5.79 KB
/
async_channel_pattern.rs
File metadata and controls
169 lines (151 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! 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::Rng;
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<CubeChannel>) {
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<CubeChannel>,
box_mesh: Res<BoxMeshHandle>,
box_material: Res<BoxMaterialHandle>,
) {
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<CubeFinished>,
receiver: Receiver<CubeFinished>,
}
/// 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<Mesh>);
/// Resource holding the material handle for the box (used for spawning cubes)
#[derive(Resource, Deref)]
struct BoxMaterialHandle(Handle<StandardMaterial>);
/// Sets up the shared mesh and material for the cubes.
fn setup_assets(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 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<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// 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<PointLight>>, time: Res<Time>) {
for mut transform in query.iter_mut() {
let angle = 1.618 * time.elapsed_secs();
let x = LIGHT_RADIUS * cos(angle);
let z = LIGHT_RADIUS * sin(angle);
// Update the light's position to rotate around the origin
transform.translation = Vec3::new(x, LIGHT_RADIUS, z);
}
}