mirror of
https://github.com/bevyengine/bevy.git
synced 2026-05-06 06:06:42 -04:00
f255b8e57a
# Objective - `glam`, `hexasphere` & `rand` have released their latest versions, update Bevy to support them. ## Solution - The above have been updated to their compatible versions. `rand_distr` updated as well to match `rand` v0.10 support. - `rand_chacha` is soft deprecated and no longer used by `rand`, so its usage has been changed to `chacha20` to match `rand` dep tree. - `uuid` is in the process of updating to `getrandom` v0.4, which `rand` v0.10 supports. This PR remains in draft until a new `uuid` release hits crates.io. - `RngCore` is now `Rng`, and `Rng` is now `RngExt`, so this required updating across many files. - `choose_multiple` method is deprecated, changed to `sample`. ## Testing - Chase all compiler errors, since this should not regress any already existing behaviour. - This must pass CI without regressions. ## Additional Notes `getrandom` v0.4 doesn't add anything new for Web WASM support, so the same `wasm_js` feature is used.
170 lines
5.8 KiB
Rust
170 lines
5.8 KiB
Rust
//! 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::RngExt;
|
|
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);
|
|
}
|
|
}
|