bevy/examples/async_tasks/async_compute.rs
Joona Aalto 0166db33f7
Deprecate shapes in bevy_render::mesh::shape (#11773)
# Objective

#11431 and #11688 implemented meshing support for Bevy's new geometric
primitives. The next step is to deprecate the shapes in
`bevy_render::mesh::shape` and to later remove them completely for 0.14.

## Solution

Deprecate the shapes and reduce code duplication by utilizing the
primitive meshing API for the old shapes where possible.

Note that some shapes have behavior that can't be exactly reproduced
with the new primitives yet:

- `Box` is more of an AABB with min/max extents
- `Plane` supports a subdivision count
- `Quad` has a `flipped` property

These types have not been changed to utilize the new primitives yet.

---

## Changelog

- Deprecated all shapes in `bevy_render::mesh::shape`
- Changed all examples to use new primitives for meshing

## Migration Guide

Bevy has previously used rendering-specific types like `UVSphere` and
`Quad` for primitive mesh shapes. These have now been deprecated to use
the geometric primitives newly introduced in version 0.13.

Some examples:

```rust
let before = meshes.add(shape::Box::new(5.0, 0.15, 5.0));
let after = meshes.add(Cuboid::new(5.0, 0.15, 5.0));

let before = meshes.add(shape::Quad::default());
let after = meshes.add(Rectangle::default());

let before = meshes.add(shape::Plane::from_size(5.0));
// The surface normal can now also be specified when using `new`
let after = meshes.add(Plane3d::default().mesh().size(5.0, 5.0));

let before = meshes.add(
    Mesh::try_from(shape::Icosphere {
        radius: 0.5,
        subdivisions: 5,
    })
    .unwrap(),
);
let after = meshes.add(Sphere::new(0.5).mesh().ico(5).unwrap());
```
2024-02-08 18:01:34 +00:00

146 lines
5.7 KiB
Rust

//! This example shows how to use the ECS and the [`AsyncComputeTaskPool`]
//! to spawn, poll, and complete tasks across systems and system ticks.
use bevy::{
ecs::system::{CommandQueue, SystemState},
prelude::*,
tasks::{block_on, futures_lite::future, AsyncComputeTaskPool, Task},
};
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<Mesh>);
#[derive(Resource, Deref)]
struct BoxMaterialHandle(Handle<StandardMaterial>);
/// 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<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let box_mesh_handle = meshes.add(Cuboid::new(0.25, 0.25, 0.25));
commands.insert_resource(BoxMeshHandle(box_mesh_handle));
let box_material_handle = materials.add(Color::rgb(1.0, 0.2, 0.3));
commands.insert_resource(BoxMaterialHandle(box_material_handle));
}
#[derive(Component)]
struct ComputeTransform(Task<CommandQueue>);
/// 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 entity = commands.spawn_empty().id();
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!
let transform = Transform::from_xyz(x as f32, y as f32, z as f32);
let mut command_queue = CommandQueue::default();
// we use a raw command queue to pass a FnOne(&mut World) back to be
// applied in a deferred manner.
command_queue.push(move |world: &mut World| {
let (box_mesh_handle, box_material_handle) = {
let mut system_state = SystemState::<(
Res<BoxMeshHandle>,
Res<BoxMaterialHandle>,
)>::new(world);
let (box_mesh_handle, box_material_handle) =
system_state.get_mut(world);
(box_mesh_handle.clone(), box_material_handle.clone())
};
world
.entity_mut(entity)
// Add our new PbrBundle of components to our tagged entity
.insert(PbrBundle {
mesh: box_mesh_handle,
material: box_material_handle,
transform,
..default()
})
// Task is complete, so remove task component from entity
.remove::<ComputeTransform>();
});
command_queue
});
// Spawn new entity and add our new task as a component
commands.entity(entity).insert(ComputeTransform(task));
}
}
}
}
/// This system queries for entities that have our Task<Transform> 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<&mut ComputeTransform>) {
for mut task in &mut transform_tasks {
if let Some(mut commands_queue) = block_on(future::poll_once(&mut task.0)) {
// append the returned command queue to have it execute later
commands.append(&mut commands_queue);
}
}
}
/// 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()
});
}