mirror of
https://github.com/bevyengine/bevy
synced 2024-09-20 06:22:01 +00:00
Merge branch 'main' into ui-stack-system-walk
This commit is contained in:
commit
eae57fef91
402 changed files with 13986 additions and 6614 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -219,7 +219,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.24.3
|
||||
uses: crate-ci/typos@v1.24.5
|
||||
- name: Typos info
|
||||
if: failure()
|
||||
run: |
|
||||
|
|
2
.github/workflows/post-release.yml
vendored
2
.github/workflows/post-release.yml
vendored
|
@ -49,7 +49,7 @@ jobs:
|
|||
--exclude build-wasm-example
|
||||
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
delete-branch: true
|
||||
base: "main"
|
||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -46,7 +46,7 @@ jobs:
|
|||
--exclude build-wasm-example
|
||||
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
delete-branch: true
|
||||
base: "main"
|
||||
|
|
2
.github/workflows/welcome.yml
vendored
2
.github/workflows/welcome.yml
vendored
|
@ -11,6 +11,8 @@ on:
|
|||
jobs:
|
||||
welcome:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -6,6 +6,7 @@ Cargo.lock
|
|||
.cargo/config.toml
|
||||
/.idea
|
||||
/.vscode
|
||||
.zed
|
||||
/benches/target
|
||||
/tools/compile_fail_utils/target
|
||||
dxcompiler.dll
|
||||
|
|
35
Cargo.toml
35
Cargo.toml
|
@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
|
|||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
documentation = "https://docs.rs/bevy"
|
||||
rust-version = "1.79.0"
|
||||
rust-version = "1.80.0"
|
||||
|
||||
[workspace]
|
||||
exclude = [
|
||||
|
@ -3244,6 +3244,28 @@ description = "A first-person camera that uses a world model and a view model wi
|
|||
category = "Camera"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "projection_zoom"
|
||||
path = "examples/camera/projection_zoom.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.projection_zoom]
|
||||
name = "Projection Zoom"
|
||||
description = "Shows how to zoom orthographic and perspective projection cameras."
|
||||
category = "Camera"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "camera_orbit"
|
||||
path = "examples/camera/camera_orbit.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.camera_orbit]
|
||||
name = "Camera Orbit"
|
||||
description = "Shows how to orbit a static scene using pitch, yaw, and roll."
|
||||
category = "Camera"
|
||||
wasm = true
|
||||
|
||||
[package.metadata.example.fps_overlay]
|
||||
name = "FPS overlay"
|
||||
description = "Demonstrates FPS overlay"
|
||||
|
@ -3430,6 +3452,17 @@ description = "Demonstrates animation masks"
|
|||
category = "Animation"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "pcss"
|
||||
path = "examples/3d/pcss.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.pcss]
|
||||
name = "Percentage-closer soft shadows"
|
||||
description = "Demonstrates percentage-closer soft shadows (PCSS)"
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
|
|
BIN
assets/environment_maps/sky_skybox.ktx2
Normal file
BIN
assets/environment_maps/sky_skybox.ktx2
Normal file
Binary file not shown.
BIN
assets/models/PalmTree/PalmTree.bin
Normal file
BIN
assets/models/PalmTree/PalmTree.bin
Normal file
Binary file not shown.
1066
assets/models/PalmTree/PalmTree.gltf
Normal file
1066
assets/models/PalmTree/PalmTree.gltf
Normal file
File diff suppressed because it is too large
Load diff
BIN
assets/models/PalmTree/StylizedWater.png
Normal file
BIN
assets/models/PalmTree/StylizedWater.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 540 KiB |
|
@ -5,10 +5,21 @@
|
|||
@group(1) @binding(1) var<uniform> slider: f32;
|
||||
@group(1) @binding(2) var material_color_texture: texture_2d<f32>;
|
||||
@group(1) @binding(3) var material_color_sampler: sampler;
|
||||
@group(1) @binding(4) var<uniform> border_color: vec4<f32>;
|
||||
|
||||
|
||||
@fragment
|
||||
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
|
||||
let r = in.uv - 0.5;
|
||||
let b = vec2(
|
||||
select(in.border_widths.x, in.border_widths.y, r.x < 0.),
|
||||
select(in.border_widths.z, in.border_widths.w, r.y < 0.)
|
||||
);
|
||||
|
||||
if any(0.5 - b < abs(r)) {
|
||||
return border_color;
|
||||
}
|
||||
|
||||
if in.uv.x < slider {
|
||||
let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color;
|
||||
return output_color;
|
||||
|
|
111
benches/benches/bevy_ecs/components/add_remove_very_big_table.rs
Normal file
111
benches/benches/bevy_ecs/components/add_remove_very_big_table.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use glam::*;
|
||||
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct A<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct B<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct C<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct D<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct E<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct F<const N: usize>(Mat4);
|
||||
#[derive(Component, Copy, Clone)]
|
||||
struct Z<const N: usize>;
|
||||
|
||||
pub struct Benchmark(World, Vec<Entity>);
|
||||
|
||||
impl Benchmark {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::default();
|
||||
let mut entities = Vec::with_capacity(10_000);
|
||||
for _ in 0..10_000 {
|
||||
entities.push(
|
||||
world
|
||||
.spawn((
|
||||
(
|
||||
A::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
A::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
),
|
||||
(
|
||||
A::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
A::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
),
|
||||
(
|
||||
A::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
A::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
),
|
||||
(
|
||||
A::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
B::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
C::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
D::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
E::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
Z::<1>,
|
||||
Z::<2>,
|
||||
Z::<3>,
|
||||
Z::<4>,
|
||||
Z::<5>,
|
||||
Z::<6>,
|
||||
Z::<7>,
|
||||
),
|
||||
))
|
||||
.id(),
|
||||
);
|
||||
}
|
||||
|
||||
Self(world, entities)
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
for entity in &self.1 {
|
||||
self.0.entity_mut(*entity).insert((
|
||||
F::<1>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<2>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<3>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<4>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<5>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<6>(Mat4::from_scale(Vec3::ONE)),
|
||||
F::<7>(Mat4::from_scale(Vec3::ONE)),
|
||||
));
|
||||
}
|
||||
|
||||
for entity in &self.1 {
|
||||
self.0
|
||||
.entity_mut(*entity)
|
||||
.remove::<(F<1>, F<2>, F<3>, F<4>, F<5>, F<6>, F<7>)>();
|
||||
self.0
|
||||
.entity_mut(*entity)
|
||||
.remove::<(Z<1>, Z<2>, Z<3>, Z<4>, Z<5>, Z<6>, Z<7>)>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ mod add_remove_big_sparse_set;
|
|||
mod add_remove_big_table;
|
||||
mod add_remove_sparse_set;
|
||||
mod add_remove_table;
|
||||
mod add_remove_very_big_table;
|
||||
mod archetype_updates;
|
||||
mod insert_simple;
|
||||
mod insert_simple_unbatched;
|
||||
|
@ -14,6 +15,7 @@ criterion_group!(
|
|||
components_benches,
|
||||
add_remove,
|
||||
add_remove_big,
|
||||
add_remove_very_big,
|
||||
insert_simple,
|
||||
no_archetypes,
|
||||
added_archetypes,
|
||||
|
@ -49,6 +51,17 @@ fn add_remove_big(c: &mut Criterion) {
|
|||
group.finish();
|
||||
}
|
||||
|
||||
fn add_remove_very_big(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("add_remove_very_big");
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
group.measurement_time(std::time::Duration::from_secs(4));
|
||||
group.bench_function("table", |b| {
|
||||
let mut bench = add_remove_very_big_table::Benchmark::new();
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn insert_simple(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("insert_simple");
|
||||
group.warm_up_time(std::time::Duration::from_millis(500));
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::mem::size_of;
|
||||
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
|
@ -146,9 +144,9 @@ pub fn fake_commands(criterion: &mut Criterion) {
|
|||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
for i in 0..command_count {
|
||||
if black_box(i % 2 == 0) {
|
||||
commands.add(FakeCommandA);
|
||||
commands.queue(FakeCommandA);
|
||||
} else {
|
||||
commands.add(FakeCommandB(0));
|
||||
commands.queue(FakeCommandB(0));
|
||||
}
|
||||
}
|
||||
command_queue.apply(&mut world);
|
||||
|
@ -190,7 +188,7 @@ pub fn sized_commands_impl<T: Default + Command>(criterion: &mut Criterion) {
|
|||
bencher.iter(|| {
|
||||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
for _ in 0..command_count {
|
||||
commands.add(T::default());
|
||||
commands.queue(T::default());
|
||||
}
|
||||
command_queue.apply(&mut world);
|
||||
});
|
||||
|
|
32
clippy.toml
32
clippy.toml
|
@ -10,3 +10,35 @@ doc-valid-idents = [
|
|||
"WebGPU",
|
||||
"..",
|
||||
]
|
||||
|
||||
check-private-items = true
|
||||
|
||||
disallowed-methods = [
|
||||
{ path = "f32::powi", reason = "use bevy_math::ops::FloatPow::squared, bevy_math::ops::FloatPow::cubed, or bevy_math::ops::powf instead for libm determinism" },
|
||||
{ path = "f32::log", reason = "use bevy_math::ops::ln, bevy_math::ops::log2, or bevy_math::ops::log10 instead for libm determinism" },
|
||||
{ path = "f32::abs_sub", reason = "deprecated and deeply confusing method" },
|
||||
{ path = "f32::powf", reason = "use bevy_math::ops::powf instead for libm determinism" },
|
||||
{ path = "f32::exp", reason = "use bevy_math::ops::exp instead for libm determinism" },
|
||||
{ path = "f32::exp2", reason = "use bevy_math::ops::exp2 instead for libm determinism" },
|
||||
{ path = "f32::ln", reason = "use bevy_math::ops::ln instead for libm determinism" },
|
||||
{ path = "f32::log2", reason = "use bevy_math::ops::log2 instead for libm determinism" },
|
||||
{ path = "f32::log10", reason = "use bevy_math::ops::log10 instead for libm determinism" },
|
||||
{ path = "f32::cbrt", reason = "use bevy_math::ops::cbrt instead for libm determinism" },
|
||||
{ path = "f32::hypot", reason = "use bevy_math::ops::hypot instead for libm determinism" },
|
||||
{ path = "f32::sin", reason = "use bevy_math::ops::sin instead for libm determinism" },
|
||||
{ path = "f32::cos", reason = "use bevy_math::ops::cos instead for libm determinism" },
|
||||
{ path = "f32::tan", reason = "use bevy_math::ops::tan instead for libm determinism" },
|
||||
{ path = "f32::asin", reason = "use bevy_math::ops::asin instead for libm determinism" },
|
||||
{ path = "f32::acos", reason = "use bevy_math::ops::acos instead for libm determinism" },
|
||||
{ path = "f32::atan", reason = "use bevy_math::ops::atan instead for libm determinism" },
|
||||
{ path = "f32::atan2", reason = "use bevy_math::ops::atan2 instead for libm determinism" },
|
||||
{ path = "f32::sin_cos", reason = "use bevy_math::ops::sin_cos instead for libm determinism" },
|
||||
{ path = "f32::exp_m1", reason = "use bevy_math::ops::exp_m1 instead for libm determinism" },
|
||||
{ path = "f32::ln_1p", reason = "use bevy_math::ops::ln_1p instead for libm determinism" },
|
||||
{ path = "f32::sinh", reason = "use bevy_math::ops::sinh instead for libm determinism" },
|
||||
{ path = "f32::cosh", reason = "use bevy_math::ops::cosh instead for libm determinism" },
|
||||
{ path = "f32::tanh", reason = "use bevy_math::ops::tanh instead for libm determinism" },
|
||||
{ path = "f32::asinh", reason = "use bevy_math::ops::asinh instead for libm determinism" },
|
||||
{ path = "f32::acosh", reason = "use bevy_math::ops::acosh instead for libm determinism" },
|
||||
{ path = "f32::atanh", reason = "use bevy_math::ops::atanh instead for libm determinism" },
|
||||
]
|
||||
|
|
|
@ -21,6 +21,7 @@ use bevy_ecs::{
|
|||
schedule::SystemSet,
|
||||
system::Resource,
|
||||
};
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
|
||||
/// Wrapper struct for [`accesskit::ActionRequest`]. Required to allow it to be used as an `Event`.
|
||||
|
@ -94,7 +95,7 @@ impl From<NodeBuilder> for AccessibilityNode {
|
|||
|
||||
/// Resource representing which entity has keyboard focus, if any.
|
||||
#[derive(Resource, Default, Deref, DerefMut, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
#[reflect(Resource, Default)]
|
||||
pub struct Focus(pub Option<Entity>);
|
||||
|
||||
/// Set enum for the systems relating to accessibility
|
||||
|
|
|
@ -22,7 +22,8 @@ use bevy_app::{App, Plugin, PostUpdate};
|
|||
use bevy_asset::{Asset, AssetApp, Assets, Handle};
|
||||
use bevy_core::Name;
|
||||
use bevy_ecs::{entity::MapEntities, prelude::*, reflect::ReflectMapEntities};
|
||||
use bevy_math::{FloatExt, Quat, Vec3};
|
||||
use bevy_math::{FloatExt, FloatPow, Quat, Vec3};
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::mesh::morph::MorphWeights;
|
||||
use bevy_time::Time;
|
||||
|
@ -540,7 +541,7 @@ impl ActiveAnimation {
|
|||
/// Automatically added to any root animations of a `SceneBundle` when it is
|
||||
/// spawned.
|
||||
#[derive(Component, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct AnimationPlayer {
|
||||
/// We use a `BTreeMap` instead of a `HashMap` here to ensure a consistent
|
||||
/// ordering when applying the animations.
|
||||
|
@ -1210,10 +1211,10 @@ fn cubic_spline_interpolation<T>(
|
|||
where
|
||||
T: Mul<f32, Output = T> + Add<Output = T>,
|
||||
{
|
||||
value_start * (2.0 * lerp.powi(3) - 3.0 * lerp.powi(2) + 1.0)
|
||||
+ tangent_out_start * (step_duration) * (lerp.powi(3) - 2.0 * lerp.powi(2) + lerp)
|
||||
+ value_end * (-2.0 * lerp.powi(3) + 3.0 * lerp.powi(2))
|
||||
+ tangent_in_end * step_duration * (lerp.powi(3) - lerp.powi(2))
|
||||
value_start * (2.0 * lerp.cubed() - 3.0 * lerp.squared() + 1.0)
|
||||
+ tangent_out_start * (step_duration) * (lerp.cubed() - 2.0 * lerp.squared() + lerp)
|
||||
+ value_end * (-2.0 * lerp.cubed() + 3.0 * lerp.squared())
|
||||
+ tangent_in_end * step_duration * (lerp.cubed() - lerp.squared())
|
||||
}
|
||||
|
||||
/// Adds animation support to an app
|
||||
|
|
|
@ -1115,7 +1115,7 @@ impl Termination for AppExit {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{iter, marker::PhantomData, mem::size_of, sync::Mutex};
|
||||
use std::{iter, marker::PhantomData, sync::Mutex};
|
||||
|
||||
use bevy_ecs::{
|
||||
change_detection::{DetectChanges, ResMut},
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
|
||||
use bevy_utils::get_short_name;
|
||||
use bevy_utils::ShortName;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
|
@ -206,7 +206,7 @@ impl<A: Asset> Default for Handle<A> {
|
|||
|
||||
impl<A: Asset> std::fmt::Debug for Handle<A> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let name = get_short_name(std::any::type_name::<A>());
|
||||
let name = ShortName::of::<A>();
|
||||
match self {
|
||||
Handle::Strong(handle) => {
|
||||
write!(
|
||||
|
|
|
@ -51,8 +51,7 @@ impl FileAssetReader {
|
|||
/// Returns the base path of the assets directory, which is normally the executable's parent
|
||||
/// directory.
|
||||
///
|
||||
/// If the `CARGO_MANIFEST_DIR` environment variable is set, then its value will be used
|
||||
/// instead. It's set by cargo when running with `cargo run`.
|
||||
/// To change this, set [`AssetPlugin.file_path`].
|
||||
pub fn get_base_path() -> PathBuf {
|
||||
get_base_path()
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ use futures_io::{AsyncRead, AsyncSeek, AsyncWrite};
|
|||
use futures_lite::{ready, Stream};
|
||||
use std::{
|
||||
io::SeekFrom,
|
||||
mem::size_of,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
|
@ -130,7 +129,10 @@ where
|
|||
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
|
||||
/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetReader`] instead.
|
||||
///
|
||||
/// Also see [`AssetWriter`].
|
||||
/// This trait defines asset-agnostic mechanisms to read bytes from a storage system.
|
||||
/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader).
|
||||
///
|
||||
/// For a complementary version of this trait that can write assets to storage, see [`AssetWriter`].
|
||||
pub trait AssetReader: Send + Sync + 'static {
|
||||
/// Returns a future to load the full file data at the provided path.
|
||||
///
|
||||
|
@ -261,7 +263,10 @@ pub enum AssetWriterError {
|
|||
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
|
||||
/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetWriter`] instead.
|
||||
///
|
||||
/// Also see [`AssetReader`].
|
||||
/// This trait defines asset-agnostic mechanisms to write bytes to a storage system.
|
||||
/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader).
|
||||
///
|
||||
/// For a complementary version of this trait that can read assets from storage, see [`AssetReader`].
|
||||
pub trait AssetWriter: Send + Sync + 'static {
|
||||
/// Writes the full asset bytes at the provided path.
|
||||
fn write<'a>(
|
||||
|
|
|
@ -1,3 +1,143 @@
|
|||
//! In the context of game development, an "asset" is a piece of content that is loaded from disk and displayed in the game.
|
||||
//! Typically, these are authored by artists and designers (in contrast to code),
|
||||
//! are relatively large in size, and include everything from textures and models to sounds and music to levels and scripts.
|
||||
//!
|
||||
//! This presents two main challenges:
|
||||
//! - Assets take up a lot of memory; simply storing a copy for each instance of an asset in the game would be prohibitively expensive.
|
||||
//! - Loading assets from disk is slow, and can cause long load times and delays.
|
||||
//!
|
||||
//! These problems play into each other, for if assets are expensive to store in memory,
|
||||
//! then larger game worlds will need to load them from disk as needed, ideally without a loading screen.
|
||||
//!
|
||||
//! As is common in Rust, non-blocking asset loading is done using `async`, with background tasks used to load assets while the game is running.
|
||||
//! Bevy coordinates these tasks using the [`AssetServer`] resource, storing each loaded asset in a strongly-typed [`Assets<T>`] collection (also a resource).
|
||||
//! [`Handle`]s serve as an id-based reference to entries in the [`Assets`] collection, allowing them to be cheaply shared between systems,
|
||||
//! and providing a way to initialize objects (generally entities) before the required assets are loaded.
|
||||
//! In short: [`Handle`]s are not the assets themselves, they just tell how to look them up!
|
||||
//!
|
||||
//! ## Loading assets
|
||||
//!
|
||||
//! The [`AssetServer`] is the main entry point for loading assets.
|
||||
//! Typically, you'll use the [`AssetServer::load`] method to load an asset from disk, which returns a [`Handle`].
|
||||
//! Note that this method does not attempt to reload the asset if it has already been loaded: as long as at least one handle has not been dropped,
|
||||
//! calling [`AssetServer::load`] on the same path will return the same handle.
|
||||
//! The handle that's returned can be used to instantiate various [`Component`](bevy_ecs::prelude::Component)s that require asset data to function,
|
||||
//! which will then be spawned into the world as part of an entity.
|
||||
//!
|
||||
//! To avoid assets "popping" into existence, you may want to check that all of the required assets are loaded before transitioning to a new scene.
|
||||
//! This can be done by checking the [`LoadState`] of the asset handle using [`AssetServer::is_loaded_with_dependencies`],
|
||||
//! which will be `true` when the asset is ready to use.
|
||||
//!
|
||||
//! Keep track of what you're waiting on by using a [`HashSet`] of asset handles or similar data structure,
|
||||
//! which iterate over and poll in your update loop, and transition to the new scene once all assets are loaded.
|
||||
//! Bevy's built-in states system can be very helpful for this!
|
||||
//!
|
||||
//! # Modifying entities that use assets
|
||||
//!
|
||||
//! If we later want to change the asset data a given component uses (such as changing an entity's material), we have three options:
|
||||
//!
|
||||
//! 1. Change the handle stored on the responsible component to the handle of a different asset
|
||||
//! 2. Despawn the entity and spawn a new one with the new asset data.
|
||||
//! 3. Use the [`Assets`] collection to directly modify the current handle's asset data
|
||||
//!
|
||||
//! The first option is the most common: just query for the component that holds the handle, and mutate it, pointing to the new asset.
|
||||
//! Check how the handle was passed in to the entity when it was spawned: if a mesh-related component required a handle to a mesh asset,
|
||||
//! you'll need to find that component via a query and change the handle to the new mesh asset.
|
||||
//! This is so commonly done that you should think about strategies for how to store and swap handles in your game.
|
||||
//!
|
||||
//! The second option is the simplest, but can be slow if done frequently,
|
||||
//! and can lead to frustrating bugs as references to the old entity (such as what is targeting it) and other data on the entity are lost.
|
||||
//! Generally, this isn't a great strategy.
|
||||
//!
|
||||
//! The third option has different semantics: rather than modifying the asset data for a single entity, it modifies the asset data for *all* entities using this handle.
|
||||
//! While this might be what you want, it generally isn't!
|
||||
//!
|
||||
//! # Hot reloading assets
|
||||
//!
|
||||
//! Bevy supports asset hot reloading, allowing you to change assets on disk and see the changes reflected in your game without restarting.
|
||||
//! When enabled, any changes to the underlying asset file will be detected by the [`AssetServer`], which will then reload the asset,
|
||||
//! mutating the asset data in the [`Assets`] collection and thus updating all entities that use the asset.
|
||||
//! While it has limited uses in published games, it is very useful when developing, as it allows you to iterate quickly.
|
||||
//!
|
||||
//! To enable asset hot reloading on desktop platforms, enable `bevy`'s `file_watcher` cargo feature.
|
||||
//! To toggle it at runtime, you can use the `watch_for_changes_override` field in the [`AssetPlugin`] to enable or disable hot reloading.
|
||||
//!
|
||||
//! # Procedural asset creation
|
||||
//!
|
||||
//! Not all assets are loaded from disk: some are generated at runtime, such as procedural materials, sounds or even levels.
|
||||
//! After creating an item of a type that implements [`Asset`], you can add it to the [`Assets`] collection using [`Assets::add`].
|
||||
//! Once in the asset collection, this data can be operated on like any other asset.
|
||||
//!
|
||||
//! Note that, unlike assets loaded from a file path, no general mechanism currently exists to deduplicate procedural assets:
|
||||
//! calling [`Assets::add`] for every entity that needs the asset will create a new copy of the asset for each entity,
|
||||
//! quickly consuming memory.
|
||||
//!
|
||||
//! ## Handles and reference counting
|
||||
//!
|
||||
//! [`Handle`] (or their untyped counterpart [`UntypedHandle`]) are used to reference assets in the [`Assets`] collection,
|
||||
//! and are the primary way to interact with assets in Bevy.
|
||||
//! As a user, you'll be working with handles a lot!
|
||||
//!
|
||||
//! The most important thing to know about handles is that they are reference counted: when you clone a handle, you're incrementing a reference count.
|
||||
//! When the object holding the handle is dropped (generally because an entity was despawned), the reference count is decremented.
|
||||
//! When the reference count hits zero, the asset it references is removed from the [`Assets`] collection.
|
||||
//!
|
||||
//! This reference counting is a simple, largely automatic way to avoid holding onto memory for game objects that are no longer in use.
|
||||
//! However, it can lead to surprising behavior if you're not careful!
|
||||
//!
|
||||
//! There are two categories of problems to watch out for:
|
||||
//! - never dropping a handle, causing the asset to never be removed from memory
|
||||
//! - dropping a handle too early, causing the asset to be removed from memory while it's still in use
|
||||
//!
|
||||
//! The first problem is less critical for beginners, as for tiny games, you can often get away with simply storing all of the assets in memory at once,
|
||||
//! and loading them all at the start of the game.
|
||||
//! As your game grows, you'll need to be more careful about when you load and unload assets,
|
||||
//! segmenting them by level or area, and loading them on-demand.
|
||||
//! This problem generally arises when handles are stored in a persistent "collection" or "manifest" of possible objects (generally in a resource),
|
||||
//! which is convenient for easy access and zero-latency spawning, but can result in high but stable memory usage.
|
||||
//!
|
||||
//! The second problem is more concerning, and looks like your models or textures suddenly disappearing from the game.
|
||||
//! Debugging reveals that the *entities* are still there, but nothing is rendering!
|
||||
//! This is because the assets were removed from memory while they were still in use.
|
||||
//! You were probably too aggressive with the use of weak handles (which don't increment the reference count of the asset): think through the lifecycle of your assets carefully!
|
||||
//! As soon as an asset is loaded, you must ensure that at least one strong handle is held to it until all matching entities are out of sight of the player.
|
||||
//!
|
||||
//! # Asset dependencies
|
||||
//!
|
||||
//! Some assets depend on other assets to be loaded before they can be loaded themselves.
|
||||
//! For example, a 3D model might require both textures and meshes to be loaded,
|
||||
//! or a 2D level might require a tileset to be loaded.
|
||||
//!
|
||||
//! The assets that are required to load another asset are called "dependencies".
|
||||
//! An asset is only considered fully loaded when it and all of its dependencies are loaded.
|
||||
//! Asset dependencies can be declared when implementing the [`Asset`] trait by implementing the [`VisitAssetDependencies`] trait,
|
||||
//! and the `#[dependency]` attribute can be used to automatically derive this implementation.
|
||||
//!
|
||||
//! # Custom asset types
|
||||
//!
|
||||
//! While Bevy comes with implementations for a large number of common game-oriented asset types (often behind off-by-default feature flags!),
|
||||
//! implementing a custom asset type can be useful when dealing with unusual, game-specific, or proprietary formats.
|
||||
//!
|
||||
//! Defining a new asset type is as simple as implementing the [`Asset`] trait.
|
||||
//! This requires [`TypePath`] for metadata about the asset type,
|
||||
//! and [`VisitAssetDependencies`] to track asset dependencies.
|
||||
//! In simple cases, you can derive [`Asset`] and [`Reflect`] and be done with it: the required supertraits will be implemented for you.
|
||||
//!
|
||||
//! With a new asset type in place, we now need to figure out how to load it.
|
||||
//! While [`AssetReader`](io::AssetReader) describes strategies to read asset bytes from various sources,
|
||||
//! [`AssetLoader`] is the trait that actually turns those into your desired in-memory format.
|
||||
//! Generally, (only) [`AssetLoader`] needs to be implemented for custom assets, as the [`AssetReader`](io::AssetReader) implementations are provided by Bevy.
|
||||
//!
|
||||
//! However, [`AssetLoader`] shouldn't be implemented for your asset type directly: instead, this is implemented for a "loader" type
|
||||
//! that can store settings and any additional data required to load your asset, while your asset type is used as the [`AssetLoader::Asset`] associated type.
|
||||
//! As the trait documentation explains, this allows various [`AssetLoader::Settings`] to be used to configure the loader.
|
||||
//!
|
||||
//! After the loader is implemented, it needs to be registered with the [`AssetServer`] using [`App::register_asset_loader`](AssetApp::register_asset_loader).
|
||||
//! Once your asset type is loaded, you can use it in your game like any other asset type!
|
||||
//!
|
||||
//! If you want to save your assets back to disk, you should implement [`AssetSaver`](saver::AssetSaver) as well.
|
||||
//! This trait mirrors [`AssetLoader`] in structure, and works in tandem with [`AssetWriter`](io::AssetWriter), which mirrors [`AssetReader`](io::AssetReader).
|
||||
|
||||
// FIXME(3492): remove once docs are ready
|
||||
#![allow(missing_docs)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
@ -100,6 +240,12 @@ pub struct AssetPlugin {
|
|||
pub meta_check: AssetMetaCheck,
|
||||
}
|
||||
|
||||
/// Controls whether or not assets are pre-processed before being loaded.
|
||||
///
|
||||
/// This setting is controlled by setting [`AssetPlugin::mode`].
|
||||
///
|
||||
/// When building on web, asset preprocessing can cause problems due to the lack of filesystem access.
|
||||
/// See [bevy#10157](https://github.com/bevyengine/bevy/issues/10157) for context.
|
||||
#[derive(Debug)]
|
||||
pub enum AssetMode {
|
||||
/// Loads assets from their [`AssetSource`]'s default [`AssetReader`] without any "preprocessing".
|
||||
|
@ -234,6 +380,13 @@ impl Plugin for AssetPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
/// Declares that this type is an asset,
|
||||
/// which can be loaded and managed by the [`AssetServer`] and stored in [`Assets`] collections.
|
||||
///
|
||||
/// Generally, assets are large, complex, and/or expensive to load from disk, and are often authored by artists or designers.
|
||||
///
|
||||
/// [`TypePath`] is largely used for diagnostic purposes, and should almost always be implemented by deriving [`Reflect`] on your type.
|
||||
/// [`VisitAssetDependencies`] is used to track asset dependencies, and an implementation is automatically generated when deriving [`Asset`].
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` is not an `Asset`",
|
||||
label = "invalid `Asset`",
|
||||
|
@ -241,6 +394,10 @@ impl Plugin for AssetPlugin {
|
|||
)]
|
||||
pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {}
|
||||
|
||||
/// This trait defines how to visit the dependencies of an asset.
|
||||
/// For example, a 3D model might require both textures and meshes to be loaded.
|
||||
///
|
||||
/// Note that this trait is automatically implemented when deriving [`Asset`].
|
||||
pub trait VisitAssetDependencies {
|
||||
fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId));
|
||||
}
|
||||
|
@ -1089,13 +1246,19 @@ mod tests {
|
|||
|
||||
assert!(d_text.is_none());
|
||||
assert!(matches!(d_load, LoadState::Failed(_)));
|
||||
assert_eq!(d_deps, DependencyLoadState::Failed);
|
||||
assert_eq!(d_rec_deps, RecursiveDependencyLoadState::Failed);
|
||||
assert!(matches!(d_deps, DependencyLoadState::Failed(_)));
|
||||
assert!(matches!(
|
||||
d_rec_deps,
|
||||
RecursiveDependencyLoadState::Failed(_)
|
||||
));
|
||||
|
||||
assert_eq!(a_text.text, "a");
|
||||
assert_eq!(a_load, LoadState::Loaded);
|
||||
assert_eq!(a_deps, DependencyLoadState::Loaded);
|
||||
assert_eq!(a_rec_deps, RecursiveDependencyLoadState::Failed);
|
||||
assert!(matches!(
|
||||
a_rec_deps,
|
||||
RecursiveDependencyLoadState::Failed(_)
|
||||
));
|
||||
|
||||
assert_eq!(b_text.text, "b");
|
||||
assert_eq!(b_load, LoadState::Loaded);
|
||||
|
@ -1104,9 +1267,127 @@ mod tests {
|
|||
|
||||
assert_eq!(c_text.text, "c");
|
||||
assert_eq!(c_load, LoadState::Loaded);
|
||||
assert_eq!(c_deps, DependencyLoadState::Failed);
|
||||
assert_eq!(c_rec_deps, RecursiveDependencyLoadState::Failed);
|
||||
assert!(matches!(c_deps, DependencyLoadState::Failed(_)));
|
||||
assert!(matches!(
|
||||
c_rec_deps,
|
||||
RecursiveDependencyLoadState::Failed(_)
|
||||
));
|
||||
|
||||
assert_eq!(asset_server.load_state(a_id), LoadState::Loaded);
|
||||
assert_eq!(
|
||||
asset_server.dependency_load_state(a_id),
|
||||
DependencyLoadState::Loaded
|
||||
);
|
||||
assert!(matches!(
|
||||
asset_server.recursive_dependency_load_state(a_id),
|
||||
RecursiveDependencyLoadState::Failed(_)
|
||||
));
|
||||
|
||||
assert!(asset_server.is_loaded(a_id));
|
||||
assert!(asset_server.is_loaded_with_direct_dependencies(a_id));
|
||||
assert!(!asset_server.is_loaded_with_dependencies(a_id));
|
||||
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependency_load_states() {
|
||||
// The particular usage of GatedReader in this test will cause deadlocking if running single-threaded
|
||||
#[cfg(not(feature = "multi_threaded"))]
|
||||
panic!("This test requires the \"multi_threaded\" feature, otherwise it will deadlock.\ncargo test --package bevy_asset --features multi_threaded");
|
||||
|
||||
let a_path = "a.cool.ron";
|
||||
let a_ron = r#"
|
||||
(
|
||||
text: "a",
|
||||
dependencies: [
|
||||
"b.cool.ron",
|
||||
"c.cool.ron",
|
||||
],
|
||||
embedded_dependencies: [],
|
||||
sub_texts: []
|
||||
)"#;
|
||||
let b_path = "b.cool.ron";
|
||||
let b_ron = r#"
|
||||
(
|
||||
text: "b",
|
||||
dependencies: [],
|
||||
MALFORMED
|
||||
embedded_dependencies: [],
|
||||
sub_texts: []
|
||||
)"#;
|
||||
|
||||
let c_path = "c.cool.ron";
|
||||
let c_ron = r#"
|
||||
(
|
||||
text: "c",
|
||||
dependencies: [],
|
||||
embedded_dependencies: [],
|
||||
sub_texts: []
|
||||
)"#;
|
||||
|
||||
let dir = Dir::default();
|
||||
dir.insert_asset_text(Path::new(a_path), a_ron);
|
||||
dir.insert_asset_text(Path::new(b_path), b_ron);
|
||||
dir.insert_asset_text(Path::new(c_path), c_ron);
|
||||
|
||||
let (mut app, gate_opener) = test_app(dir);
|
||||
app.init_asset::<CoolText>()
|
||||
.register_asset_loader(CoolTextLoader);
|
||||
let asset_server = app.world().resource::<AssetServer>().clone();
|
||||
let handle: Handle<CoolText> = asset_server.load(a_path);
|
||||
let a_id = handle.id();
|
||||
app.world_mut().spawn(handle);
|
||||
|
||||
gate_opener.open(a_path);
|
||||
run_app_until(&mut app, |world| {
|
||||
let _a_text = get::<CoolText>(world, a_id)?;
|
||||
let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
|
||||
assert_eq!(a_load, LoadState::Loaded);
|
||||
assert_eq!(a_deps, DependencyLoadState::Loading);
|
||||
assert_eq!(a_rec_deps, RecursiveDependencyLoadState::Loading);
|
||||
Some(())
|
||||
});
|
||||
|
||||
gate_opener.open(b_path);
|
||||
run_app_until(&mut app, |world| {
|
||||
let a_text = get::<CoolText>(world, a_id)?;
|
||||
let b_id = a_text.dependencies[0].id();
|
||||
|
||||
let (b_load, _b_deps, _b_rec_deps) = asset_server.get_load_states(b_id).unwrap();
|
||||
if !matches!(b_load, LoadState::Failed(_)) {
|
||||
// wait until b fails
|
||||
return None;
|
||||
}
|
||||
|
||||
let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
|
||||
assert_eq!(a_load, LoadState::Loaded);
|
||||
assert!(matches!(a_deps, DependencyLoadState::Failed(_)));
|
||||
assert!(matches!(
|
||||
a_rec_deps,
|
||||
RecursiveDependencyLoadState::Failed(_)
|
||||
));
|
||||
Some(())
|
||||
});
|
||||
|
||||
gate_opener.open(c_path);
|
||||
run_app_until(&mut app, |world| {
|
||||
let a_text = get::<CoolText>(world, a_id)?;
|
||||
let c_id = a_text.dependencies[1].id();
|
||||
// wait until c loads
|
||||
let _c_text = get::<CoolText>(world, c_id)?;
|
||||
|
||||
let (a_load, a_deps, a_rec_deps) = asset_server.get_load_states(a_id).unwrap();
|
||||
assert_eq!(a_load, LoadState::Loaded);
|
||||
assert!(
|
||||
matches!(a_deps, DependencyLoadState::Failed(_)),
|
||||
"Successful dependency load should not overwrite a previous failure"
|
||||
);
|
||||
assert!(
|
||||
matches!(a_rec_deps, RecursiveDependencyLoadState::Failed(_)),
|
||||
"Successful dependency load should not overwrite a previous failure"
|
||||
);
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@ use thiserror::Error;
|
|||
|
||||
/// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`]
|
||||
/// should be loaded.
|
||||
///
|
||||
/// This trait is generally used in concert with [`AssetReader`](crate::io::AssetReader) to load assets from a byte source.
|
||||
///
|
||||
/// For a complementary version of this trait that can save assets, see [`AssetSaver`](crate::saver::AssetSaver).
|
||||
pub trait AssetLoader: Send + Sync + 'static {
|
||||
/// The top level [`Asset`] loaded by this [`AssetLoader`].
|
||||
type Asset: Asset;
|
||||
|
|
|
@ -1,3 +1,42 @@
|
|||
//! Asset processing in Bevy is a framework for automatically transforming artist-authored assets into the format that best suits the needs of your particular game.
|
||||
//!
|
||||
//! You can think of the asset processing system as a "build system" for assets.
|
||||
//! When an artist adds a new asset to the project or an asset is changed (assuming asset hot reloading is enabled), the asset processing system will automatically perform the specified processing steps on the asset.
|
||||
//! This can include things like creating lightmaps for baked lighting, compressing a `.wav` file to an `.ogg`, or generating mipmaps for a texture.
|
||||
//!
|
||||
//! Its core values are:
|
||||
//!
|
||||
//! 1. Automatic: new and changed assets should be ready to use in-game without requiring any manual conversion or cleanup steps.
|
||||
//! 2. Configurable: every game has its own needs, and a high level of transparency and control is required.
|
||||
//! 3. Lossless: the original asset should always be preserved, ensuring artists can make changes later.
|
||||
//! 4. Deterministic: performing the same processing steps on the same asset should (generally) produce the exact same result. In cases where this doesn't make sense (steps that involve a degree of randomness or uncertainty), the results across runs should be "acceptably similar", as they will be generated once for a given set of inputs and cached.
|
||||
//!
|
||||
//! Taken together, this means that the original asset plus the processing steps should be enough to regenerate the final asset.
|
||||
//! While it may be possible to manually edit the final asset, this should be discouraged.
|
||||
//! Final post-processed assets should generally not be version-controlled, except to save developer time when recomputing heavy asset processing steps.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Asset processing can be enabled or disabled in [`AssetPlugin`](crate::AssetPlugin) by setting the [`AssetMode`](crate::AssetMode).\
|
||||
//! Enable Bevy's `file_watcher` feature to automatically watch for changes to assets and reprocess them.
|
||||
//!
|
||||
//! To register a new asset processor, use [`AssetProcessor::register_processor`].
|
||||
//! To set the default asset processor for a given extension, use [`AssetProcessor::set_default_processor`].
|
||||
//! In most cases, these methods will be called directly on [`App`](bevy_app::App) using the [`AssetApp`](crate::AssetApp) extension trait.
|
||||
//!
|
||||
//! If a default asset processor is set, assets with a matching extension will be processed using that processor before loading.
|
||||
//!
|
||||
//! For an end-to-end example, check out the examples in the [`examples/asset/processing`](https://github.com/bevyengine/bevy/tree/latest/examples/asset/processing) directory of the Bevy repository.
|
||||
//!
|
||||
//! # Defining asset processors
|
||||
//!
|
||||
//! Bevy provides two different ways to define new asset processors:
|
||||
//!
|
||||
//! - [`LoadTransformAndSave`] + [`AssetTransformer`](crate::transformer::AssetTransformer): a high-level API for loading, transforming, and saving assets.
|
||||
//! - [`Process`]: a flexible low-level API for processing assets in arbitrary ways.
|
||||
//!
|
||||
//! In most cases, [`LoadTransformAndSave`] should be sufficient.
|
||||
|
||||
mod log;
|
||||
mod process;
|
||||
|
||||
|
@ -61,6 +100,7 @@ pub struct AssetProcessor {
|
|||
pub(crate) data: Arc<AssetProcessorData>,
|
||||
}
|
||||
|
||||
/// Internal data stored inside an [`AssetProcessor`].
|
||||
pub struct AssetProcessorData {
|
||||
pub(crate) asset_infos: async_lock::RwLock<ProcessorAssetInfos>,
|
||||
log: async_lock::RwLock<Option<ProcessorTransactionLog>>,
|
||||
|
@ -91,6 +131,7 @@ impl AssetProcessor {
|
|||
Self { server, data }
|
||||
}
|
||||
|
||||
/// Gets a reference to the [`Arc`] containing the [`AssetProcessorData`].
|
||||
pub fn data(&self) -> &Arc<AssetProcessorData> {
|
||||
&self.data
|
||||
}
|
||||
|
@ -965,6 +1006,7 @@ impl AssetProcessor {
|
|||
}
|
||||
|
||||
impl AssetProcessorData {
|
||||
/// Initializes a new [`AssetProcessorData`] using the given [`AssetSources`].
|
||||
pub fn new(source: AssetSources) -> Self {
|
||||
let (mut finished_sender, finished_receiver) = async_broadcast::broadcast(1);
|
||||
let (mut initialized_sender, initialized_receiver) = async_broadcast::broadcast(1);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::io::SliceReader;
|
||||
use crate::transformer::IdentityAssetTransformer;
|
||||
use crate::{
|
||||
io::{
|
||||
AssetReaderError, AssetWriterError, MissingAssetWriterError,
|
||||
|
@ -47,6 +48,11 @@ pub trait Process: Send + Sync + Sized + 'static {
|
|||
/// an [`AssetSaver`] that allows you save any `S` asset. However you can
|
||||
/// also implement [`Process`] directly if [`LoadTransformAndSave`] feels limiting or unnecessary.
|
||||
///
|
||||
/// If your [`Process`] does not need to transform the [`Asset`], you can use [`IdentityAssetTransformer`] as `T`.
|
||||
/// This will directly return the input [`Asset`], allowing your [`Process`] to directly load and then save an [`Asset`].
|
||||
/// However, this pattern should only be used for cases such as file format conversion.
|
||||
/// Otherwise, consider refactoring your [`AssetLoader`] and [`AssetSaver`] to isolate the transformation step into an explicit [`AssetTransformer`].
|
||||
///
|
||||
/// This uses [`LoadTransformAndSaveSettings`] to configure the processor.
|
||||
///
|
||||
/// [`Asset`]: crate::Asset
|
||||
|
@ -60,6 +66,18 @@ pub struct LoadTransformAndSave<
|
|||
marker: PhantomData<fn() -> L>,
|
||||
}
|
||||
|
||||
impl<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> From<S>
|
||||
for LoadTransformAndSave<L, IdentityAssetTransformer<L::Asset>, S>
|
||||
{
|
||||
fn from(value: S) -> Self {
|
||||
LoadTransformAndSave {
|
||||
transformer: IdentityAssetTransformer::new(),
|
||||
saver: value,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings for the [`LoadTransformAndSave`] [`Process::Settings`] implementation.
|
||||
///
|
||||
/// `LoaderSettings` corresponds to [`AssetLoader::Settings`], `TransformerSettings` corresponds to [`AssetTransformer::Settings`],
|
||||
|
@ -98,30 +116,16 @@ impl<
|
|||
/// This uses [`LoadAndSaveSettings`] to configure the processor.
|
||||
///
|
||||
/// [`Asset`]: crate::Asset
|
||||
pub struct LoadAndSave<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> {
|
||||
saver: S,
|
||||
marker: PhantomData<fn() -> L>,
|
||||
}
|
||||
|
||||
impl<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> From<S> for LoadAndSave<L, S> {
|
||||
fn from(value: S) -> Self {
|
||||
LoadAndSave {
|
||||
saver: value,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[deprecated = "Use `LoadTransformAndSave<L, IdentityAssetTransformer<<L as AssetLoader>::Asset>, S>` instead"]
|
||||
pub type LoadAndSave<L, S> =
|
||||
LoadTransformAndSave<L, IdentityAssetTransformer<<L as AssetLoader>::Asset>, S>;
|
||||
|
||||
/// Settings for the [`LoadAndSave`] [`Process::Settings`] implementation.
|
||||
///
|
||||
/// `LoaderSettings` corresponds to [`AssetLoader::Settings`] and `SaverSettings` corresponds to [`AssetSaver::Settings`].
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct LoadAndSaveSettings<LoaderSettings, SaverSettings> {
|
||||
/// The [`AssetLoader::Settings`] for [`LoadAndSave`].
|
||||
pub loader_settings: LoaderSettings,
|
||||
/// The [`AssetSaver::Settings`] for [`LoadAndSave`].
|
||||
pub saver_settings: SaverSettings,
|
||||
}
|
||||
#[deprecated = "Use `LoadTransformAndSaveSettings<LoaderSettings, (), SaverSettings>` instead"]
|
||||
pub type LoadAndSaveSettings<LoaderSettings, SaverSettings> =
|
||||
LoadTransformAndSaveSettings<LoaderSettings, (), SaverSettings>;
|
||||
|
||||
/// An error that is encountered during [`Process::process`].
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -213,36 +217,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<Loader: AssetLoader, Saver: AssetSaver<Asset = Loader::Asset>> Process
|
||||
for LoadAndSave<Loader, Saver>
|
||||
{
|
||||
type Settings = LoadAndSaveSettings<Loader::Settings, Saver::Settings>;
|
||||
type OutputLoader = Saver::OutputLoader;
|
||||
|
||||
async fn process<'a>(
|
||||
&'a self,
|
||||
context: &'a mut ProcessContext<'_>,
|
||||
meta: AssetMeta<(), Self>,
|
||||
writer: &'a mut Writer,
|
||||
) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
|
||||
let AssetAction::Process { settings, .. } = meta.asset else {
|
||||
return Err(ProcessError::WrongMetaType);
|
||||
};
|
||||
let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load {
|
||||
loader: std::any::type_name::<Loader>().to_string(),
|
||||
settings: settings.loader_settings,
|
||||
});
|
||||
let loaded_asset = context.load_source_asset(loader_meta).await?;
|
||||
let saved_asset = SavedAsset::<Loader::Asset>::from_loaded(&loaded_asset).unwrap();
|
||||
let output_settings = self
|
||||
.saver
|
||||
.save(writer, saved_asset, &settings.saver_settings)
|
||||
.await
|
||||
.map_err(|error| ProcessError::AssetSaveError(error.into()))?;
|
||||
Ok(output_settings)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erased variant of [`Process`] that enables interacting with processor implementations without knowing
|
||||
/// their type.
|
||||
pub trait ErasedProcessor: Send + Sync {
|
||||
|
|
|
@ -8,6 +8,10 @@ use std::{borrow::Borrow, hash::Hash, ops::Deref};
|
|||
|
||||
/// Saves an [`Asset`] of a given [`AssetSaver::Asset`] type. [`AssetSaver::OutputLoader`] will then be used to load the saved asset
|
||||
/// in the final deployed application. The saver should produce asset bytes in a format that [`AssetSaver::OutputLoader`] can read.
|
||||
///
|
||||
/// This trait is generally used in concert with [`AssetWriter`](crate::io::AssetWriter) to write assets as bytes.
|
||||
///
|
||||
/// For a complementary version of this trait that can load assets, see [`AssetLoader`].
|
||||
pub trait AssetSaver: Send + Sync + 'static {
|
||||
/// The top level [`Asset`] saved by this [`AssetSaver`].
|
||||
type Asset: Asset;
|
||||
|
|
|
@ -388,8 +388,10 @@ impl AssetInfos {
|
|||
loaded_asset.value.insert(loaded_asset_id, world);
|
||||
let mut loading_deps = loaded_asset.dependencies;
|
||||
let mut failed_deps = HashSet::new();
|
||||
let mut dep_error = None;
|
||||
let mut loading_rec_deps = loading_deps.clone();
|
||||
let mut failed_rec_deps = HashSet::new();
|
||||
let mut rec_dep_error = None;
|
||||
loading_deps.retain(|dep_id| {
|
||||
if let Some(dep_info) = self.get_mut(*dep_id) {
|
||||
match dep_info.rec_dep_load_state {
|
||||
|
@ -404,7 +406,10 @@ impl AssetInfos {
|
|||
// If dependency is loaded, reduce our count by one
|
||||
loading_rec_deps.remove(dep_id);
|
||||
}
|
||||
RecursiveDependencyLoadState::Failed => {
|
||||
RecursiveDependencyLoadState::Failed(ref error) => {
|
||||
if rec_dep_error.is_none() {
|
||||
rec_dep_error = Some(error.clone());
|
||||
}
|
||||
failed_rec_deps.insert(*dep_id);
|
||||
loading_rec_deps.remove(dep_id);
|
||||
}
|
||||
|
@ -419,7 +424,10 @@ impl AssetInfos {
|
|||
// If dependency is loaded, reduce our count by one
|
||||
false
|
||||
}
|
||||
LoadState::Failed(_) => {
|
||||
LoadState::Failed(ref error) => {
|
||||
if dep_error.is_none() {
|
||||
dep_error = Some(error.clone());
|
||||
}
|
||||
failed_deps.insert(*dep_id);
|
||||
false
|
||||
}
|
||||
|
@ -437,7 +445,7 @@ impl AssetInfos {
|
|||
let dep_load_state = match (loading_deps.len(), failed_deps.len()) {
|
||||
(0, 0) => DependencyLoadState::Loaded,
|
||||
(_loading, 0) => DependencyLoadState::Loading,
|
||||
(_loading, _failed) => DependencyLoadState::Failed,
|
||||
(_loading, _failed) => DependencyLoadState::Failed(dep_error.unwrap()),
|
||||
};
|
||||
|
||||
let rec_dep_load_state = match (loading_rec_deps.len(), failed_rec_deps.len()) {
|
||||
|
@ -450,7 +458,7 @@ impl AssetInfos {
|
|||
RecursiveDependencyLoadState::Loaded
|
||||
}
|
||||
(_loading, 0) => RecursiveDependencyLoadState::Loading,
|
||||
(_loading, _failed) => RecursiveDependencyLoadState::Failed,
|
||||
(_loading, _failed) => RecursiveDependencyLoadState::Failed(rec_dep_error.unwrap()),
|
||||
};
|
||||
|
||||
let (dependants_waiting_on_load, dependants_waiting_on_rec_load) = {
|
||||
|
@ -480,14 +488,14 @@ impl AssetInfos {
|
|||
info.failed_rec_dependencies = failed_rec_deps;
|
||||
info.load_state = LoadState::Loaded;
|
||||
info.dep_load_state = dep_load_state;
|
||||
info.rec_dep_load_state = rec_dep_load_state;
|
||||
info.rec_dep_load_state = rec_dep_load_state.clone();
|
||||
if watching_for_changes {
|
||||
info.loader_dependencies = loaded_asset.loader_dependencies;
|
||||
}
|
||||
|
||||
let dependants_waiting_on_rec_load = if matches!(
|
||||
rec_dep_load_state,
|
||||
RecursiveDependencyLoadState::Loaded | RecursiveDependencyLoadState::Failed
|
||||
RecursiveDependencyLoadState::Loaded | RecursiveDependencyLoadState::Failed(_)
|
||||
) {
|
||||
Some(std::mem::take(
|
||||
&mut info.dependants_waiting_on_recursive_dep_load,
|
||||
|
@ -505,7 +513,9 @@ impl AssetInfos {
|
|||
for id in dependants_waiting_on_load {
|
||||
if let Some(info) = self.get_mut(id) {
|
||||
info.loading_dependencies.remove(&loaded_asset_id);
|
||||
if info.loading_dependencies.is_empty() {
|
||||
if info.loading_dependencies.is_empty()
|
||||
&& !matches!(info.dep_load_state, DependencyLoadState::Failed(_))
|
||||
{
|
||||
// send dependencies loaded event
|
||||
info.dep_load_state = DependencyLoadState::Loaded;
|
||||
}
|
||||
|
@ -519,9 +529,9 @@ impl AssetInfos {
|
|||
Self::propagate_loaded_state(self, loaded_asset_id, dep_id, sender);
|
||||
}
|
||||
}
|
||||
RecursiveDependencyLoadState::Failed => {
|
||||
RecursiveDependencyLoadState::Failed(ref error) => {
|
||||
for dep_id in dependants_waiting_on_rec_load {
|
||||
Self::propagate_failed_state(self, loaded_asset_id, dep_id);
|
||||
Self::propagate_failed_state(self, loaded_asset_id, dep_id, error);
|
||||
}
|
||||
}
|
||||
RecursiveDependencyLoadState::Loading | RecursiveDependencyLoadState::NotLoaded => {
|
||||
|
@ -570,11 +580,12 @@ impl AssetInfos {
|
|||
infos: &mut AssetInfos,
|
||||
failed_id: UntypedAssetId,
|
||||
waiting_id: UntypedAssetId,
|
||||
error: &Arc<AssetLoadError>,
|
||||
) {
|
||||
let dependants_waiting_on_rec_load = if let Some(info) = infos.get_mut(waiting_id) {
|
||||
info.loading_rec_dependencies.remove(&failed_id);
|
||||
info.failed_rec_dependencies.insert(failed_id);
|
||||
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed;
|
||||
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone());
|
||||
Some(std::mem::take(
|
||||
&mut info.dependants_waiting_on_recursive_dep_load,
|
||||
))
|
||||
|
@ -584,7 +595,7 @@ impl AssetInfos {
|
|||
|
||||
if let Some(dependants_waiting_on_rec_load) = dependants_waiting_on_rec_load {
|
||||
for dep_id in dependants_waiting_on_rec_load {
|
||||
Self::propagate_failed_state(infos, waiting_id, dep_id);
|
||||
Self::propagate_failed_state(infos, waiting_id, dep_id, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -595,14 +606,15 @@ impl AssetInfos {
|
|||
return;
|
||||
}
|
||||
|
||||
let error = Arc::new(error);
|
||||
let (dependants_waiting_on_load, dependants_waiting_on_rec_load) = {
|
||||
let Some(info) = self.get_mut(failed_id) else {
|
||||
// The asset was already dropped.
|
||||
return;
|
||||
};
|
||||
info.load_state = LoadState::Failed(Box::new(error));
|
||||
info.dep_load_state = DependencyLoadState::Failed;
|
||||
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed;
|
||||
info.load_state = LoadState::Failed(error.clone());
|
||||
info.dep_load_state = DependencyLoadState::Failed(error.clone());
|
||||
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone());
|
||||
(
|
||||
std::mem::take(&mut info.dependants_waiting_on_load),
|
||||
std::mem::take(&mut info.dependants_waiting_on_recursive_dep_load),
|
||||
|
@ -613,12 +625,15 @@ impl AssetInfos {
|
|||
if let Some(info) = self.get_mut(waiting_id) {
|
||||
info.loading_dependencies.remove(&failed_id);
|
||||
info.failed_dependencies.insert(failed_id);
|
||||
info.dep_load_state = DependencyLoadState::Failed;
|
||||
// don't overwrite DependencyLoadState if already failed to preserve first error
|
||||
if !(matches!(info.dep_load_state, DependencyLoadState::Failed(_))) {
|
||||
info.dep_load_state = DependencyLoadState::Failed(error.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for waiting_id in dependants_waiting_on_rec_load {
|
||||
Self::propagate_failed_state(self, failed_id, waiting_id);
|
||||
Self::propagate_failed_state(self, failed_id, waiting_id, &error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -266,6 +266,9 @@ impl AssetServer {
|
|||
/// it returns a "strong" [`Handle`]. When the [`Asset`] is loaded (and enters [`LoadState::Loaded`]), it will be added to the
|
||||
/// associated [`Assets`] resource.
|
||||
///
|
||||
/// Note that if the asset at this path is already loaded, this function will return the existing handle,
|
||||
/// and will not waste work spawning a new load task.
|
||||
///
|
||||
/// In case the file path contains a hashtag (`#`), the `path` must be specified using [`Path`]
|
||||
/// or [`AssetPath`] because otherwise the hashtag would be interpreted as separator between
|
||||
/// the file path and the label. For example:
|
||||
|
@ -895,17 +898,20 @@ impl AssetServer {
|
|||
&self,
|
||||
id: impl Into<UntypedAssetId>,
|
||||
) -> Option<(LoadState, DependencyLoadState, RecursiveDependencyLoadState)> {
|
||||
self.data
|
||||
.infos
|
||||
.read()
|
||||
.get(id.into())
|
||||
.map(|i| (i.load_state.clone(), i.dep_load_state, i.rec_dep_load_state))
|
||||
self.data.infos.read().get(id.into()).map(|i| {
|
||||
(
|
||||
i.load_state.clone(),
|
||||
i.dep_load_state.clone(),
|
||||
i.rec_dep_load_state.clone(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieves the main [`LoadState`] of a given asset `id`.
|
||||
///
|
||||
/// Note that this is "just" the root asset load state. To check if an asset _and_ its recursive
|
||||
/// dependencies have loaded, see [`AssetServer::is_loaded_with_dependencies`].
|
||||
/// Note that this is "just" the root asset load state. To get the load state of
|
||||
/// its dependencies or recursive dependencies, see [`AssetServer::get_dependency_load_state`]
|
||||
/// and [`AssetServer::get_recursive_dependency_load_state`] respectively.
|
||||
pub fn get_load_state(&self, id: impl Into<UntypedAssetId>) -> Option<LoadState> {
|
||||
self.data
|
||||
.infos
|
||||
|
@ -914,7 +920,27 @@ impl AssetServer {
|
|||
.map(|i| i.load_state.clone())
|
||||
}
|
||||
|
||||
/// Retrieves the [`RecursiveDependencyLoadState`] of a given asset `id`.
|
||||
/// Retrieves the [`DependencyLoadState`] of a given asset `id`'s dependencies.
|
||||
///
|
||||
/// Note that this is only the load state of direct dependencies of the root asset. To get
|
||||
/// the load state of the root asset itself or its recursive dependencies, see
|
||||
/// [`AssetServer::get_load_state`] and [`AssetServer::get_recursive_dependency_load_state`] respectively.
|
||||
pub fn get_dependency_load_state(
|
||||
&self,
|
||||
id: impl Into<UntypedAssetId>,
|
||||
) -> Option<DependencyLoadState> {
|
||||
self.data
|
||||
.infos
|
||||
.read()
|
||||
.get(id.into())
|
||||
.map(|i| i.dep_load_state.clone())
|
||||
}
|
||||
|
||||
/// Retrieves the main [`RecursiveDependencyLoadState`] of a given asset `id`'s recursive dependencies.
|
||||
///
|
||||
/// Note that this is only the load state of recursive dependencies of the root asset. To get
|
||||
/// the load state of the root asset itself or its direct dependencies only, see
|
||||
/// [`AssetServer::get_load_state`] and [`AssetServer::get_dependency_load_state`] respectively.
|
||||
pub fn get_recursive_dependency_load_state(
|
||||
&self,
|
||||
id: impl Into<UntypedAssetId>,
|
||||
|
@ -923,15 +949,30 @@ impl AssetServer {
|
|||
.infos
|
||||
.read()
|
||||
.get(id.into())
|
||||
.map(|i| i.rec_dep_load_state)
|
||||
.map(|i| i.rec_dep_load_state.clone())
|
||||
}
|
||||
|
||||
/// Retrieves the main [`LoadState`] of a given asset `id`.
|
||||
///
|
||||
/// This is the same as [`AssetServer::get_load_state`] except the result is unwrapped. If
|
||||
/// the result is None, [`LoadState::NotLoaded`] is returned.
|
||||
pub fn load_state(&self, id: impl Into<UntypedAssetId>) -> LoadState {
|
||||
self.get_load_state(id).unwrap_or(LoadState::NotLoaded)
|
||||
}
|
||||
|
||||
/// Retrieves the [`DependencyLoadState`] of a given asset `id`.
|
||||
///
|
||||
/// This is the same as [`AssetServer::get_dependency_load_state`] except the result is unwrapped. If
|
||||
/// the result is None, [`DependencyLoadState::NotLoaded`] is returned.
|
||||
pub fn dependency_load_state(&self, id: impl Into<UntypedAssetId>) -> DependencyLoadState {
|
||||
self.get_dependency_load_state(id)
|
||||
.unwrap_or(DependencyLoadState::NotLoaded)
|
||||
}
|
||||
|
||||
/// Retrieves the [`RecursiveDependencyLoadState`] of a given asset `id`.
|
||||
///
|
||||
/// This is the same as [`AssetServer::get_recursive_dependency_load_state`] except the result is unwrapped. If
|
||||
/// the result is None, [`RecursiveDependencyLoadState::NotLoaded`] is returned.
|
||||
pub fn recursive_dependency_load_state(
|
||||
&self,
|
||||
id: impl Into<UntypedAssetId>,
|
||||
|
@ -940,11 +981,30 @@ impl AssetServer {
|
|||
.unwrap_or(RecursiveDependencyLoadState::NotLoaded)
|
||||
}
|
||||
|
||||
/// Returns true if the asset and all of its dependencies (recursive) have been loaded.
|
||||
/// Convenience method that returns true if the asset has been loaded.
|
||||
pub fn is_loaded(&self, id: impl Into<UntypedAssetId>) -> bool {
|
||||
matches!(self.load_state(id), LoadState::Loaded)
|
||||
}
|
||||
|
||||
/// Convenience method that returns true if the asset and all of its direct dependencies have been loaded.
|
||||
pub fn is_loaded_with_direct_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
|
||||
matches!(
|
||||
self.get_load_states(id),
|
||||
Some((LoadState::Loaded, DependencyLoadState::Loaded, _))
|
||||
)
|
||||
}
|
||||
|
||||
/// Convenience method that returns true if the asset, all of its dependencies, and all of its recursive
|
||||
/// dependencies have been loaded.
|
||||
pub fn is_loaded_with_dependencies(&self, id: impl Into<UntypedAssetId>) -> bool {
|
||||
let id = id.into();
|
||||
self.load_state(id) == LoadState::Loaded
|
||||
&& self.recursive_dependency_load_state(id) == RecursiveDependencyLoadState::Loaded
|
||||
matches!(
|
||||
self.get_load_states(id),
|
||||
Some((
|
||||
LoadState::Loaded,
|
||||
DependencyLoadState::Loaded,
|
||||
RecursiveDependencyLoadState::Loaded
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an active handle for the given path, if the asset at the given path has already started loading,
|
||||
|
@ -1360,12 +1420,14 @@ pub enum LoadState {
|
|||
Loading,
|
||||
/// The asset has been loaded and has been added to the [`World`]
|
||||
Loaded,
|
||||
/// The asset failed to load.
|
||||
Failed(Box<AssetLoadError>),
|
||||
/// The asset failed to load. The underlying [`AssetLoadError`] is
|
||||
/// referenced by [`Arc`] clones in all related [`DependencyLoadState`]s
|
||||
/// and [`RecursiveDependencyLoadState`]s in the asset's dependency tree.
|
||||
Failed(Arc<AssetLoadError>),
|
||||
}
|
||||
|
||||
/// The load state of an asset's dependencies.
|
||||
#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[derive(Component, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum DependencyLoadState {
|
||||
/// The asset has not started loading yet
|
||||
NotLoaded,
|
||||
|
@ -1373,12 +1435,14 @@ pub enum DependencyLoadState {
|
|||
Loading,
|
||||
/// Dependencies have all loaded
|
||||
Loaded,
|
||||
/// One or more dependencies have failed to load
|
||||
Failed,
|
||||
/// One or more dependencies have failed to load. The underlying [`AssetLoadError`]
|
||||
/// is referenced by [`Arc`] clones in all related [`LoadState`] and
|
||||
/// [`RecursiveDependencyLoadState`]s in the asset's dependency tree.
|
||||
Failed(Arc<AssetLoadError>),
|
||||
}
|
||||
|
||||
/// The recursive load state of an asset's dependencies.
|
||||
#[derive(Component, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
#[derive(Component, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum RecursiveDependencyLoadState {
|
||||
/// The asset has not started loading yet
|
||||
NotLoaded,
|
||||
|
@ -1386,8 +1450,11 @@ pub enum RecursiveDependencyLoadState {
|
|||
Loading,
|
||||
/// Dependencies in this asset's dependency tree have all loaded
|
||||
Loaded,
|
||||
/// One or more dependencies have failed to load in this asset's dependency tree
|
||||
Failed,
|
||||
/// One or more dependencies have failed to load in this asset's dependency
|
||||
/// tree. The underlying [`AssetLoadError`] is referenced by [`Arc`] clones
|
||||
/// in all related [`LoadState`]s and [`DependencyLoadState`]s in the asset's
|
||||
/// dependency tree.
|
||||
Failed(Arc<AssetLoadError>),
|
||||
}
|
||||
|
||||
/// An error that occurs during an [`Asset`] load.
|
||||
|
|
|
@ -4,11 +4,15 @@ use bevy_utils::{ConditionalSendFuture, HashMap};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
convert::Infallible,
|
||||
hash::Hash,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
/// Transforms an [`Asset`] of a given [`AssetTransformer::AssetInput`] type to an [`Asset`] of [`AssetTransformer::AssetOutput`] type.
|
||||
///
|
||||
/// This trait is commonly used in association with [`LoadTransformAndSave`](crate::processor::LoadTransformAndSave) to accomplish common asset pipeline workflows.
|
||||
pub trait AssetTransformer: Send + Sync + 'static {
|
||||
/// The [`Asset`] type which this [`AssetTransformer`] takes as and input.
|
||||
type AssetInput: Asset;
|
||||
|
@ -241,3 +245,37 @@ impl<'a, A: Asset> TransformedSubAsset<'a, A> {
|
|||
self.labeled_assets.keys().map(|s| &**s)
|
||||
}
|
||||
}
|
||||
|
||||
/// An identity [`AssetTransformer`] which infallibly returns the input [`Asset`] on transformation.]
|
||||
pub struct IdentityAssetTransformer<A: Asset> {
|
||||
_phantom: PhantomData<fn(A) -> A>,
|
||||
}
|
||||
|
||||
impl<A: Asset> IdentityAssetTransformer<A> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Asset> Default for IdentityAssetTransformer<A> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Asset> AssetTransformer for IdentityAssetTransformer<A> {
|
||||
type AssetInput = A;
|
||||
type AssetOutput = A;
|
||||
type Settings = ();
|
||||
type Error = Infallible;
|
||||
|
||||
async fn transform<'a>(
|
||||
&'a self,
|
||||
asset: TransformedAsset<Self::AssetInput>,
|
||||
_settings: &'a Self::Settings,
|
||||
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
|
||||
Ok(asset)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ pub enum PlaybackMode {
|
|||
/// [`AudioSink`][crate::AudioSink] or [`SpatialAudioSink`][crate::SpatialAudioSink]
|
||||
/// components. Changes to this component will *not* be applied to already-playing audio.
|
||||
#[derive(Component, Clone, Copy, Debug, Reflect)]
|
||||
#[reflect(Default, Component)]
|
||||
#[reflect(Default, Component, Debug)]
|
||||
pub struct PlaybackSettings {
|
||||
/// The desired playback behavior.
|
||||
pub mode: PlaybackMode,
|
||||
|
@ -144,7 +144,7 @@ impl PlaybackSettings {
|
|||
/// This must be accompanied by `Transform` and `GlobalTransform`.
|
||||
/// Only one entity with a `SpatialListener` should be present at any given time.
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[reflect(Default, Component)]
|
||||
#[reflect(Default, Component, Debug)]
|
||||
pub struct SpatialListener {
|
||||
/// Left ear position relative to the `GlobalTransform`.
|
||||
pub left_ear_offset: Vec3,
|
||||
|
@ -175,7 +175,7 @@ impl SpatialListener {
|
|||
///
|
||||
/// Note: changing this value will not affect already playing audio.
|
||||
#[derive(Resource, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
#[reflect(Resource, Default)]
|
||||
pub struct GlobalVolume {
|
||||
/// The global volume of all audio.
|
||||
pub volume: Volume,
|
||||
|
@ -223,7 +223,7 @@ impl Default for SpatialScale {
|
|||
///
|
||||
/// Default is `Vec3::ONE`.
|
||||
#[derive(Resource, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
#[reflect(Resource, Default)]
|
||||
pub struct DefaultSpatialScale(pub SpatialScale);
|
||||
|
||||
/// Bundle for playing a standard bevy audio asset
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, Hsla, Hsva, Hwba, LinearRgba,
|
||||
Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
|
||||
};
|
||||
use bevy_math::{Vec3, Vec4};
|
||||
use bevy_math::{ops, Vec3, Vec4};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::prelude::*;
|
||||
|
||||
|
@ -225,7 +225,7 @@ impl From<Laba> for Xyza {
|
|||
let fx = a / 500.0 + fy;
|
||||
let fz = fy - b / 200.0;
|
||||
let xr = {
|
||||
let fx3 = fx.powf(3.0);
|
||||
let fx3 = ops::powf(fx, 3.0);
|
||||
|
||||
if fx3 > Laba::CIE_EPSILON {
|
||||
fx3
|
||||
|
@ -234,12 +234,12 @@ impl From<Laba> for Xyza {
|
|||
}
|
||||
};
|
||||
let yr = if l > Laba::CIE_EPSILON * Laba::CIE_KAPPA {
|
||||
((l + 16.0) / 116.0).powf(3.0)
|
||||
ops::powf((l + 16.0) / 116.0, 3.0)
|
||||
} else {
|
||||
l / Laba::CIE_KAPPA
|
||||
};
|
||||
let zr = {
|
||||
let fz3 = fz.powf(3.0);
|
||||
let fz3 = ops::powf(fz, 3.0);
|
||||
|
||||
if fz3 > Laba::CIE_EPSILON {
|
||||
fz3
|
||||
|
@ -262,17 +262,17 @@ impl From<Xyza> for Laba {
|
|||
let yr = y / Xyza::D65_WHITE.y;
|
||||
let zr = z / Xyza::D65_WHITE.z;
|
||||
let fx = if xr > Laba::CIE_EPSILON {
|
||||
xr.cbrt()
|
||||
ops::cbrt(xr)
|
||||
} else {
|
||||
(Laba::CIE_KAPPA * xr + 16.0) / 116.0
|
||||
};
|
||||
let fy = if yr > Laba::CIE_EPSILON {
|
||||
yr.cbrt()
|
||||
ops::cbrt(yr)
|
||||
} else {
|
||||
(Laba::CIE_KAPPA * yr + 16.0) / 116.0
|
||||
};
|
||||
let fz = if yr > Laba::CIE_EPSILON {
|
||||
zr.cbrt()
|
||||
ops::cbrt(zr)
|
||||
} else {
|
||||
(Laba::CIE_KAPPA * zr + 16.0) / 116.0
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
Alpha, ColorToComponents, Gray, Hue, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor,
|
||||
Xyza,
|
||||
};
|
||||
use bevy_math::{Vec3, Vec4};
|
||||
use bevy_math::{ops, Vec3, Vec4};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::prelude::*;
|
||||
|
||||
|
@ -257,8 +257,9 @@ impl From<Lcha> for Laba {
|
|||
) -> Self {
|
||||
// Based on http://www.brucelindbloom.com/index.html?Eqn_LCH_to_Lab.html
|
||||
let l = lightness;
|
||||
let a = chroma * hue.to_radians().cos();
|
||||
let b = chroma * hue.to_radians().sin();
|
||||
let (sin, cos) = ops::sin_cos(hue.to_radians());
|
||||
let a = chroma * cos;
|
||||
let b = chroma * sin;
|
||||
|
||||
Laba::new(l, a, b, alpha)
|
||||
}
|
||||
|
@ -274,9 +275,9 @@ impl From<Laba> for Lcha {
|
|||
}: Laba,
|
||||
) -> Self {
|
||||
// Based on http://www.brucelindbloom.com/index.html?Eqn_Lab_to_LCH.html
|
||||
let c = (a.powf(2.0) + b.powf(2.0)).sqrt();
|
||||
let c = ops::hypot(a, b);
|
||||
let h = {
|
||||
let h = b.to_radians().atan2(a.to_radians()).to_degrees();
|
||||
let h = ops::atan2(b.to_radians(), a.to_radians()).to_degrees();
|
||||
|
||||
if h < 0.0 {
|
||||
h + 360.0
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents,
|
||||
Gray, Hsla, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza,
|
||||
};
|
||||
use bevy_math::{Vec3, Vec4};
|
||||
use bevy_math::{ops, FloatPow, Vec3, Vec4};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::prelude::*;
|
||||
|
||||
|
@ -156,9 +156,9 @@ impl Luminance for Oklaba {
|
|||
impl EuclideanDistance for Oklaba {
|
||||
#[inline]
|
||||
fn distance_squared(&self, other: &Self) -> f32 {
|
||||
(self.lightness - other.lightness).powi(2)
|
||||
+ (self.a - other.a).powi(2)
|
||||
+ (self.b - other.b).powi(2)
|
||||
(self.lightness - other.lightness).squared()
|
||||
+ (self.a - other.a).squared()
|
||||
+ (self.b - other.b).squared()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,9 +229,9 @@ impl From<LinearRgba> for Oklaba {
|
|||
let l = 0.4122214708 * red + 0.5363325363 * green + 0.0514459929 * blue;
|
||||
let m = 0.2119034982 * red + 0.6806995451 * green + 0.1073969566 * blue;
|
||||
let s = 0.0883024619 * red + 0.2817188376 * green + 0.6299787005 * blue;
|
||||
let l_ = l.cbrt();
|
||||
let m_ = m.cbrt();
|
||||
let s_ = s.cbrt();
|
||||
let l_ = ops::cbrt(l);
|
||||
let m_ = ops::cbrt(m);
|
||||
let s_ = ops::cbrt(s);
|
||||
let l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
|
||||
let a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
|
||||
let b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
|||
color_difference::EuclideanDistance, Alpha, ColorToComponents, Gray, Hsla, Hsva, Hue, Hwba,
|
||||
Laba, Lcha, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
|
||||
};
|
||||
use bevy_math::{Vec3, Vec4};
|
||||
use bevy_math::{ops, FloatPow, Vec3, Vec4};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::prelude::*;
|
||||
|
||||
|
@ -191,9 +191,9 @@ impl Luminance for Oklcha {
|
|||
impl EuclideanDistance for Oklcha {
|
||||
#[inline]
|
||||
fn distance_squared(&self, other: &Self) -> f32 {
|
||||
(self.lightness - other.lightness).powi(2)
|
||||
+ (self.chroma - other.chroma).powi(2)
|
||||
+ (self.hue - other.hue).powi(2)
|
||||
(self.lightness - other.lightness).squared()
|
||||
+ (self.chroma - other.chroma).squared()
|
||||
+ (self.hue - other.hue).squared()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,8 +260,8 @@ impl From<Oklaba> for Oklcha {
|
|||
alpha,
|
||||
}: Oklaba,
|
||||
) -> Self {
|
||||
let chroma = a.hypot(b);
|
||||
let hue = b.atan2(a).to_degrees();
|
||||
let chroma = ops::hypot(a, b);
|
||||
let hue = ops::atan2(b, a).to_degrees();
|
||||
|
||||
let hue = if hue < 0.0 { hue + 360.0 } else { hue };
|
||||
|
||||
|
@ -279,8 +279,9 @@ impl From<Oklcha> for Oklaba {
|
|||
}: Oklcha,
|
||||
) -> Self {
|
||||
let l = lightness;
|
||||
let a = chroma * hue.to_radians().cos();
|
||||
let b = chroma * hue.to_radians().sin();
|
||||
let (sin, cos) = ops::sin_cos(hue.to_radians());
|
||||
let a = chroma * cos;
|
||||
let b = chroma * sin;
|
||||
|
||||
Oklaba::new(l, a, b, alpha)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
impl_componentwise_vector_space, Alpha, ColorToComponents, ColorToPacked, Gray, LinearRgba,
|
||||
Luminance, Mix, StandardColor, Xyza,
|
||||
};
|
||||
use bevy_math::{Vec3, Vec4};
|
||||
use bevy_math::{ops, Vec3, Vec4};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::prelude::*;
|
||||
use thiserror::Error;
|
||||
|
@ -215,7 +215,7 @@ impl Srgba {
|
|||
if value <= 0.04045 {
|
||||
value / 12.92 // linear falloff in dark values
|
||||
} else {
|
||||
((value + 0.055) / 1.055).powf(2.4) // gamma curve in other area
|
||||
ops::powf((value + 0.055) / 1.055, 2.4) // gamma curve in other area
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,7 +228,7 @@ impl Srgba {
|
|||
if value <= 0.0031308 {
|
||||
value * 12.92 // linear falloff in dark values
|
||||
} else {
|
||||
(1.055 * value.powf(1.0 / 2.4)) - 0.055 // gamma curve in other area
|
||||
(1.055 * ops::powf(value, 1.0 / 2.4)) - 0.055 // gamma curve in other area
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ use bevy_render::{
|
|||
};
|
||||
use bevy_utils::{Entry, HashMap};
|
||||
|
||||
use super::pipeline::AutoExposureSettingsUniform;
|
||||
use super::AutoExposureSettings;
|
||||
use super::pipeline::AutoExposureUniform;
|
||||
use super::AutoExposure;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub(super) struct AutoExposureBuffers {
|
||||
|
@ -16,19 +16,19 @@ pub(super) struct AutoExposureBuffers {
|
|||
|
||||
pub(super) struct AutoExposureBuffer {
|
||||
pub(super) state: StorageBuffer<f32>,
|
||||
pub(super) settings: UniformBuffer<AutoExposureSettingsUniform>,
|
||||
pub(super) settings: UniformBuffer<AutoExposureUniform>,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub(super) struct ExtractedStateBuffers {
|
||||
changed: Vec<(Entity, AutoExposureSettings)>,
|
||||
changed: Vec<(Entity, AutoExposure)>,
|
||||
removed: Vec<Entity>,
|
||||
}
|
||||
|
||||
pub(super) fn extract_buffers(
|
||||
mut commands: Commands,
|
||||
changed: Extract<Query<(Entity, &AutoExposureSettings), Changed<AutoExposureSettings>>>,
|
||||
mut removed: Extract<RemovedComponents<AutoExposureSettings>>,
|
||||
changed: Extract<Query<(Entity, &AutoExposure), Changed<AutoExposure>>>,
|
||||
mut removed: Extract<RemovedComponents<AutoExposure>>,
|
||||
) {
|
||||
commands.insert_resource(ExtractedStateBuffers {
|
||||
changed: changed
|
||||
|
@ -50,7 +50,7 @@ pub(super) fn prepare_buffers(
|
|||
let (low_percent, high_percent) = settings.filter.into_inner();
|
||||
let initial_state = 0.0f32.clamp(min_log_lum, max_log_lum);
|
||||
|
||||
let settings = AutoExposureSettingsUniform {
|
||||
let settings = AutoExposureUniform {
|
||||
min_log_lum,
|
||||
inv_log_lum_range: 1.0 / (max_log_lum - min_log_lum),
|
||||
log_lum_range: max_log_lum - min_log_lum,
|
||||
|
|
|
@ -26,14 +26,15 @@ use node::AutoExposureNode;
|
|||
use pipeline::{
|
||||
AutoExposurePass, AutoExposurePipeline, ViewAutoExposurePipeline, METERING_SHADER_HANDLE,
|
||||
};
|
||||
pub use settings::AutoExposureSettings;
|
||||
#[allow(deprecated)]
|
||||
pub use settings::{AutoExposure, AutoExposureSettings};
|
||||
|
||||
use crate::auto_exposure::compensation_curve::GpuAutoExposureCompensationCurve;
|
||||
use crate::core_3d::graph::{Core3d, Node3d};
|
||||
|
||||
/// Plugin for the auto exposure feature.
|
||||
///
|
||||
/// See [`AutoExposureSettings`] for more details.
|
||||
/// See [`AutoExposure`] for more details.
|
||||
pub struct AutoExposurePlugin;
|
||||
|
||||
#[derive(Resource)]
|
||||
|
@ -58,8 +59,8 @@ impl Plugin for AutoExposurePlugin {
|
|||
.resource_mut::<Assets<AutoExposureCompensationCurve>>()
|
||||
.insert(&Handle::default(), AutoExposureCompensationCurve::default());
|
||||
|
||||
app.register_type::<AutoExposureSettings>();
|
||||
app.add_plugins(ExtractComponentPlugin::<AutoExposureSettings>::default());
|
||||
app.register_type::<AutoExposure>();
|
||||
app.add_plugins(ExtractComponentPlugin::<AutoExposure>::default());
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
|
@ -113,9 +114,9 @@ fn queue_view_auto_exposure_pipelines(
|
|||
pipeline_cache: Res<PipelineCache>,
|
||||
mut compute_pipelines: ResMut<SpecializedComputePipelines<AutoExposurePipeline>>,
|
||||
pipeline: Res<AutoExposurePipeline>,
|
||||
view_targets: Query<(Entity, &AutoExposureSettings)>,
|
||||
view_targets: Query<(Entity, &AutoExposure)>,
|
||||
) {
|
||||
for (entity, settings) in view_targets.iter() {
|
||||
for (entity, auto_exposure) in view_targets.iter() {
|
||||
let histogram_pipeline =
|
||||
compute_pipelines.specialize(&pipeline_cache, &pipeline, AutoExposurePass::Histogram);
|
||||
let average_pipeline =
|
||||
|
@ -124,8 +125,8 @@ fn queue_view_auto_exposure_pipelines(
|
|||
commands.entity(entity).insert(ViewAutoExposurePipeline {
|
||||
histogram_pipeline,
|
||||
mean_luminance_pipeline: average_pipeline,
|
||||
compensation_curve: settings.compensation_curve.clone(),
|
||||
metering_mask: settings.metering_mask.clone(),
|
||||
compensation_curve: auto_exposure.compensation_curve.clone(),
|
||||
metering_mask: auto_exposure.metering_mask.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ pub struct ViewAutoExposurePipeline {
|
|||
}
|
||||
|
||||
#[derive(ShaderType, Clone, Copy)]
|
||||
pub struct AutoExposureSettingsUniform {
|
||||
pub struct AutoExposureUniform {
|
||||
pub(super) min_log_lum: f32,
|
||||
pub(super) inv_log_lum_range: f32,
|
||||
pub(super) log_lum_range: f32,
|
||||
|
@ -59,7 +59,7 @@ impl FromWorld for AutoExposurePipeline {
|
|||
ShaderStages::COMPUTE,
|
||||
(
|
||||
uniform_buffer::<GlobalsUniform>(false),
|
||||
uniform_buffer::<AutoExposureSettingsUniform>(false),
|
||||
uniform_buffer::<AutoExposureUniform>(false),
|
||||
texture_2d(TextureSampleType::Float { filterable: false }),
|
||||
texture_2d(TextureSampleType::Float { filterable: false }),
|
||||
texture_1d(TextureSampleType::Float { filterable: false }),
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::ops::RangeInclusive;
|
|||
use super::compensation_curve::AutoExposureCompensationCurve;
|
||||
use bevy_asset::Handle;
|
||||
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::{extract_component::ExtractComponent, texture::Image};
|
||||
use bevy_utils::default;
|
||||
|
@ -24,8 +25,8 @@ use bevy_utils::default;
|
|||
/// **Auto Exposure requires compute shaders and is not compatible with WebGL2.**
|
||||
///
|
||||
#[derive(Component, Clone, Reflect, ExtractComponent)]
|
||||
#[reflect(Component)]
|
||||
pub struct AutoExposureSettings {
|
||||
#[reflect(Component, Default)]
|
||||
pub struct AutoExposure {
|
||||
/// The range of exposure values for the histogram.
|
||||
///
|
||||
/// Pixel values below this range will be ignored, and pixel values above this range will be
|
||||
|
@ -88,7 +89,10 @@ pub struct AutoExposureSettings {
|
|||
pub compensation_curve: Handle<AutoExposureCompensationCurve>,
|
||||
}
|
||||
|
||||
impl Default for AutoExposureSettings {
|
||||
#[deprecated(since = "0.15.0", note = "Renamed to `AutoExposure`")]
|
||||
pub type AutoExposureSettings = AutoExposure;
|
||||
|
||||
impl Default for AutoExposure {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
range: -8.0..=8.0,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{BloomSettings, BLOOM_SHADER_HANDLE, BLOOM_TEXTURE_FORMAT};
|
||||
use super::{Bloom, BLOOM_SHADER_HANDLE, BLOOM_TEXTURE_FORMAT};
|
||||
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, Entity},
|
||||
|
@ -33,7 +33,7 @@ pub struct BloomDownsamplingPipelineKeys {
|
|||
first_downsample: bool,
|
||||
}
|
||||
|
||||
/// The uniform struct extracted from [`BloomSettings`] attached to a Camera.
|
||||
/// The uniform struct extracted from [`Bloom`] attached to a Camera.
|
||||
/// Will be available for use in the Bloom shader.
|
||||
#[derive(Component, ShaderType, Clone)]
|
||||
pub struct BloomUniforms {
|
||||
|
@ -136,10 +136,10 @@ pub fn prepare_downsampling_pipeline(
|
|||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<BloomDownsamplingPipeline>>,
|
||||
pipeline: Res<BloomDownsamplingPipeline>,
|
||||
views: Query<(Entity, &BloomSettings)>,
|
||||
views: Query<(Entity, &Bloom)>,
|
||||
) {
|
||||
for (entity, settings) in &views {
|
||||
let prefilter = settings.prefilter_settings.threshold > 0.0;
|
||||
for (entity, bloom) in &views {
|
||||
let prefilter = bloom.prefilter.threshold > 0.0;
|
||||
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
|
|
|
@ -3,7 +3,10 @@ mod settings;
|
|||
mod upsampling_pipeline;
|
||||
|
||||
use bevy_color::{Gray, LinearRgba};
|
||||
pub use settings::{BloomCompositeMode, BloomPrefilterSettings, BloomSettings};
|
||||
#[allow(deprecated)]
|
||||
pub use settings::{
|
||||
Bloom, BloomCompositeMode, BloomPrefilter, BloomPrefilterSettings, BloomSettings,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
|
@ -12,7 +15,7 @@ use crate::{
|
|||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, Handle};
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_math::UVec2;
|
||||
use bevy_math::{ops, UVec2};
|
||||
use bevy_render::{
|
||||
camera::ExtractedCamera,
|
||||
diagnostic::RecordDiagnostics,
|
||||
|
@ -44,11 +47,11 @@ impl Plugin for BloomPlugin {
|
|||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, BLOOM_SHADER_HANDLE, "bloom.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.register_type::<BloomSettings>();
|
||||
app.register_type::<BloomPrefilterSettings>();
|
||||
app.register_type::<Bloom>();
|
||||
app.register_type::<BloomPrefilter>();
|
||||
app.register_type::<BloomCompositeMode>();
|
||||
app.add_plugins((
|
||||
ExtractComponentPlugin::<BloomSettings>::default(),
|
||||
ExtractComponentPlugin::<Bloom>::default(),
|
||||
UniformComponentPlugin::<BloomUniforms>::default(),
|
||||
));
|
||||
|
||||
|
@ -100,7 +103,7 @@ impl ViewNode for BloomNode {
|
|||
&'static BloomTexture,
|
||||
&'static BloomBindGroups,
|
||||
&'static DynamicUniformIndex<BloomUniforms>,
|
||||
&'static BloomSettings,
|
||||
&'static Bloom,
|
||||
&'static UpsamplingPipelineIds,
|
||||
&'static BloomDownsamplingPipelineIds,
|
||||
);
|
||||
|
@ -324,18 +327,18 @@ fn prepare_bloom_textures(
|
|||
mut commands: Commands,
|
||||
mut texture_cache: ResMut<TextureCache>,
|
||||
render_device: Res<RenderDevice>,
|
||||
views: Query<(Entity, &ExtractedCamera, &BloomSettings)>,
|
||||
views: Query<(Entity, &ExtractedCamera, &Bloom)>,
|
||||
) {
|
||||
for (entity, camera, settings) in &views {
|
||||
for (entity, camera, bloom) in &views {
|
||||
if let Some(UVec2 {
|
||||
x: width,
|
||||
y: height,
|
||||
}) = camera.physical_viewport_size
|
||||
{
|
||||
// How many times we can halve the resolution minus one so we don't go unnecessarily low
|
||||
let mip_count = settings.max_mip_dimension.ilog2().max(2) - 1;
|
||||
let mip_count = bloom.max_mip_dimension.ilog2().max(2) - 1;
|
||||
let mip_height_ratio = if height != 0 {
|
||||
settings.max_mip_dimension as f32 / height as f32
|
||||
bloom.max_mip_dimension as f32 / height as f32
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
@ -457,19 +460,20 @@ fn prepare_bloom_bind_groups(
|
|||
/// * `max_mip` - the index of the lowest frequency pyramid level.
|
||||
///
|
||||
/// This function can be visually previewed for all values of *mip* (normalized) with tweakable
|
||||
/// [`BloomSettings`] parameters on [Desmos graphing calculator](https://www.desmos.com/calculator/ncc8xbhzzl).
|
||||
fn compute_blend_factor(bloom_settings: &BloomSettings, mip: f32, max_mip: f32) -> f32 {
|
||||
let mut lf_boost = (1.0
|
||||
- (1.0 - (mip / max_mip)).powf(1.0 / (1.0 - bloom_settings.low_frequency_boost_curvature)))
|
||||
* bloom_settings.low_frequency_boost;
|
||||
/// [`Bloom`] parameters on [Desmos graphing calculator](https://www.desmos.com/calculator/ncc8xbhzzl).
|
||||
fn compute_blend_factor(bloom: &Bloom, mip: f32, max_mip: f32) -> f32 {
|
||||
let mut lf_boost =
|
||||
(1.0 - ops::powf(
|
||||
1.0 - (mip / max_mip),
|
||||
1.0 / (1.0 - bloom.low_frequency_boost_curvature),
|
||||
)) * bloom.low_frequency_boost;
|
||||
let high_pass_lq = 1.0
|
||||
- (((mip / max_mip) - bloom_settings.high_pass_frequency)
|
||||
/ bloom_settings.high_pass_frequency)
|
||||
- (((mip / max_mip) - bloom.high_pass_frequency) / bloom.high_pass_frequency)
|
||||
.clamp(0.0, 1.0);
|
||||
lf_boost *= match bloom_settings.composite_mode {
|
||||
BloomCompositeMode::EnergyConserving => 1.0 - bloom_settings.intensity,
|
||||
lf_boost *= match bloom.composite_mode {
|
||||
BloomCompositeMode::EnergyConserving => 1.0 - bloom.intensity,
|
||||
BloomCompositeMode::Additive => 1.0,
|
||||
};
|
||||
|
||||
(bloom_settings.intensity + lf_boost) * high_pass_lq
|
||||
(bloom.intensity + lf_boost) * high_pass_lq
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ use bevy_render::{extract_component::ExtractComponent, prelude::Camera};
|
|||
/// used in Bevy as well as a visualization of the curve's respective scattering profile.
|
||||
#[derive(Component, Reflect, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct BloomSettings {
|
||||
pub struct Bloom {
|
||||
/// Controls the baseline of how much the image is scattered (default: 0.15).
|
||||
///
|
||||
/// This parameter should be used only to control the strength of the bloom
|
||||
|
@ -90,15 +90,21 @@ pub struct BloomSettings {
|
|||
/// * 1.0 - maximum scattering angle is 90 degrees
|
||||
pub high_pass_frequency: f32,
|
||||
|
||||
pub prefilter_settings: BloomPrefilterSettings,
|
||||
/// Controls the threshold filter used for extracting the brightest regions from the input image
|
||||
/// before blurring them and compositing back onto the original image.
|
||||
///
|
||||
/// Changing these settings creates a physically inaccurate image and makes it easy to make
|
||||
/// the final result look worse. However, they can be useful when emulating the 1990s-2000s game look.
|
||||
/// See [`BloomPrefilter`] for more information.
|
||||
pub prefilter: BloomPrefilter,
|
||||
|
||||
/// Controls whether bloom textures
|
||||
/// are blended between or added to each other. Useful
|
||||
/// if image brightening is desired and a must-change
|
||||
/// if `prefilter_settings` are used.
|
||||
/// if `prefilter` is used.
|
||||
///
|
||||
/// # Recommendation
|
||||
/// Set to [`BloomCompositeMode::Additive`] if `prefilter_settings` are
|
||||
/// Set to [`BloomCompositeMode::Additive`] if `prefilter` is
|
||||
/// configured in a non-energy-conserving way,
|
||||
/// otherwise set to [`BloomCompositeMode::EnergyConserving`].
|
||||
pub composite_mode: BloomCompositeMode,
|
||||
|
@ -112,7 +118,10 @@ pub struct BloomSettings {
|
|||
pub uv_offset: f32,
|
||||
}
|
||||
|
||||
impl BloomSettings {
|
||||
#[deprecated(since = "0.15.0", note = "Renamed to `Bloom`")]
|
||||
pub type BloomSettings = Bloom;
|
||||
|
||||
impl Bloom {
|
||||
const DEFAULT_MAX_MIP_DIMENSION: u32 = 512;
|
||||
const DEFAULT_UV_OFFSET: f32 = 0.004;
|
||||
|
||||
|
@ -124,7 +133,7 @@ impl BloomSettings {
|
|||
low_frequency_boost: 0.7,
|
||||
low_frequency_boost_curvature: 0.95,
|
||||
high_pass_frequency: 1.0,
|
||||
prefilter_settings: BloomPrefilterSettings {
|
||||
prefilter: BloomPrefilter {
|
||||
threshold: 0.0,
|
||||
threshold_softness: 0.0,
|
||||
},
|
||||
|
@ -139,7 +148,7 @@ impl BloomSettings {
|
|||
low_frequency_boost: 0.7,
|
||||
low_frequency_boost_curvature: 0.95,
|
||||
high_pass_frequency: 1.0,
|
||||
prefilter_settings: BloomPrefilterSettings {
|
||||
prefilter: BloomPrefilter {
|
||||
threshold: 0.6,
|
||||
threshold_softness: 0.2,
|
||||
},
|
||||
|
@ -154,7 +163,7 @@ impl BloomSettings {
|
|||
low_frequency_boost: 0.0,
|
||||
low_frequency_boost_curvature: 0.0,
|
||||
high_pass_frequency: 1.0 / 3.0,
|
||||
prefilter_settings: BloomPrefilterSettings {
|
||||
prefilter: BloomPrefilter {
|
||||
threshold: 0.0,
|
||||
threshold_softness: 0.0,
|
||||
},
|
||||
|
@ -164,7 +173,7 @@ impl BloomSettings {
|
|||
};
|
||||
}
|
||||
|
||||
impl Default for BloomSettings {
|
||||
impl Default for Bloom {
|
||||
fn default() -> Self {
|
||||
Self::NATURAL
|
||||
}
|
||||
|
@ -179,7 +188,7 @@ impl Default for BloomSettings {
|
|||
/// * Changing these settings makes it easy to make the final result look worse
|
||||
/// * Non-default prefilter settings should be used in conjunction with [`BloomCompositeMode::Additive`]
|
||||
#[derive(Default, Clone, Reflect)]
|
||||
pub struct BloomPrefilterSettings {
|
||||
pub struct BloomPrefilter {
|
||||
/// Baseline of the quadratic threshold curve (default: 0.0).
|
||||
///
|
||||
/// RGB values under the threshold curve will not contribute to the effect.
|
||||
|
@ -194,19 +203,22 @@ pub struct BloomPrefilterSettings {
|
|||
pub threshold_softness: f32,
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.15.0", note = "Renamed to `BloomPrefilter`")]
|
||||
pub type BloomPrefilterSettings = BloomPrefilter;
|
||||
|
||||
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, Copy)]
|
||||
pub enum BloomCompositeMode {
|
||||
EnergyConserving,
|
||||
Additive,
|
||||
}
|
||||
|
||||
impl ExtractComponent for BloomSettings {
|
||||
impl ExtractComponent for Bloom {
|
||||
type QueryData = (&'static Self, &'static Camera);
|
||||
|
||||
type QueryFilter = ();
|
||||
type Out = (Self, BloomUniforms);
|
||||
|
||||
fn extract_component((settings, camera): QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
|
||||
fn extract_component((bloom, camera): QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
|
||||
match (
|
||||
camera.physical_viewport_rect(),
|
||||
camera.physical_viewport_size(),
|
||||
|
@ -215,8 +227,8 @@ impl ExtractComponent for BloomSettings {
|
|||
camera.hdr,
|
||||
) {
|
||||
(Some(URect { min: origin, .. }), Some(size), Some(target_size), true, true) => {
|
||||
let threshold = settings.prefilter_settings.threshold;
|
||||
let threshold_softness = settings.prefilter_settings.threshold_softness;
|
||||
let threshold = bloom.prefilter.threshold;
|
||||
let threshold_softness = bloom.prefilter.threshold_softness;
|
||||
let knee = threshold * threshold_softness.clamp(0.0, 1.0);
|
||||
|
||||
let uniform = BloomUniforms {
|
||||
|
@ -229,11 +241,13 @@ impl ExtractComponent for BloomSettings {
|
|||
viewport: UVec4::new(origin.x, origin.y, size.x, size.y).as_vec4()
|
||||
/ UVec4::new(target_size.x, target_size.y, target_size.x, target_size.y)
|
||||
.as_vec4(),
|
||||
aspect: AspectRatio::from_pixels(size.x, size.y).into(),
|
||||
uv_offset: settings.uv_offset,
|
||||
aspect: AspectRatio::try_from_pixels(size.x, size.y)
|
||||
.expect("Valid screen size values for Bloom settings")
|
||||
.ratio(),
|
||||
uv_offset: bloom.uv_offset,
|
||||
};
|
||||
|
||||
Some((settings.clone(), uniform))
|
||||
Some((bloom.clone(), uniform))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::{
|
||||
downsampling_pipeline::BloomUniforms, BloomCompositeMode, BloomSettings, BLOOM_SHADER_HANDLE,
|
||||
downsampling_pipeline::BloomUniforms, Bloom, BloomCompositeMode, BLOOM_SHADER_HANDLE,
|
||||
BLOOM_TEXTURE_FORMAT,
|
||||
};
|
||||
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||
|
@ -133,14 +133,14 @@ pub fn prepare_upsampling_pipeline(
|
|||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<BloomUpsamplingPipeline>>,
|
||||
pipeline: Res<BloomUpsamplingPipeline>,
|
||||
views: Query<(Entity, &BloomSettings)>,
|
||||
views: Query<(Entity, &Bloom)>,
|
||||
) {
|
||||
for (entity, settings) in &views {
|
||||
for (entity, bloom) in &views {
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&pipeline,
|
||||
BloomUpsamplingPipelineKeys {
|
||||
composite_mode: settings.composite_mode,
|
||||
composite_mode: bloom.composite_mode,
|
||||
final_pipeline: false,
|
||||
},
|
||||
);
|
||||
|
@ -149,7 +149,7 @@ pub fn prepare_upsampling_pipeline(
|
|||
&pipeline_cache,
|
||||
&pipeline,
|
||||
BloomUpsamplingPipelineKeys {
|
||||
composite_mode: settings.composite_mode,
|
||||
composite_mode: bloom.composite_mode,
|
||||
final_pipeline: true,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, Handle};
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::{
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},
|
||||
|
@ -34,10 +35,10 @@ pub use node::CasNode;
|
|||
/// based on the local contrast. This can help avoid over-sharpening areas with high contrast
|
||||
/// and under-sharpening areas with low contrast.
|
||||
///
|
||||
/// To use this, add the [`ContrastAdaptiveSharpeningSettings`] component to a 2D or 3D camera.
|
||||
/// To use this, add the [`ContrastAdaptiveSharpening`] component to a 2D or 3D camera.
|
||||
#[derive(Component, Reflect, Clone)]
|
||||
#[reflect(Component)]
|
||||
pub struct ContrastAdaptiveSharpeningSettings {
|
||||
#[reflect(Component, Default)]
|
||||
pub struct ContrastAdaptiveSharpening {
|
||||
/// Enable or disable sharpening.
|
||||
pub enabled: bool,
|
||||
/// Adjusts sharpening strength. Higher values increase the amount of sharpening.
|
||||
|
@ -54,9 +55,12 @@ pub struct ContrastAdaptiveSharpeningSettings {
|
|||
pub denoise: bool,
|
||||
}
|
||||
|
||||
impl Default for ContrastAdaptiveSharpeningSettings {
|
||||
#[deprecated(since = "0.15.0", note = "Renamed to `ContrastAdaptiveSharpening`")]
|
||||
pub type ContrastAdaptiveSharpeningSettings = ContrastAdaptiveSharpening;
|
||||
|
||||
impl Default for ContrastAdaptiveSharpening {
|
||||
fn default() -> Self {
|
||||
ContrastAdaptiveSharpeningSettings {
|
||||
ContrastAdaptiveSharpening {
|
||||
enabled: true,
|
||||
sharpening_strength: 0.6,
|
||||
denoise: false,
|
||||
|
@ -65,10 +69,10 @@ impl Default for ContrastAdaptiveSharpeningSettings {
|
|||
}
|
||||
|
||||
#[derive(Component, Default, Reflect, Clone)]
|
||||
#[reflect(Component)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct DenoiseCas(bool);
|
||||
|
||||
/// The uniform struct extracted from [`ContrastAdaptiveSharpeningSettings`] attached to a [`Camera`].
|
||||
/// The uniform struct extracted from [`ContrastAdaptiveSharpening`] attached to a [`Camera`].
|
||||
/// Will be available for use in the CAS shader.
|
||||
#[doc(hidden)]
|
||||
#[derive(Component, ShaderType, Clone)]
|
||||
|
@ -76,7 +80,7 @@ pub struct CasUniform {
|
|||
sharpness: f32,
|
||||
}
|
||||
|
||||
impl ExtractComponent for ContrastAdaptiveSharpeningSettings {
|
||||
impl ExtractComponent for ContrastAdaptiveSharpening {
|
||||
type QueryData = &'static Self;
|
||||
type QueryFilter = With<Camera>;
|
||||
type Out = (DenoiseCas, CasUniform);
|
||||
|
@ -110,9 +114,9 @@ impl Plugin for CasPlugin {
|
|||
Shader::from_wgsl
|
||||
);
|
||||
|
||||
app.register_type::<ContrastAdaptiveSharpeningSettings>();
|
||||
app.register_type::<ContrastAdaptiveSharpening>();
|
||||
app.add_plugins((
|
||||
ExtractComponentPlugin::<ContrastAdaptiveSharpeningSettings>::default(),
|
||||
ExtractComponentPlugin::<ContrastAdaptiveSharpening>::default(),
|
||||
UniformComponentPlugin::<CasUniform>::default(),
|
||||
));
|
||||
|
||||
|
@ -241,12 +245,12 @@ fn prepare_cas_pipelines(
|
|||
sharpening_pipeline: Res<CasPipeline>,
|
||||
views: Query<(Entity, &ExtractedView, &DenoiseCas), With<CasUniform>>,
|
||||
) {
|
||||
for (entity, view, cas_settings) in &views {
|
||||
for (entity, view, cas) in &views {
|
||||
let pipeline_id = pipelines.specialize(
|
||||
&pipeline_cache,
|
||||
&sharpening_pipeline,
|
||||
CasPipelineKey {
|
||||
denoise: cas_settings.0,
|
||||
denoise: cas.0,
|
||||
texture_format: if view.hdr {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::core_2d::graph::Core2d;
|
||||
use crate::tonemapping::{DebandDither, Tonemapping};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::prelude::Msaa;
|
||||
use bevy_render::{
|
||||
|
@ -16,17 +17,13 @@ use bevy_transform::prelude::{GlobalTransform, Transform};
|
|||
|
||||
#[derive(Component, Default, Reflect, Clone, ExtractComponent)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[reflect(Component)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct Camera2d;
|
||||
|
||||
#[derive(Bundle, Clone)]
|
||||
pub struct Camera2dBundle {
|
||||
pub camera: Camera,
|
||||
pub camera_render_graph: CameraRenderGraph,
|
||||
/// Note: default value for `OrthographicProjection.near` is `0.0`
|
||||
/// which makes objects on the screen plane invisible to 2D camera.
|
||||
/// `Camera2dBundle::default()` sets `near` to negative value,
|
||||
/// so be careful when initializing this field manually.
|
||||
pub projection: OrthographicProjection,
|
||||
pub visible_entities: VisibleEntities,
|
||||
pub frustum: Frustum,
|
||||
|
@ -41,11 +38,7 @@ pub struct Camera2dBundle {
|
|||
|
||||
impl Default for Camera2dBundle {
|
||||
fn default() -> Self {
|
||||
let projection = OrthographicProjection {
|
||||
far: 1000.,
|
||||
near: -1000.,
|
||||
..Default::default()
|
||||
};
|
||||
let projection = OrthographicProjection::default_2d();
|
||||
let transform = Transform::default();
|
||||
let frustum = projection.compute_frustum(&GlobalTransform::from(transform));
|
||||
Self {
|
||||
|
@ -77,7 +70,7 @@ impl Camera2dBundle {
|
|||
// the camera's translation by far and use a right handed coordinate system
|
||||
let projection = OrthographicProjection {
|
||||
far,
|
||||
..Default::default()
|
||||
..OrthographicProjection::default_2d()
|
||||
};
|
||||
let transform = Transform::from_xyz(0.0, 0.0, far - 0.1);
|
||||
let frustum = projection.compute_frustum(&GlobalTransform::from(transform));
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
tonemapping::{DebandDither, Tonemapping},
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
|
||||
use bevy_render::view::Msaa;
|
||||
use bevy_render::{
|
||||
|
@ -20,7 +21,7 @@ use serde::{Deserialize, Serialize};
|
|||
/// This means "forward" is -Z.
|
||||
#[derive(Component, Reflect, Clone, ExtractComponent)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[reflect(Component)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct Camera3d {
|
||||
/// The depth clear operation to perform for the main 3d pass.
|
||||
pub depth_load_op: Camera3dDepthLoadOp,
|
||||
|
@ -112,7 +113,7 @@ impl From<Camera3dDepthLoadOp> for LoadOp<f32> {
|
|||
///
|
||||
/// **Note:** You can get better-looking results at any quality level by enabling TAA. See: [`TemporalAntiAliasPlugin`](crate::experimental::taa::TemporalAntiAliasPlugin).
|
||||
#[derive(Resource, Default, Clone, Copy, Reflect, PartialEq, PartialOrd, Debug)]
|
||||
#[reflect(Resource)]
|
||||
#[reflect(Resource, Default, Debug, PartialEq)]
|
||||
pub enum ScreenSpaceTransmissionQuality {
|
||||
/// Best performance at the cost of quality. Suitable for lower end GPUs. (e.g. Mobile)
|
||||
///
|
||||
|
|
|
@ -52,7 +52,7 @@ struct DepthOfFieldParams {
|
|||
max_circle_of_confusion_diameter: f32,
|
||||
|
||||
/// The depth value that we clamp distant objects to. See the comment in
|
||||
/// [`DepthOfFieldSettings`] for more information.
|
||||
/// [`DepthOfField`] for more information.
|
||||
max_depth: f32,
|
||||
|
||||
/// Padding.
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//! [depth of field], and this term is used more generally in computer graphics
|
||||
//! to refer to the effect that simulates focus of lenses.
|
||||
//!
|
||||
//! Attaching [`DepthOfFieldSettings`] to a camera causes Bevy to simulate the
|
||||
//! Attaching [`DepthOfField`] to a camera causes Bevy to simulate the
|
||||
//! focus of a camera lens. Generally, Bevy's implementation of depth of field
|
||||
//! is optimized for speed instead of physical accuracy. Nevertheless, the depth
|
||||
//! of field effect in Bevy is based on physical parameters.
|
||||
|
@ -26,6 +26,7 @@ use bevy_ecs::{
|
|||
system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_math::ops;
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
camera::{PhysicalCameraParameters, Projection},
|
||||
|
@ -68,10 +69,13 @@ const DOF_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(203186118073921
|
|||
/// A plugin that adds support for the depth of field effect to Bevy.
|
||||
pub struct DepthOfFieldPlugin;
|
||||
|
||||
/// Depth of field settings.
|
||||
/// A component that enables a [depth of field] postprocessing effect when attached to a [`Camera3d`],
|
||||
/// simulating the focus of a camera lens.
|
||||
///
|
||||
/// [depth of field]: https://en.wikipedia.org/wiki/Depth_of_field
|
||||
#[derive(Component, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct DepthOfFieldSettings {
|
||||
pub struct DepthOfField {
|
||||
/// The appearance of the effect.
|
||||
pub mode: DepthOfFieldMode,
|
||||
|
||||
|
@ -112,6 +116,9 @@ pub struct DepthOfFieldSettings {
|
|||
pub max_depth: f32,
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.15.0", note = "Renamed to `DepthOfField`")]
|
||||
pub type DepthOfFieldSettings = DepthOfField;
|
||||
|
||||
/// Controls the appearance of the effect.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Default, PartialEq)]
|
||||
|
@ -156,11 +163,11 @@ pub struct DepthOfFieldUniform {
|
|||
coc_scale_factor: f32,
|
||||
|
||||
/// The maximum circle of confusion diameter in pixels. See the comment in
|
||||
/// [`DepthOfFieldSettings`] for more information.
|
||||
/// [`DepthOfField`] for more information.
|
||||
max_circle_of_confusion_diameter: f32,
|
||||
|
||||
/// The depth value that we clamp distant objects to. See the comment in
|
||||
/// [`DepthOfFieldSettings`] for more information.
|
||||
/// [`DepthOfField`] for more information.
|
||||
max_depth: f32,
|
||||
|
||||
/// Padding.
|
||||
|
@ -199,7 +206,7 @@ impl Plugin for DepthOfFieldPlugin {
|
|||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, DOF_SHADER_HANDLE, "dof.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.register_type::<DepthOfFieldSettings>();
|
||||
app.register_type::<DepthOfField>();
|
||||
app.register_type::<DepthOfFieldMode>();
|
||||
app.add_plugins(UniformComponentPlugin::<DepthOfFieldUniform>::default());
|
||||
|
||||
|
@ -339,7 +346,7 @@ impl ViewNode for DepthOfFieldNode {
|
|||
view_depth_texture,
|
||||
view_pipelines,
|
||||
view_bind_group_layouts,
|
||||
dof_settings_uniform_index,
|
||||
depth_of_field_uniform_index,
|
||||
auxiliary_dof_texture,
|
||||
): QueryItem<'w, Self::ViewQuery>,
|
||||
world: &'w World,
|
||||
|
@ -440,7 +447,11 @@ impl ViewNode for DepthOfFieldNode {
|
|||
// Set the per-view bind group.
|
||||
render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]);
|
||||
// Set the global bind group shared among all invocations of the shader.
|
||||
render_pass.set_bind_group(1, global_bind_group, &[dof_settings_uniform_index.index()]);
|
||||
render_pass.set_bind_group(
|
||||
1,
|
||||
global_bind_group,
|
||||
&[depth_of_field_uniform_index.index()],
|
||||
);
|
||||
// Render the full-screen pass.
|
||||
render_pass.draw(0..3, 0..1);
|
||||
}
|
||||
|
@ -449,7 +460,7 @@ impl ViewNode for DepthOfFieldNode {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for DepthOfFieldSettings {
|
||||
impl Default for DepthOfField {
|
||||
fn default() -> Self {
|
||||
let physical_camera_default = PhysicalCameraParameters::default();
|
||||
Self {
|
||||
|
@ -463,8 +474,8 @@ impl Default for DepthOfFieldSettings {
|
|||
}
|
||||
}
|
||||
|
||||
impl DepthOfFieldSettings {
|
||||
/// Initializes [`DepthOfFieldSettings`] from a set of
|
||||
impl DepthOfField {
|
||||
/// Initializes [`DepthOfField`] from a set of
|
||||
/// [`PhysicalCameraParameters`].
|
||||
///
|
||||
/// By passing the same [`PhysicalCameraParameters`] object to this function
|
||||
|
@ -472,10 +483,10 @@ impl DepthOfFieldSettings {
|
|||
/// results for both the exposure and depth of field effects can be
|
||||
/// obtained.
|
||||
///
|
||||
/// All fields of the returned [`DepthOfFieldSettings`] other than
|
||||
/// All fields of the returned [`DepthOfField`] other than
|
||||
/// `focal_length` and `aperture_f_stops` are set to their default values.
|
||||
pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfFieldSettings {
|
||||
DepthOfFieldSettings {
|
||||
pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfField {
|
||||
DepthOfField {
|
||||
sensor_height: camera.sensor_height,
|
||||
aperture_f_stops: camera.aperture_f_stops,
|
||||
..default()
|
||||
|
@ -521,10 +532,10 @@ impl FromWorld for DepthOfFieldGlobalBindGroupLayout {
|
|||
/// specific to each view.
|
||||
pub fn prepare_depth_of_field_view_bind_group_layouts(
|
||||
mut commands: Commands,
|
||||
view_targets: Query<(Entity, &DepthOfFieldSettings, &Msaa)>,
|
||||
view_targets: Query<(Entity, &DepthOfField, &Msaa)>,
|
||||
render_device: Res<RenderDevice>,
|
||||
) {
|
||||
for (view, dof_settings, msaa) in view_targets.iter() {
|
||||
for (view, depth_of_field, msaa) in view_targets.iter() {
|
||||
// Create the bind group layout for the passes that take one input.
|
||||
let single_input = render_device.create_bind_group_layout(
|
||||
Some("depth of field bind group layout (single input)"),
|
||||
|
@ -544,7 +555,7 @@ pub fn prepare_depth_of_field_view_bind_group_layouts(
|
|||
|
||||
// If needed, create the bind group layout for the second bokeh pass,
|
||||
// which takes two inputs. We only need to do this if bokeh is in use.
|
||||
let dual_input = match dof_settings.mode {
|
||||
let dual_input = match depth_of_field.mode {
|
||||
DepthOfFieldMode::Gaussian => None,
|
||||
DepthOfFieldMode::Bokeh => Some(render_device.create_bind_group_layout(
|
||||
Some("depth of field bind group layout (dual input)"),
|
||||
|
@ -581,7 +592,7 @@ pub fn prepare_depth_of_field_view_bind_group_layouts(
|
|||
/// need to set the appropriate flag to tell Bevy to make samplable depth
|
||||
/// buffers.
|
||||
pub fn configure_depth_of_field_view_targets(
|
||||
mut view_targets: Query<&mut Camera3d, With<DepthOfFieldSettings>>,
|
||||
mut view_targets: Query<&mut Camera3d, With<DepthOfField>>,
|
||||
) {
|
||||
for mut camera_3d in view_targets.iter_mut() {
|
||||
let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages);
|
||||
|
@ -595,10 +606,10 @@ pub fn configure_depth_of_field_view_targets(
|
|||
pub fn prepare_depth_of_field_global_bind_group(
|
||||
global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
|
||||
mut dof_bind_group: ResMut<DepthOfFieldGlobalBindGroup>,
|
||||
dof_settings_uniforms: Res<ComponentUniforms<DepthOfFieldUniform>>,
|
||||
depth_of_field_uniforms: Res<ComponentUniforms<DepthOfFieldUniform>>,
|
||||
render_device: Res<RenderDevice>,
|
||||
) {
|
||||
let Some(dof_settings_uniforms) = dof_settings_uniforms.binding() else {
|
||||
let Some(depth_of_field_uniforms) = depth_of_field_uniforms.binding() else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -606,7 +617,7 @@ pub fn prepare_depth_of_field_global_bind_group(
|
|||
Some("depth of field global bind group"),
|
||||
&global_bind_group_layout.layout,
|
||||
&BindGroupEntries::sequential((
|
||||
dof_settings_uniforms, // `dof_params`
|
||||
depth_of_field_uniforms, // `dof_params`
|
||||
&global_bind_group_layout.color_texture_sampler, // `color_texture_sampler`
|
||||
)),
|
||||
));
|
||||
|
@ -618,11 +629,11 @@ pub fn prepare_auxiliary_depth_of_field_textures(
|
|||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
mut texture_cache: ResMut<TextureCache>,
|
||||
mut view_targets: Query<(Entity, &ViewTarget, &DepthOfFieldSettings)>,
|
||||
mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>,
|
||||
) {
|
||||
for (entity, view_target, dof_settings) in view_targets.iter_mut() {
|
||||
for (entity, view_target, depth_of_field) in view_targets.iter_mut() {
|
||||
// An auxiliary texture is only needed for bokeh.
|
||||
if dof_settings.mode != DepthOfFieldMode::Bokeh {
|
||||
if depth_of_field.mode != DepthOfFieldMode::Bokeh {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -655,12 +666,12 @@ pub fn prepare_depth_of_field_pipelines(
|
|||
view_targets: Query<(
|
||||
Entity,
|
||||
&ExtractedView,
|
||||
&DepthOfFieldSettings,
|
||||
&DepthOfField,
|
||||
&ViewDepthOfFieldBindGroupLayouts,
|
||||
&Msaa,
|
||||
)>,
|
||||
) {
|
||||
for (entity, view, dof_settings, view_bind_group_layouts, msaa) in view_targets.iter() {
|
||||
for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() {
|
||||
let dof_pipeline = DepthOfFieldPipeline {
|
||||
view_bind_group_layouts: view_bind_group_layouts.clone(),
|
||||
global_bind_group_layout: global_bind_group_layout.layout.clone(),
|
||||
|
@ -670,7 +681,7 @@ pub fn prepare_depth_of_field_pipelines(
|
|||
let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off);
|
||||
|
||||
// Go ahead and specialize the pipelines.
|
||||
match dof_settings.mode {
|
||||
match depth_of_field.mode {
|
||||
DepthOfFieldMode::Gaussian => {
|
||||
commands
|
||||
.entity(entity)
|
||||
|
@ -795,10 +806,10 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline {
|
|||
}
|
||||
}
|
||||
|
||||
/// Extracts all [`DepthOfFieldSettings`] components into the render world.
|
||||
/// Extracts all [`DepthOfField`] components into the render world.
|
||||
fn extract_depth_of_field_settings(
|
||||
mut commands: Commands,
|
||||
mut query: Extract<Query<(Entity, &DepthOfFieldSettings, &Projection)>>,
|
||||
mut query: Extract<Query<(Entity, &DepthOfField, &Projection)>>,
|
||||
) {
|
||||
if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
|
||||
info_once!(
|
||||
|
@ -807,25 +818,25 @@ fn extract_depth_of_field_settings(
|
|||
return;
|
||||
}
|
||||
|
||||
for (entity, dof_settings, projection) in query.iter_mut() {
|
||||
for (entity, depth_of_field, projection) in query.iter_mut() {
|
||||
// Depth of field is nonsensical without a perspective projection.
|
||||
let Projection::Perspective(ref perspective_projection) = *projection else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let focal_length =
|
||||
calculate_focal_length(dof_settings.sensor_height, perspective_projection.fov);
|
||||
calculate_focal_length(depth_of_field.sensor_height, perspective_projection.fov);
|
||||
|
||||
// Convert `DepthOfFieldSettings` to `DepthOfFieldUniform`.
|
||||
// Convert `DepthOfField` to `DepthOfFieldUniform`.
|
||||
commands.get_or_spawn(entity).insert((
|
||||
*dof_settings,
|
||||
*depth_of_field,
|
||||
DepthOfFieldUniform {
|
||||
focal_distance: dof_settings.focal_distance,
|
||||
focal_distance: depth_of_field.focal_distance,
|
||||
focal_length,
|
||||
coc_scale_factor: focal_length * focal_length
|
||||
/ (dof_settings.sensor_height * dof_settings.aperture_f_stops),
|
||||
max_circle_of_confusion_diameter: dof_settings.max_circle_of_confusion_diameter,
|
||||
max_depth: dof_settings.max_depth,
|
||||
/ (depth_of_field.sensor_height * depth_of_field.aperture_f_stops),
|
||||
max_circle_of_confusion_diameter: depth_of_field.max_circle_of_confusion_diameter,
|
||||
max_depth: depth_of_field.max_depth,
|
||||
pad_a: 0,
|
||||
pad_b: 0,
|
||||
pad_c: 0,
|
||||
|
@ -838,7 +849,7 @@ fn extract_depth_of_field_settings(
|
|||
///
|
||||
/// See <https://photo.stackexchange.com/a/97218>.
|
||||
pub fn calculate_focal_length(sensor_height: f32, fov: f32) -> f32 {
|
||||
0.5 * sensor_height / f32::tan(0.5 * fov)
|
||||
0.5 * sensor_height / ops::tan(0.5 * fov)
|
||||
}
|
||||
|
||||
impl DepthOfFieldPipelines {
|
||||
|
|
|
@ -49,9 +49,12 @@ impl Sensitivity {
|
|||
}
|
||||
}
|
||||
|
||||
/// A component for enabling Fast Approximate Anti-Aliasing (FXAA)
|
||||
/// for a [`bevy_render::camera::Camera`].
|
||||
#[derive(Reflect, Component, Clone, ExtractComponent)]
|
||||
#[reflect(Component, Default)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[doc(alias = "FastApproximateAntiAliasing")]
|
||||
pub struct Fxaa {
|
||||
/// Enable render passes for FXAA.
|
||||
pub enabled: bool,
|
||||
|
@ -60,7 +63,7 @@ pub struct Fxaa {
|
|||
/// Use higher sensitivity for a slower, smoother, result.
|
||||
/// [`Ultra`](`Sensitivity::Ultra`) and [`Extreme`](`Sensitivity::Extreme`)
|
||||
/// settings can result in significant smearing and loss of detail.
|
||||
|
||||
///
|
||||
/// The minimum amount of local contrast required to apply algorithm.
|
||||
pub edge_threshold: Sensitivity,
|
||||
|
||||
|
|
|
@ -34,9 +34,10 @@ pub use skybox::Skybox;
|
|||
/// Expect bugs, missing features, compatibility issues, low performance, and/or future breaking changes.
|
||||
pub mod experimental {
|
||||
pub mod taa {
|
||||
#[allow(deprecated)]
|
||||
pub use crate::taa::{
|
||||
TemporalAntiAliasBundle, TemporalAntiAliasNode, TemporalAntiAliasPlugin,
|
||||
TemporalAntiAliasSettings,
|
||||
TemporalAntiAliasSettings, TemporalAntiAliasing,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,10 +33,10 @@ impl ViewNode for MotionBlurNode {
|
|||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
(view_target, pipeline_id, prepass_textures, settings, msaa): QueryItem<Self::ViewQuery>,
|
||||
(view_target, pipeline_id, prepass_textures, motion_blur, msaa): QueryItem<Self::ViewQuery>,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
if settings.samples == 0 || settings.shutter_angle <= 0.0 {
|
||||
if motion_blur.samples == 0 || motion_blur.shutter_angle <= 0.0 {
|
||||
return Ok(()); // We can skip running motion blur in these cases.
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ use std::ops::Range;
|
|||
use bevy_asset::UntypedAssetId;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::Mat4;
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::{
|
||||
render_phase::{
|
||||
|
@ -52,20 +53,24 @@ pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float
|
|||
|
||||
/// If added to a [`crate::prelude::Camera3d`] then depth values will be copied to a separate texture available to the main pass.
|
||||
#[derive(Component, Default, Reflect, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct DepthPrepass;
|
||||
|
||||
/// If added to a [`crate::prelude::Camera3d`] then vertex world normals will be copied to a separate texture available to the main pass.
|
||||
/// Normals will have normal map textures already applied.
|
||||
#[derive(Component, Default, Reflect, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct NormalPrepass;
|
||||
|
||||
/// If added to a [`crate::prelude::Camera3d`] then screen space motion vectors will be copied to a separate texture available to the main pass.
|
||||
#[derive(Component, Default, Reflect, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct MotionVectorPrepass;
|
||||
|
||||
/// If added to a [`crate::prelude::Camera3d`] then deferred materials will be rendered to the deferred gbuffer texture and will be available to subsequent passes.
|
||||
/// Note the default deferred lighting plugin also requires `DepthPrepass` to work correctly.
|
||||
#[derive(Component, Default, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct DeferredPrepass;
|
||||
|
||||
#[derive(Component, ShaderType, Clone)]
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
//! which have made SMAA less popular when advanced photorealistic rendering
|
||||
//! features are used in recent years.
|
||||
//!
|
||||
//! To use SMAA, add [`SmaaSettings`] to a [`bevy_render::camera::Camera`]. In a
|
||||
//! To use SMAA, add [`Smaa`] to a [`bevy_render::camera::Camera`]. In a
|
||||
//! pinch, you can simply use the default settings (via the [`Default`] trait)
|
||||
//! for a high-quality, high-performance appearance. When using SMAA, you will
|
||||
//! likely want set [`bevy_render::view::Msaa`] to [`bevy_render::view::Msaa::Off`]
|
||||
|
@ -95,17 +95,21 @@ const SMAA_SEARCH_LUT_TEXTURE_HANDLE: Handle<Image> = Handle::weak_from_u128(318
|
|||
/// Adds support for subpixel morphological antialiasing, or SMAA.
|
||||
pub struct SmaaPlugin;
|
||||
|
||||
/// Add this component to a [`bevy_render::camera::Camera`] to enable subpixel
|
||||
/// morphological antialiasing (SMAA).
|
||||
/// A component for enabling Subpixel Morphological Anti-Aliasing (SMAA)
|
||||
/// for a [`bevy_render::camera::Camera`].
|
||||
#[derive(Clone, Copy, Default, Component, Reflect, ExtractComponent)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct SmaaSettings {
|
||||
#[doc(alias = "SubpixelMorphologicalAntiAliasing")]
|
||||
pub struct Smaa {
|
||||
/// A predefined set of SMAA parameters: i.e. a quality level.
|
||||
///
|
||||
/// Generally, you can leave this at its default level.
|
||||
pub preset: SmaaPreset,
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.15.0", note = "Renamed to `Smaa`")]
|
||||
pub type SmaaSettings = Smaa;
|
||||
|
||||
/// A preset quality level for SMAA.
|
||||
///
|
||||
/// Higher values are slower but result in a higher-quality image.
|
||||
|
@ -339,8 +343,8 @@ impl Plugin for SmaaPlugin {
|
|||
.resource_mut::<bevy_asset::Assets<Image>>()
|
||||
.insert(SMAA_SEARCH_LUT_TEXTURE_HANDLE.id(), lut_placeholder());
|
||||
|
||||
app.add_plugins(ExtractComponentPlugin::<SmaaSettings>::default())
|
||||
.register_type::<SmaaSettings>();
|
||||
app.add_plugins(ExtractComponentPlugin::<Smaa>::default())
|
||||
.register_type::<Smaa>();
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
|
@ -614,13 +618,13 @@ fn prepare_smaa_pipelines(
|
|||
pipeline_cache: Res<PipelineCache>,
|
||||
mut specialized_render_pipelines: ResMut<SmaaSpecializedRenderPipelines>,
|
||||
smaa_pipelines: Res<SmaaPipelines>,
|
||||
view_targets: Query<(Entity, &ExtractedView, &SmaaSettings)>,
|
||||
view_targets: Query<(Entity, &ExtractedView, &Smaa)>,
|
||||
) {
|
||||
for (entity, view, settings) in &view_targets {
|
||||
for (entity, view, smaa) in &view_targets {
|
||||
let edge_detection_pipeline_id = specialized_render_pipelines.edge_detection.specialize(
|
||||
&pipeline_cache,
|
||||
&smaa_pipelines.edge_detection,
|
||||
settings.preset,
|
||||
smaa.preset,
|
||||
);
|
||||
|
||||
let blending_weight_calculation_pipeline_id = specialized_render_pipelines
|
||||
|
@ -628,7 +632,7 @@ fn prepare_smaa_pipelines(
|
|||
.specialize(
|
||||
&pipeline_cache,
|
||||
&smaa_pipelines.blending_weight_calculation,
|
||||
settings.preset,
|
||||
smaa.preset,
|
||||
);
|
||||
|
||||
let neighborhood_blending_pipeline_id = specialized_render_pipelines
|
||||
|
@ -642,7 +646,7 @@ fn prepare_smaa_pipelines(
|
|||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
},
|
||||
preset: settings.preset,
|
||||
preset: smaa.preset,
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -660,7 +664,7 @@ fn prepare_smaa_uniforms(
|
|||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
view_targets: Query<(Entity, &ExtractedView), With<SmaaSettings>>,
|
||||
view_targets: Query<(Entity, &ExtractedView), With<Smaa>>,
|
||||
mut smaa_info_buffer: ResMut<SmaaInfoUniformBuffer>,
|
||||
) {
|
||||
smaa_info_buffer.clear();
|
||||
|
@ -691,7 +695,7 @@ fn prepare_smaa_textures(
|
|||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
mut texture_cache: ResMut<TextureCache>,
|
||||
view_targets: Query<(Entity, &ExtractedCamera), (With<ExtractedView>, With<SmaaSettings>)>,
|
||||
view_targets: Query<(Entity, &ExtractedCamera), (With<ExtractedView>, With<Smaa>)>,
|
||||
) {
|
||||
for (entity, camera) in &view_targets {
|
||||
let Some(texture_size) = camera.physical_target_size else {
|
||||
|
@ -765,7 +769,7 @@ fn prepare_smaa_bind_groups(
|
|||
render_device: Res<RenderDevice>,
|
||||
smaa_pipelines: Res<SmaaPipelines>,
|
||||
images: Res<RenderAssets<GpuImage>>,
|
||||
view_targets: Query<(Entity, &SmaaTextures), (With<ExtractedView>, With<SmaaSettings>)>,
|
||||
view_targets: Query<(Entity, &SmaaTextures), (With<ExtractedView>, With<Smaa>)>,
|
||||
) {
|
||||
// Fetch the two lookup textures. These are bundled in this library.
|
||||
let (Some(search_texture), Some(area_texture)) = (
|
||||
|
|
|
@ -8,13 +8,14 @@ use bevy_app::{App, Plugin};
|
|||
use bevy_asset::{load_internal_asset, Handle};
|
||||
use bevy_core::FrameCount;
|
||||
use bevy_ecs::{
|
||||
prelude::{Bundle, Component, Entity},
|
||||
prelude::{Bundle, Component, Entity, ReflectComponent},
|
||||
query::{QueryItem, With},
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_math::vec2;
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::{
|
||||
camera::{ExtractedCamera, MipBias, TemporalJitter},
|
||||
|
@ -40,14 +41,14 @@ const TAA_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(656865235226276
|
|||
|
||||
/// Plugin for temporal anti-aliasing.
|
||||
///
|
||||
/// See [`TemporalAntiAliasSettings`] for more details.
|
||||
/// See [`TemporalAntiAliasing`] for more details.
|
||||
pub struct TemporalAntiAliasPlugin;
|
||||
|
||||
impl Plugin for TemporalAntiAliasPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, TAA_SHADER_HANDLE, "taa.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.register_type::<TemporalAntiAliasSettings>();
|
||||
app.register_type::<TemporalAntiAliasing>();
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
|
@ -88,7 +89,7 @@ impl Plugin for TemporalAntiAliasPlugin {
|
|||
/// Bundle to apply temporal anti-aliasing.
|
||||
#[derive(Bundle, Default, Clone)]
|
||||
pub struct TemporalAntiAliasBundle {
|
||||
pub settings: TemporalAntiAliasSettings,
|
||||
pub settings: TemporalAntiAliasing,
|
||||
pub jitter: TemporalJitter,
|
||||
pub depth_prepass: DepthPrepass,
|
||||
pub motion_vector_prepass: MotionVectorPrepass,
|
||||
|
@ -136,7 +137,9 @@ pub struct TemporalAntiAliasBundle {
|
|||
///
|
||||
/// If no [`MipBias`] component is attached to the camera, TAA will add a MipBias(-1.0) component.
|
||||
#[derive(Component, Reflect, Clone)]
|
||||
pub struct TemporalAntiAliasSettings {
|
||||
#[reflect(Component, Default)]
|
||||
#[doc(alias = "Taa")]
|
||||
pub struct TemporalAntiAliasing {
|
||||
/// Set to true to delete the saved temporal history (past frames).
|
||||
///
|
||||
/// Useful for preventing ghosting when the history is no longer
|
||||
|
@ -147,7 +150,10 @@ pub struct TemporalAntiAliasSettings {
|
|||
pub reset: bool,
|
||||
}
|
||||
|
||||
impl Default for TemporalAntiAliasSettings {
|
||||
#[deprecated(since = "0.15.0", note = "Renamed to `TemporalAntiAliasing`")]
|
||||
pub type TemporalAntiAliasSettings = TemporalAntiAliasing;
|
||||
|
||||
impl Default for TemporalAntiAliasing {
|
||||
fn default() -> Self {
|
||||
Self { reset: true }
|
||||
}
|
||||
|
@ -347,7 +353,7 @@ impl SpecializedRenderPipeline for TaaPipeline {
|
|||
|
||||
fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
|
||||
let mut cameras_3d = main_world
|
||||
.query_filtered::<(Entity, &Camera, &Projection, &mut TemporalAntiAliasSettings), (
|
||||
.query_filtered::<(Entity, &Camera, &Projection, &mut TemporalAntiAliasing), (
|
||||
With<Camera3d>,
|
||||
With<TemporalJitter>,
|
||||
With<DepthPrepass>,
|
||||
|
@ -367,10 +373,7 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld
|
|||
|
||||
fn prepare_taa_jitter_and_mip_bias(
|
||||
frame_count: Res<FrameCount>,
|
||||
mut query: Query<
|
||||
(Entity, &mut TemporalJitter, Option<&MipBias>),
|
||||
With<TemporalAntiAliasSettings>,
|
||||
>,
|
||||
mut query: Query<(Entity, &mut TemporalJitter, Option<&MipBias>), With<TemporalAntiAliasing>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
// Halton sequence (2, 3) - 0.5, skipping i = 0
|
||||
|
@ -407,7 +410,7 @@ fn prepare_taa_history_textures(
|
|||
mut texture_cache: ResMut<TextureCache>,
|
||||
render_device: Res<RenderDevice>,
|
||||
frame_count: Res<FrameCount>,
|
||||
views: Query<(Entity, &ExtractedCamera, &ExtractedView), With<TemporalAntiAliasSettings>>,
|
||||
views: Query<(Entity, &ExtractedCamera, &ExtractedView), With<TemporalAntiAliasing>>,
|
||||
) {
|
||||
for (entity, camera, view) in &views {
|
||||
if let Some(physical_target_size) = camera.physical_target_size {
|
||||
|
@ -461,7 +464,7 @@ fn prepare_taa_pipelines(
|
|||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<TaaPipeline>>,
|
||||
pipeline: Res<TaaPipeline>,
|
||||
views: Query<(Entity, &ExtractedView, &TemporalAntiAliasSettings)>,
|
||||
views: Query<(Entity, &ExtractedView, &TemporalAntiAliasing)>,
|
||||
) {
|
||||
for (entity, view, taa_settings) in &views {
|
||||
let mut pipeline_key = TaaPipelineKey {
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
|||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, Assets, Handle};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::std_traits::ReflectDefault;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::extract_component::{ExtractComponent, ExtractComponentPlugin};
|
||||
use bevy_render::extract_resource::{ExtractResource, ExtractResourcePlugin};
|
||||
|
@ -136,7 +137,7 @@ pub struct TonemappingPipeline {
|
|||
Component, Debug, Hash, Clone, Copy, Reflect, Default, ExtractComponent, PartialEq, Eq,
|
||||
)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[reflect(Component)]
|
||||
#[reflect(Component, Debug, Hash, Default, PartialEq)]
|
||||
pub enum Tonemapping {
|
||||
/// Bypass tonemapping.
|
||||
None,
|
||||
|
@ -391,7 +392,7 @@ pub fn prepare_view_tonemapping_pipelines(
|
|||
Component, Debug, Hash, Clone, Copy, Reflect, Default, ExtractComponent, PartialEq, Eq,
|
||||
)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[reflect(Component)]
|
||||
#[reflect(Component, Debug, Hash, Default, PartialEq)]
|
||||
pub enum DebandDither {
|
||||
#[default]
|
||||
Disabled,
|
||||
|
|
|
@ -17,8 +17,9 @@ use std::time::Duration;
|
|||
/// (`ci_testing_config.ron` by default) and executes its specified actions. For a reference of the
|
||||
/// allowed configuration, see [`CiTestingConfig`].
|
||||
///
|
||||
/// This plugin is included within `DefaultPlugins` and `MinimalPlugins` when the `bevy_ci_testing`
|
||||
/// feature is enabled. It is recommended to only used this plugin during testing (manual or
|
||||
/// This plugin is included within `DefaultPlugins`, `HeadlessPlugins` and `MinimalPlugins`
|
||||
/// when the `bevy_ci_testing` feature is enabled.
|
||||
/// It is recommended to only used this plugin during testing (manual or
|
||||
/// automatic), and disable it during regular development and for production builds.
|
||||
#[derive(Default)]
|
||||
pub struct CiTestingPlugin;
|
||||
|
|
|
@ -5,12 +5,14 @@ use bevy_asset::Handle;
|
|||
use bevy_color::Color;
|
||||
use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChangesMut,
|
||||
component::Component,
|
||||
query::With,
|
||||
schedule::{common_conditions::resource_changed, IntoSystemConfigs},
|
||||
system::{Commands, Query, Res, Resource},
|
||||
};
|
||||
use bevy_hierarchy::{BuildChildren, ChildBuild};
|
||||
use bevy_render::view::Visibility;
|
||||
use bevy_text::{Font, Text, TextSection, TextStyle};
|
||||
use bevy_ui::{
|
||||
node_bundles::{NodeBundle, TextBundle},
|
||||
|
@ -47,7 +49,7 @@ impl Plugin for FpsOverlayPlugin {
|
|||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
customize_text.run_if(resource_changed::<FpsOverlayConfig>),
|
||||
(customize_text, toggle_display).run_if(resource_changed::<FpsOverlayConfig>),
|
||||
update_text,
|
||||
),
|
||||
);
|
||||
|
@ -59,6 +61,8 @@ impl Plugin for FpsOverlayPlugin {
|
|||
pub struct FpsOverlayConfig {
|
||||
/// Configuration of text in the overlay.
|
||||
pub text_config: TextStyle,
|
||||
/// Displays the FPS overlay if true.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for FpsOverlayConfig {
|
||||
|
@ -69,6 +73,7 @@ impl Default for FpsOverlayConfig {
|
|||
font_size: 32.0,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,3 +126,15 @@ fn customize_text(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_display(
|
||||
overlay_config: Res<FpsOverlayConfig>,
|
||||
mut query: Query<&mut Visibility, With<FpsText>>,
|
||||
) {
|
||||
for mut visibility in &mut query {
|
||||
visibility.set_if_neq(match overlay_config.enabled {
|
||||
true => Visibility::Visible,
|
||||
false => Visibility::Hidden,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ fn update_debug_camera(
|
|||
projection: OrthographicProjection {
|
||||
far: 1000.0,
|
||||
viewport_origin: Vec2::new(0.0, 0.0),
|
||||
..default()
|
||||
..OrthographicProjection::default_3d()
|
||||
},
|
||||
camera: Camera {
|
||||
order: LAYOUT_DEBUG_CAMERA_ORDER,
|
||||
|
|
|
@ -34,6 +34,7 @@ serde = { version = "1", optional = true, default-features = false }
|
|||
thiserror = "1.0"
|
||||
nonmax = "0.5"
|
||||
arrayvec = { version = "0.7.4", optional = true }
|
||||
smallvec = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
|
|
|
@ -120,7 +120,8 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream {
|
|||
);
|
||||
|
||||
let filter_impl = quote! {
|
||||
impl #user_impl_generics #path::query::QueryFilter
|
||||
// SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access.
|
||||
unsafe impl #user_impl_generics #path::query::QueryFilter
|
||||
for #struct_name #user_ty_generics #user_where_clauses {
|
||||
const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*;
|
||||
|
||||
|
|
|
@ -499,6 +499,16 @@ impl Archetype {
|
|||
self.components.len()
|
||||
}
|
||||
|
||||
/// Gets an iterator of all of the components in the archetype, along with
|
||||
/// their archetype component ID.
|
||||
pub(crate) fn components_with_archetype_component_id(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (ComponentId, ArchetypeComponentId)> + '_ {
|
||||
self.components
|
||||
.iter()
|
||||
.map(|(component_id, info)| (*component_id, info.archetype_component_id))
|
||||
}
|
||||
|
||||
/// Fetches an immutable reference to the archetype's [`Edges`], a cache of
|
||||
/// archetypal relationships.
|
||||
#[inline]
|
||||
|
@ -766,7 +776,7 @@ pub struct Archetypes {
|
|||
/// find the archetype id by the archetype's components
|
||||
by_components: HashMap<ArchetypeComponents, ArchetypeId>,
|
||||
/// find all the archetypes that contain a component
|
||||
by_component: ComponentIndex,
|
||||
pub(crate) by_component: ComponentIndex,
|
||||
}
|
||||
|
||||
/// Metadata about how a component is stored in an [`Archetype`].
|
||||
|
|
|
@ -517,33 +517,30 @@ impl BundleInfo {
|
|||
let component_id = *self.component_ids.get_unchecked(bundle_component);
|
||||
match storage_type {
|
||||
StorageType::Table => {
|
||||
let column =
|
||||
// SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that
|
||||
// the target table contains the component.
|
||||
unsafe { table.get_column_mut(component_id).debug_checked_unwrap() };
|
||||
// SAFETY: bundle_component is a valid index for this bundle
|
||||
let status = unsafe { bundle_component_status.get_status(bundle_component) };
|
||||
// SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that
|
||||
// the target table contains the component.
|
||||
let column = table.get_column_mut(component_id).debug_checked_unwrap();
|
||||
match (status, insert_mode) {
|
||||
(ComponentStatus::Added, _) => {
|
||||
column.initialize(
|
||||
table_row,
|
||||
component_ptr,
|
||||
change_tick,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
(ComponentStatus::Existing, InsertMode::Replace) => {
|
||||
column.replace(
|
||||
table_row,
|
||||
component_ptr,
|
||||
change_tick,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
);
|
||||
}
|
||||
(ComponentStatus::Added, _) => column.initialize(
|
||||
table_row,
|
||||
component_ptr,
|
||||
change_tick,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
),
|
||||
(ComponentStatus::Existing, InsertMode::Replace) => column.replace(
|
||||
table_row,
|
||||
component_ptr,
|
||||
change_tick,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
),
|
||||
(ComponentStatus::Existing, InsertMode::Keep) => {
|
||||
column.drop(component_ptr);
|
||||
if let Some(drop_fn) = table.get_drop_for(component_id) {
|
||||
drop_fn(component_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1282,7 +1279,7 @@ impl<'w> BundleSpawner<'w> {
|
|||
unsafe { &mut self.world.world_mut().entities }
|
||||
}
|
||||
|
||||
/// # Safety:
|
||||
/// # Safety
|
||||
/// - `Self` must be dropped after running this function as it may invalidate internal pointers.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn flush_commands(&mut self) {
|
||||
|
@ -1347,11 +1344,13 @@ impl Bundles {
|
|||
}
|
||||
|
||||
/// # Safety
|
||||
/// A `BundleInfo` with the given `BundleId` must have been initialized for this instance of `Bundles`.
|
||||
/// A [`BundleInfo`] with the given [`BundleId`] must have been initialized for this instance of `Bundles`.
|
||||
pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo {
|
||||
self.bundle_infos.get_unchecked(id.0)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This [`BundleId`] must have been initialized with a single [`Component`] (via [`init_component_info`](Self::init_dynamic_info))
|
||||
pub(crate) unsafe fn get_storage_unchecked(&self, id: BundleId) -> StorageType {
|
||||
*self
|
||||
.dynamic_component_storages
|
||||
|
@ -1359,6 +1358,8 @@ impl Bundles {
|
|||
.debug_checked_unwrap()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// This [`BundleId`] must have been initialized with multiple [`Component`]s (via [`init_dynamic_info`](Self::init_dynamic_info))
|
||||
pub(crate) unsafe fn get_storages_unchecked(&mut self, id: BundleId) -> &mut Vec<StorageType> {
|
||||
self.dynamic_bundle_storages
|
||||
.get_mut(&id)
|
||||
|
|
|
@ -738,9 +738,9 @@ impl Debug for ComponentDescriptor {
|
|||
}
|
||||
|
||||
impl ComponentDescriptor {
|
||||
/// # SAFETY
|
||||
/// # Safety
|
||||
///
|
||||
/// `x` must points to a valid value of type `T`.
|
||||
/// `x` must point to a valid value of type `T`.
|
||||
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
|
||||
// SAFETY: Contract is required to be upheld by the caller.
|
||||
unsafe {
|
||||
|
|
|
@ -553,12 +553,14 @@ impl Entities {
|
|||
// Use one atomic subtract to grab a range of new IDs. The range might be
|
||||
// entirely nonnegative, meaning all IDs come from the freelist, or entirely
|
||||
// negative, meaning they are all new IDs to allocate, or a mix of both.
|
||||
let range_end = self
|
||||
.free_cursor
|
||||
// Unwrap: these conversions can only fail on platforms that don't support 64-bit atomics
|
||||
// and use AtomicIsize instead (see note on `IdCursor`).
|
||||
.fetch_sub(IdCursor::try_from(count).unwrap(), Ordering::Relaxed);
|
||||
let range_start = range_end - IdCursor::try_from(count).unwrap();
|
||||
let range_end = self.free_cursor.fetch_sub(
|
||||
IdCursor::try_from(count)
|
||||
.expect("64-bit atomic operations are not supported on this platform."),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
let range_start = range_end
|
||||
- IdCursor::try_from(count)
|
||||
.expect("64-bit atomic operations are not supported on this platform.");
|
||||
|
||||
let freelist_range = range_start.max(0) as usize..range_end.max(0) as usize;
|
||||
|
||||
|
@ -745,9 +747,9 @@ impl Entities {
|
|||
self.verify_flushed();
|
||||
|
||||
let freelist_size = *self.free_cursor.get_mut();
|
||||
// Unwrap: these conversions can only fail on platforms that don't support 64-bit atomics
|
||||
// and use AtomicIsize instead (see note on `IdCursor`).
|
||||
let shortfall = IdCursor::try_from(additional).unwrap() - freelist_size;
|
||||
let shortfall = IdCursor::try_from(additional)
|
||||
.expect("64-bit atomic operations are not supported on this platform.")
|
||||
- freelist_size;
|
||||
if shortfall > 0 {
|
||||
self.meta.reserve(shortfall as usize);
|
||||
}
|
||||
|
@ -1004,7 +1006,6 @@ impl EntityLocation {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::mem::size_of;
|
||||
|
||||
#[test]
|
||||
fn entity_niche_optimization() {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use crate as bevy_ecs;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_ecs::reflect::ReflectResource;
|
||||
use bevy_ecs::{
|
||||
event::{Event, EventCursor, EventId, EventInstance},
|
||||
system::Resource,
|
||||
};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_utils::detailed_trace;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
@ -43,7 +45,7 @@ use std::ops::{Deref, DerefMut};
|
|||
///
|
||||
/// // setup
|
||||
/// let mut events = Events::<MyEvent>::default();
|
||||
/// let mut reader = events.get_reader();
|
||||
/// let mut cursor = events.get_cursor();
|
||||
///
|
||||
/// // run this once per update/frame
|
||||
/// events.update();
|
||||
|
@ -52,12 +54,12 @@ use std::ops::{Deref, DerefMut};
|
|||
/// events.send(MyEvent { value: 1 });
|
||||
///
|
||||
/// // somewhere else: read the events
|
||||
/// for event in reader.read(&events) {
|
||||
/// for event in cursor.read(&events) {
|
||||
/// assert_eq!(event.value, 1)
|
||||
/// }
|
||||
///
|
||||
/// // events are only processed once per reader
|
||||
/// assert_eq!(reader.read(&events).count(), 0);
|
||||
/// assert_eq!(cursor.read(&events).count(), 0);
|
||||
/// ```
|
||||
///
|
||||
/// # Details
|
||||
|
@ -85,7 +87,7 @@ use std::ops::{Deref, DerefMut};
|
|||
/// [`EventWriter`]: super::EventWriter
|
||||
/// [`event_update_system`]: super::event_update_system
|
||||
#[derive(Debug, Resource)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource, Default))]
|
||||
pub struct Events<E: Event> {
|
||||
/// Holds the oldest still active events.
|
||||
/// Note that `a.start_event_count + a.len()` should always be equal to `events_b.start_event_count`.
|
||||
|
|
|
@ -51,7 +51,7 @@ use bevy_ecs::{
|
|||
/// //
|
||||
/// // NOTE: the event won't actually be sent until commands get applied during
|
||||
/// // apply_deferred.
|
||||
/// commands.add(|w: &mut World| {
|
||||
/// commands.queue(|w: &mut World| {
|
||||
/// w.send_event(MyEvent);
|
||||
/// });
|
||||
/// }
|
||||
|
|
|
@ -623,7 +623,6 @@ mod tests {
|
|||
.collect::<HashSet<_>>(),
|
||||
HashSet::from([(e1, A(1), B(3)), (e2, A(2), B(4))])
|
||||
);
|
||||
|
||||
assert_eq!(world.entity_mut(e1).take::<A>(), Some(A(1)));
|
||||
assert_eq!(
|
||||
world
|
||||
|
|
|
@ -7,11 +7,12 @@ mod trigger_event;
|
|||
pub use runner::*;
|
||||
pub use trigger_event::*;
|
||||
|
||||
use crate::entity::EntityHashMap;
|
||||
use crate::observer::entity_observer::ObservedBy;
|
||||
use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*};
|
||||
use crate::{component::ComponentId, prelude::*, world::DeferredWorld};
|
||||
use bevy_ptr::Ptr;
|
||||
use bevy_utils::{EntityHashMap, HashMap};
|
||||
use bevy_utils::HashMap;
|
||||
use std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
|
||||
|
@ -55,11 +56,37 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> {
|
|||
Ptr::from(&self.event)
|
||||
}
|
||||
|
||||
/// Returns the entity that triggered the observer, could be [`Entity::PLACEHOLDER`].
|
||||
/// Returns the [`Entity`] that triggered the observer, could be [`Entity::PLACEHOLDER`].
|
||||
pub fn entity(&self) -> Entity {
|
||||
self.trigger.entity
|
||||
}
|
||||
|
||||
/// Returns the [`Entity`] that observed the triggered event.
|
||||
/// This allows you to despawn the observer, ceasing observation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_ecs::prelude::{Commands, Trigger};
|
||||
/// #
|
||||
/// # struct MyEvent {
|
||||
/// # done: bool,
|
||||
/// # }
|
||||
/// #
|
||||
/// /// Handle `MyEvent` and if it is done, stop observation.
|
||||
/// fn my_observer(trigger: Trigger<MyEvent>, mut commands: Commands) {
|
||||
/// if trigger.event().done {
|
||||
/// commands.entity(trigger.observer()).despawn();
|
||||
/// return;
|
||||
/// }
|
||||
///
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
pub fn observer(&self) -> Entity {
|
||||
self.trigger.observer
|
||||
}
|
||||
|
||||
/// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities.
|
||||
///
|
||||
/// The path an event will propagate along is specified by its associated [`Traversal`] component. By default, events
|
||||
|
@ -152,7 +179,7 @@ pub struct ObserverTrigger {
|
|||
}
|
||||
|
||||
// Map between an observer entity and its runner
|
||||
type ObserverMap = EntityHashMap<Entity, ObserverRunner>;
|
||||
type ObserverMap = EntityHashMap<ObserverRunner>;
|
||||
|
||||
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component.
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -160,7 +187,7 @@ pub struct CachedComponentObservers {
|
|||
// Observers listening to triggers targeting this component
|
||||
map: ObserverMap,
|
||||
// Observers listening to triggers targeting this component on a specific entity
|
||||
entity_map: EntityHashMap<Entity, ObserverMap>,
|
||||
entity_map: EntityHashMap<ObserverMap>,
|
||||
}
|
||||
|
||||
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger.
|
||||
|
@ -171,7 +198,7 @@ pub struct CachedObservers {
|
|||
// Observers listening for this trigger fired at a specific component
|
||||
component_observers: HashMap<ComponentId, CachedComponentObservers>,
|
||||
// Observers listening for this trigger fired at a specific entity
|
||||
entity_observers: EntityHashMap<Entity, ObserverMap>,
|
||||
entity_observers: EntityHashMap<ObserverMap>,
|
||||
}
|
||||
|
||||
/// Metadata for observers. Stores a cache mapping trigger ids to the registered observers.
|
||||
|
@ -429,14 +456,17 @@ impl World {
|
|||
if observers.map.is_empty() && observers.entity_map.is_empty() {
|
||||
cache.component_observers.remove(component);
|
||||
if let Some(flag) = Observers::is_archetype_cached(event_type) {
|
||||
for archetype in &mut archetypes.archetypes {
|
||||
if archetype.contains(*component) {
|
||||
let no_longer_observed = archetype
|
||||
.components()
|
||||
.all(|id| !cache.component_observers.contains_key(&id));
|
||||
if let Some(by_component) = archetypes.by_component.get(component) {
|
||||
for archetype in by_component.keys() {
|
||||
let archetype = &mut archetypes.archetypes[archetype.index()];
|
||||
if archetype.contains(*component) {
|
||||
let no_longer_observed = archetype
|
||||
.components()
|
||||
.all(|id| !cache.component_observers.contains_key(&id));
|
||||
|
||||
if no_longer_observed {
|
||||
archetype.flags.set(flag, false);
|
||||
if no_longer_observed {
|
||||
archetype.flags.set(flag, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -450,6 +480,8 @@ impl World {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::vec;
|
||||
|
||||
use bevy_ptr::OwningPtr;
|
||||
|
||||
use crate as bevy_ecs;
|
||||
|
@ -476,13 +508,12 @@ mod tests {
|
|||
struct EventA;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct R(usize);
|
||||
struct Order(Vec<&'static str>);
|
||||
|
||||
impl R {
|
||||
impl Order {
|
||||
#[track_caller]
|
||||
fn assert_order(&mut self, count: usize) {
|
||||
assert_eq!(count, self.0);
|
||||
self.0 += 1;
|
||||
fn observed(&mut self, name: &'static str) {
|
||||
self.0.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -507,61 +538,72 @@ mod tests {
|
|||
#[test]
|
||||
fn observer_order_spawn_despawn() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
world.despawn(entity);
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add", "insert", "replace", "remove"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_order_insert_remove() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(2));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(3));
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
entity.insert(A);
|
||||
entity.remove::<A>();
|
||||
entity.flush();
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add", "insert", "replace", "remove"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_order_insert_remove_sparse() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<R>| res.assert_order(0));
|
||||
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<R>| res.assert_order(1));
|
||||
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<R>| res.assert_order(2));
|
||||
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<R>| res.assert_order(3));
|
||||
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<Order>| res.observed("add"));
|
||||
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<Order>| res.observed("insert"));
|
||||
world.observe(|_: Trigger<OnReplace, S>, mut res: ResMut<Order>| res.observed("replace"));
|
||||
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<Order>| res.observed("remove"));
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
entity.insert(S);
|
||||
entity.remove::<S>();
|
||||
entity.flush();
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add", "insert", "replace", "remove"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_order_replace() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<R>| res.assert_order(0));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add"));
|
||||
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<Order>| res.observed("insert"));
|
||||
world.observe(|_: Trigger<OnReplace, A>, mut res: ResMut<Order>| res.observed("replace"));
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove"));
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
// and therefore does not automatically flush.
|
||||
|
@ -570,53 +612,56 @@ mod tests {
|
|||
let mut entity = world.entity_mut(entity);
|
||||
entity.insert(A);
|
||||
entity.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["replace", "insert"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_order_recursive() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
world.observe(
|
||||
|obs: Trigger<OnAdd, A>, mut res: ResMut<R>, mut commands: Commands| {
|
||||
res.assert_order(0);
|
||||
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("add_a");
|
||||
commands.entity(obs.entity()).insert(B);
|
||||
},
|
||||
);
|
||||
world.observe(
|
||||
|obs: Trigger<OnRemove, A>, mut res: ResMut<R>, mut commands: Commands| {
|
||||
res.assert_order(2);
|
||||
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("remove_a");
|
||||
commands.entity(obs.entity()).remove::<B>();
|
||||
},
|
||||
);
|
||||
|
||||
world.observe(
|
||||
|obs: Trigger<OnAdd, B>, mut res: ResMut<R>, mut commands: Commands| {
|
||||
res.assert_order(1);
|
||||
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||
res.observed("add_b");
|
||||
commands.entity(obs.entity()).remove::<A>();
|
||||
},
|
||||
);
|
||||
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<R>| {
|
||||
res.assert_order(3);
|
||||
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<Order>| {
|
||||
res.observed("remove_b");
|
||||
});
|
||||
|
||||
let entity = world.spawn(A).flush();
|
||||
let entity = world.get_entity(entity).unwrap();
|
||||
assert!(!entity.contains::<A>());
|
||||
assert!(!entity.contains::<B>());
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add_a", "add_b", "remove_a", "remove_b"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_multiple_listeners() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_1"));
|
||||
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_2"));
|
||||
|
||||
world.spawn(A).flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["add_1", "add_2"], world.resource::<Order>().0);
|
||||
// Our A entity plus our two observers
|
||||
assert_eq!(world.entities().len(), 3);
|
||||
}
|
||||
|
@ -624,40 +669,44 @@ mod tests {
|
|||
#[test]
|
||||
fn observer_multiple_events() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
let on_remove = world.init_component::<OnRemove>();
|
||||
world.spawn(
|
||||
// SAFETY: OnAdd and OnRemove are both unit types, so this is safe
|
||||
unsafe {
|
||||
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1)
|
||||
.with_event(on_remove)
|
||||
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| {
|
||||
res.observed("add/remove");
|
||||
})
|
||||
.with_event(on_remove)
|
||||
},
|
||||
);
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
world.despawn(entity);
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["add/remove", "add/remove"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_multiple_components() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
world.init_component::<A>();
|
||||
world.init_component::<B>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| res.observed("add_ab"));
|
||||
|
||||
let entity = world.spawn(A).id();
|
||||
world.entity_mut(entity).insert(B);
|
||||
world.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["add_ab", "add_ab"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_despawn() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
|
||||
let observer = world
|
||||
.observe(|_: Trigger<OnAdd, A>| panic!("Observer triggered after being despawned."))
|
||||
|
@ -670,11 +719,11 @@ mod tests {
|
|||
#[test]
|
||||
fn observer_despawn_archetype_flags() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let entity = world.spawn((A, B)).flush();
|
||||
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<Order>| res.observed("remove_a"));
|
||||
|
||||
let observer = world
|
||||
.observe(|_: Trigger<OnRemove, B>| panic!("Observer triggered after being despawned."))
|
||||
|
@ -683,31 +732,31 @@ mod tests {
|
|||
|
||||
world.despawn(entity);
|
||||
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["remove_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_multiple_matches() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<Order>| res.observed("add_ab"));
|
||||
|
||||
world.spawn((A, B)).flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["add_ab"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_no_target() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
|
||||
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
|
||||
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
||||
assert_eq!(obs.entity(), Entity::PLACEHOLDER);
|
||||
res.0 += 1;
|
||||
res.observed("event_a");
|
||||
});
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -715,24 +764,24 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger(EventA);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_entity_routing() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
|
||||
let entity = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventA>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
|
||||
.id();
|
||||
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
|
||||
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
||||
assert_eq!(obs.entity(), entity);
|
||||
res.0 += 1;
|
||||
res.observed("a_2");
|
||||
});
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -740,17 +789,17 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventA, entity);
|
||||
world.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["a_2", "a_1"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_dynamic_component() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let component_id = world.init_component::<A>();
|
||||
world.spawn(
|
||||
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<R>| res.0 += 1)
|
||||
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<Order>| res.observed("event_a"))
|
||||
.with_component(component_id),
|
||||
);
|
||||
|
||||
|
@ -763,45 +812,45 @@ mod tests {
|
|||
|
||||
world.trigger_targets(EventA, entity);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_dynamic_trigger() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
let event_a = world.init_component::<EventA>();
|
||||
|
||||
world.spawn(ObserverState {
|
||||
// SAFETY: we registered `event_a` above and it matches the type of TriggerA
|
||||
descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) },
|
||||
runner: |mut world, _trigger, _ptr, _propagate| {
|
||||
world.resource_mut::<R>().0 += 1;
|
||||
world.resource_mut::<Order>().observed("event_a");
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
world.commands().add(
|
||||
world.commands().queue(
|
||||
// SAFETY: we registered `event_a` above and it matches the type of TriggerA
|
||||
unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) },
|
||||
);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event_a"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -809,22 +858,22 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, child);
|
||||
world.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["child", "parent"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_redundant_dispatch_same_entity() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -832,22 +881,25 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, [child, child]);
|
||||
world.flush();
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["child", "parent", "child", "parent"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_redundant_dispatch_parent_child() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -855,24 +907,27 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, [child, parent]);
|
||||
world.flush();
|
||||
assert_eq!(3, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["child", "parent", "parent"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_halt() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(
|
||||
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<R>| {
|
||||
res.0 += 1;
|
||||
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("child");
|
||||
trigger.propagate(false);
|
||||
},
|
||||
)
|
||||
|
@ -883,30 +938,30 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, child);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["child"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_join() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("parent"))
|
||||
.id();
|
||||
|
||||
let child_a = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| {
|
||||
res.0 += 1;
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("child_a");
|
||||
})
|
||||
.id();
|
||||
|
||||
let child_b = world
|
||||
.spawn(Parent(parent))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| {
|
||||
res.0 += 1;
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("child_b");
|
||||
})
|
||||
.id();
|
||||
|
||||
|
@ -915,17 +970,20 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, [child_a, child_b]);
|
||||
world.flush();
|
||||
assert_eq!(4, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["child_a", "parent", "child_b", "parent"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_no_next() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let entity = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("event"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -933,24 +991,26 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, entity);
|
||||
world.flush();
|
||||
assert_eq!(1, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_parallel_propagation() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
let parent_a = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("parent_a");
|
||||
})
|
||||
.id();
|
||||
|
||||
let child_a = world
|
||||
.spawn(Parent(parent_a))
|
||||
.observe(
|
||||
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<R>| {
|
||||
res.0 += 1;
|
||||
|mut trigger: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("child_a");
|
||||
trigger.propagate(false);
|
||||
},
|
||||
)
|
||||
|
@ -958,12 +1018,14 @@ mod tests {
|
|||
|
||||
let parent_b = world
|
||||
.spawn_empty()
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| {
|
||||
res.observed("parent_b");
|
||||
})
|
||||
.id();
|
||||
|
||||
let child_b = world
|
||||
.spawn(Parent(parent_b))
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1)
|
||||
.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("child_b"))
|
||||
.id();
|
||||
|
||||
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
|
||||
|
@ -971,15 +1033,18 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, [child_a, child_b]);
|
||||
world.flush();
|
||||
assert_eq!(3, world.resource::<R>().0);
|
||||
assert_eq!(
|
||||
vec!["child_a", "child_b", "parent_b"],
|
||||
world.resource::<Order>().0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_world() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(|_: Trigger<EventPropagating>, mut res: ResMut<R>| res.0 += 1);
|
||||
world.observe(|_: Trigger<EventPropagating>, mut res: ResMut<Order>| res.observed("event"));
|
||||
|
||||
let grandparent = world.spawn_empty().id();
|
||||
let parent = world.spawn(Parent(grandparent)).id();
|
||||
|
@ -990,18 +1055,18 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, child);
|
||||
world.flush();
|
||||
assert_eq!(3, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event", "event", "event"], world.resource::<Order>().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn observer_propagating_world_skipping() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<R>();
|
||||
world.init_resource::<Order>();
|
||||
|
||||
world.observe(
|
||||
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<R>| {
|
||||
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
|
||||
if query.get(trigger.entity()).is_ok() {
|
||||
res.0 += 1;
|
||||
res.observed("event");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -1015,6 +1080,6 @@ mod tests {
|
|||
world.flush();
|
||||
world.trigger_targets(EventPropagating, child);
|
||||
world.flush();
|
||||
assert_eq!(2, world.resource::<R>().0);
|
||||
assert_eq!(vec!["event", "event"], world.resource::<Order>().0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use std::any::Any;
|
||||
|
||||
use crate::{
|
||||
component::{ComponentHooks, ComponentId, StorageType},
|
||||
component::{ComponentHook, ComponentHooks, ComponentId, StorageType},
|
||||
observer::{ObserverDescriptor, ObserverTrigger},
|
||||
prelude::*,
|
||||
query::DebugCheckedUnwrap,
|
||||
|
@ -63,7 +65,7 @@ impl Component for ObserverState {
|
|||
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
hooks.on_add(|mut world, entity, _| {
|
||||
world.commands().add(move |world: &mut World| {
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
world.register_observer(entity);
|
||||
});
|
||||
});
|
||||
|
@ -76,7 +78,7 @@ impl Component for ObserverState {
|
|||
.as_mut()
|
||||
.descriptor,
|
||||
);
|
||||
world.commands().add(move |world: &mut World| {
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
world.unregister_observer(entity, descriptor);
|
||||
});
|
||||
});
|
||||
|
@ -257,22 +259,23 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
|||
/// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`].
|
||||
///
|
||||
/// When first added, [`Observer`] will also create an [`ObserverState`] component, which registers the observer with the [`World`] and
|
||||
/// serves as the "source of truth" of the observer. [`ObserverState`] can be used to filter for [`Observer`] [`Entity`]s, e.g.
|
||||
/// [`With<ObserverState>`].
|
||||
/// serves as the "source of truth" of the observer.
|
||||
///
|
||||
/// [`SystemParam`]: crate::system::SystemParam
|
||||
pub struct Observer<T: 'static, B: Bundle> {
|
||||
system: BoxedObserverSystem<T, B>,
|
||||
pub struct Observer {
|
||||
system: Box<dyn Any + Send + Sync + 'static>,
|
||||
descriptor: ObserverDescriptor,
|
||||
hook_on_add: ComponentHook,
|
||||
}
|
||||
|
||||
impl<E: Event, B: Bundle> Observer<E, B> {
|
||||
impl Observer {
|
||||
/// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered
|
||||
/// for _any_ entity (or no entity).
|
||||
pub fn new<M>(system: impl IntoObserverSystem<E, B, M>) -> Self {
|
||||
pub fn new<E: Event, B: Bundle, M, I: IntoObserverSystem<E, B, M>>(system: I) -> Self {
|
||||
Self {
|
||||
system: Box::new(IntoObserverSystem::into_system(system)),
|
||||
descriptor: Default::default(),
|
||||
hook_on_add: hook_on_add::<E, B, I::System>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,54 +311,20 @@ impl<E: Event, B: Bundle> Observer<E, B> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<E: Event, B: Bundle> Component for Observer<E, B> {
|
||||
impl Component for Observer {
|
||||
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
||||
fn register_component_hooks(hooks: &mut ComponentHooks) {
|
||||
hooks.on_add(|mut world, entity, _| {
|
||||
world.commands().add(move |world: &mut World| {
|
||||
let event_type = world.init_component::<E>();
|
||||
let mut components = Vec::new();
|
||||
B::component_ids(&mut world.components, &mut world.storages, &mut |id| {
|
||||
components.push(id);
|
||||
});
|
||||
let mut descriptor = ObserverDescriptor {
|
||||
events: vec![event_type],
|
||||
components,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Initialize System
|
||||
let system: *mut dyn ObserverSystem<E, B> =
|
||||
if let Some(mut observe) = world.get_mut::<Self>(entity) {
|
||||
descriptor.merge(&observe.descriptor);
|
||||
&mut *observe.system
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias
|
||||
unsafe {
|
||||
(*system).initialize(world);
|
||||
}
|
||||
|
||||
{
|
||||
let mut entity = world.entity_mut(entity);
|
||||
if let crate::world::Entry::Vacant(entry) = entity.entry::<ObserverState>() {
|
||||
entry.insert(ObserverState {
|
||||
descriptor,
|
||||
runner: observer_system_runner::<E, B>,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
hooks.on_add(|world, entity, _id| {
|
||||
let Some(observe) = world.get::<Self>(entity) else {
|
||||
return;
|
||||
};
|
||||
let hook = observe.hook_on_add;
|
||||
hook(world, entity, _id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`].
|
||||
pub type BoxedObserverSystem<E = (), B = ()> = Box<dyn ObserverSystem<E, B>>;
|
||||
|
||||
fn observer_system_runner<E: Event, B: Bundle>(
|
||||
fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
mut world: DeferredWorld,
|
||||
observer_trigger: ObserverTrigger,
|
||||
ptr: PtrMut,
|
||||
|
@ -395,23 +364,75 @@ fn observer_system_runner<E: Event, B: Bundle>(
|
|||
// This transmute is obviously not ideal, but it is safe. Ideally we can remove the
|
||||
// static constraint from ObserverSystem, but so far we have not found a way.
|
||||
let trigger: Trigger<'static, E, B> = unsafe { std::mem::transmute(trigger) };
|
||||
// SAFETY: Observer was triggered so must have an `Observer` component.
|
||||
let system = unsafe {
|
||||
&mut observer_cell
|
||||
.get_mut::<Observer<E, B>>()
|
||||
.debug_checked_unwrap()
|
||||
.system
|
||||
// SAFETY:
|
||||
// - observer was triggered so must have an `Observer` component.
|
||||
// - observer cannot be dropped or mutated until after the system pointer is already dropped.
|
||||
let system: *mut dyn ObserverSystem<E, B> = unsafe {
|
||||
let mut observe = observer_cell.get_mut::<Observer>().debug_checked_unwrap();
|
||||
let system = observe.system.downcast_mut::<S>().unwrap();
|
||||
&mut *system
|
||||
};
|
||||
|
||||
system.update_archetype_component_access(world);
|
||||
|
||||
// SAFETY:
|
||||
// - `update_archetype_component_access` was just called
|
||||
// - `update_archetype_component_access` is called first
|
||||
// - there are no outstanding references to world except a private component
|
||||
// - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld`
|
||||
// - system is the same type erased system from above
|
||||
unsafe {
|
||||
system.run_unsafe(trigger, world);
|
||||
system.queue_deferred(world.into_deferred());
|
||||
(*system).update_archetype_component_access(world);
|
||||
(*system).run_unsafe(trigger, world);
|
||||
(*system).queue_deferred(world.into_deferred());
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`ComponentHooks::on_add`).
|
||||
///
|
||||
/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters
|
||||
/// erased.
|
||||
///
|
||||
/// The type parameters of this function _must_ match those used to create the [`Observer`].
|
||||
/// As such, it is recommended to only use this function within the [`Observer::new`] method to
|
||||
/// ensure type parameters match.
|
||||
fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
mut world: DeferredWorld<'_>,
|
||||
entity: Entity,
|
||||
_: ComponentId,
|
||||
) {
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
let event_type = world.init_component::<E>();
|
||||
let mut components = Vec::new();
|
||||
B::component_ids(&mut world.components, &mut world.storages, &mut |id| {
|
||||
components.push(id);
|
||||
});
|
||||
let mut descriptor = ObserverDescriptor {
|
||||
events: vec![event_type],
|
||||
components,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Initialize System
|
||||
let system: *mut dyn ObserverSystem<E, B> =
|
||||
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
|
||||
descriptor.merge(&observe.descriptor);
|
||||
let system = observe.system.downcast_mut::<S>().unwrap();
|
||||
&mut *system
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias
|
||||
unsafe {
|
||||
(*system).initialize(world);
|
||||
}
|
||||
|
||||
{
|
||||
let mut entity = world.entity_mut(entity);
|
||||
if let crate::world::Entry::Vacant(entry) = entity.entry::<ObserverState>() {
|
||||
entry.insert(ObserverState {
|
||||
descriptor,
|
||||
runner: observer_system_runner::<E, B, S>,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -49,20 +49,22 @@ impl<'a, T: SparseSetIndex + Debug> Debug for FormattedBitSet<'a, T> {
|
|||
/// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions.
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub struct Access<T: SparseSetIndex> {
|
||||
/// All accessed components.
|
||||
/// All accessed components, or forbidden components if
|
||||
/// `Self::component_read_and_writes_inverted` is set.
|
||||
component_read_and_writes: FixedBitSet,
|
||||
/// The exclusively-accessed components.
|
||||
/// All exclusively-accessed components, or components that may not be
|
||||
/// exclusively accessed if `Self::component_writes_inverted` is set.
|
||||
component_writes: FixedBitSet,
|
||||
/// All accessed resources.
|
||||
resource_read_and_writes: FixedBitSet,
|
||||
/// The exclusively-accessed resources.
|
||||
resource_writes: FixedBitSet,
|
||||
/// Is `true` if this has access to all components.
|
||||
/// (Note that this does not include `Resources`)
|
||||
reads_all_components: bool,
|
||||
/// Is `true` if this has mutable access to all components.
|
||||
/// (Note that this does not include `Resources`)
|
||||
writes_all_components: bool,
|
||||
/// Is `true` if this component can read all components *except* those
|
||||
/// present in `Self::component_read_and_writes`.
|
||||
component_read_and_writes_inverted: bool,
|
||||
/// Is `true` if this component can write to all components *except* those
|
||||
/// present in `Self::component_writes`.
|
||||
component_writes_inverted: bool,
|
||||
/// Is `true` if this has access to all resources.
|
||||
/// This field is a performance optimization for `&World` (also harder to mess up for soundness).
|
||||
reads_all_resources: bool,
|
||||
|
@ -82,8 +84,8 @@ impl<T: SparseSetIndex> Clone for Access<T> {
|
|||
component_writes: self.component_writes.clone(),
|
||||
resource_read_and_writes: self.resource_read_and_writes.clone(),
|
||||
resource_writes: self.resource_writes.clone(),
|
||||
reads_all_components: self.reads_all_components,
|
||||
writes_all_components: self.writes_all_components,
|
||||
component_read_and_writes_inverted: self.component_read_and_writes_inverted,
|
||||
component_writes_inverted: self.component_writes_inverted,
|
||||
reads_all_resources: self.reads_all_resources,
|
||||
writes_all_resources: self.writes_all_resources,
|
||||
archetypal: self.archetypal.clone(),
|
||||
|
@ -98,8 +100,8 @@ impl<T: SparseSetIndex> Clone for Access<T> {
|
|||
self.resource_read_and_writes
|
||||
.clone_from(&source.resource_read_and_writes);
|
||||
self.resource_writes.clone_from(&source.resource_writes);
|
||||
self.reads_all_components = source.reads_all_components;
|
||||
self.writes_all_components = source.writes_all_components;
|
||||
self.component_read_and_writes_inverted = source.component_read_and_writes_inverted;
|
||||
self.component_writes_inverted = source.component_writes_inverted;
|
||||
self.reads_all_resources = source.reads_all_resources;
|
||||
self.writes_all_resources = source.writes_all_resources;
|
||||
self.archetypal.clone_from(&source.archetypal);
|
||||
|
@ -125,8 +127,11 @@ impl<T: SparseSetIndex + Debug> Debug for Access<T> {
|
|||
"resource_writes",
|
||||
&FormattedBitSet::<T>::new(&self.resource_writes),
|
||||
)
|
||||
.field("reads_all_components", &self.reads_all_components)
|
||||
.field("writes_all_components", &self.writes_all_components)
|
||||
.field(
|
||||
"component_read_and_writes_inverted",
|
||||
&self.component_read_and_writes_inverted,
|
||||
)
|
||||
.field("component_writes_inverted", &self.component_writes_inverted)
|
||||
.field("reads_all_resources", &self.reads_all_resources)
|
||||
.field("writes_all_resources", &self.writes_all_resources)
|
||||
.field("archetypal", &FormattedBitSet::<T>::new(&self.archetypal))
|
||||
|
@ -146,8 +151,8 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
Self {
|
||||
reads_all_resources: false,
|
||||
writes_all_resources: false,
|
||||
reads_all_components: false,
|
||||
writes_all_components: false,
|
||||
component_read_and_writes_inverted: false,
|
||||
component_writes_inverted: false,
|
||||
component_read_and_writes: FixedBitSet::new(),
|
||||
component_writes: FixedBitSet::new(),
|
||||
resource_read_and_writes: FixedBitSet::new(),
|
||||
|
@ -157,18 +162,33 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_component_sparse_set_index_read(&mut self, index: usize) {
|
||||
if !self.component_read_and_writes_inverted {
|
||||
self.component_read_and_writes.grow_and_insert(index);
|
||||
} else if index < self.component_read_and_writes.len() {
|
||||
self.component_read_and_writes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_component_sparse_set_index_write(&mut self, index: usize) {
|
||||
if !self.component_writes_inverted {
|
||||
self.component_writes.grow_and_insert(index);
|
||||
} else if index < self.component_writes.len() {
|
||||
self.component_writes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds access to the component given by `index`.
|
||||
pub fn add_component_read(&mut self, index: T) {
|
||||
self.component_read_and_writes
|
||||
.grow_and_insert(index.sparse_set_index());
|
||||
let sparse_set_index = index.sparse_set_index();
|
||||
self.add_component_sparse_set_index_read(sparse_set_index);
|
||||
}
|
||||
|
||||
/// Adds exclusive access to the component given by `index`.
|
||||
pub fn add_component_write(&mut self, index: T) {
|
||||
self.component_read_and_writes
|
||||
.grow_and_insert(index.sparse_set_index());
|
||||
self.component_writes
|
||||
.grow_and_insert(index.sparse_set_index());
|
||||
let sparse_set_index = index.sparse_set_index();
|
||||
self.add_component_sparse_set_index_read(sparse_set_index);
|
||||
self.add_component_sparse_set_index_write(sparse_set_index);
|
||||
}
|
||||
|
||||
/// Adds access to the resource given by `index`.
|
||||
|
@ -185,6 +205,49 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
.grow_and_insert(index.sparse_set_index());
|
||||
}
|
||||
|
||||
fn remove_component_sparse_set_index_read(&mut self, index: usize) {
|
||||
if self.component_read_and_writes_inverted {
|
||||
self.component_read_and_writes.grow_and_insert(index);
|
||||
} else if index < self.component_read_and_writes.len() {
|
||||
self.component_read_and_writes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_component_sparse_set_index_write(&mut self, index: usize) {
|
||||
if self.component_writes_inverted {
|
||||
self.component_writes.grow_and_insert(index);
|
||||
} else if index < self.component_writes.len() {
|
||||
self.component_writes.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes both read and write access to the component given by `index`.
|
||||
///
|
||||
/// Because this method corresponds to the set difference operator ∖, it can
|
||||
/// create complicated logical formulas that you should verify correctness
|
||||
/// of. For example, A ∪ (B ∖ A) isn't equivalent to (A ∪ B) ∖ A, so you
|
||||
/// can't replace a call to `remove_component_read` followed by a call to
|
||||
/// `extend` with a call to `extend` followed by a call to
|
||||
/// `remove_component_read`.
|
||||
pub fn remove_component_read(&mut self, index: T) {
|
||||
let sparse_set_index = index.sparse_set_index();
|
||||
self.remove_component_sparse_set_index_write(sparse_set_index);
|
||||
self.remove_component_sparse_set_index_read(sparse_set_index);
|
||||
}
|
||||
|
||||
/// Removes write access to the component given by `index`.
|
||||
///
|
||||
/// Because this method corresponds to the set difference operator ∖, it can
|
||||
/// create complicated logical formulas that you should verify correctness
|
||||
/// of. For example, A ∪ (B ∖ A) isn't equivalent to (A ∪ B) ∖ A, so you
|
||||
/// can't replace a call to `remove_component_write` followed by a call to
|
||||
/// `extend` with a call to `extend` followed by a call to
|
||||
/// `remove_component_write`.
|
||||
pub fn remove_component_write(&mut self, index: T) {
|
||||
let sparse_set_index = index.sparse_set_index();
|
||||
self.remove_component_sparse_set_index_write(sparse_set_index);
|
||||
}
|
||||
|
||||
/// Adds an archetypal (indirect) access to the component given by `index`.
|
||||
///
|
||||
/// This is for components whose values are not accessed (and thus will never cause conflicts),
|
||||
|
@ -199,25 +262,25 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
|
||||
/// Returns `true` if this can access the component given by `index`.
|
||||
pub fn has_component_read(&self, index: T) -> bool {
|
||||
self.reads_all_components
|
||||
|| self
|
||||
self.component_read_and_writes_inverted
|
||||
^ self
|
||||
.component_read_and_writes
|
||||
.contains(index.sparse_set_index())
|
||||
}
|
||||
|
||||
/// Returns `true` if this can access any component.
|
||||
pub fn has_any_component_read(&self) -> bool {
|
||||
self.reads_all_components || !self.component_read_and_writes.is_clear()
|
||||
self.component_read_and_writes_inverted || !self.component_read_and_writes.is_clear()
|
||||
}
|
||||
|
||||
/// Returns `true` if this can exclusively access the component given by `index`.
|
||||
pub fn has_component_write(&self, index: T) -> bool {
|
||||
self.writes_all_components || self.component_writes.contains(index.sparse_set_index())
|
||||
self.component_writes_inverted ^ self.component_writes.contains(index.sparse_set_index())
|
||||
}
|
||||
|
||||
/// Returns `true` if this accesses any component mutably.
|
||||
pub fn has_any_component_write(&self) -> bool {
|
||||
self.writes_all_components || !self.component_writes.is_clear()
|
||||
self.component_writes_inverted || !self.component_writes.is_clear()
|
||||
}
|
||||
|
||||
/// Returns `true` if this can access the resource given by `index`.
|
||||
|
@ -258,14 +321,16 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// Sets this as having access to all components (i.e. `EntityRef`).
|
||||
#[inline]
|
||||
pub fn read_all_components(&mut self) {
|
||||
self.reads_all_components = true;
|
||||
self.component_read_and_writes_inverted = true;
|
||||
self.component_read_and_writes.clear();
|
||||
}
|
||||
|
||||
/// Sets this as having mutable access to all components (i.e. `EntityMut`).
|
||||
#[inline]
|
||||
pub fn write_all_components(&mut self) {
|
||||
self.reads_all_components = true;
|
||||
self.writes_all_components = true;
|
||||
self.read_all_components();
|
||||
self.component_writes_inverted = true;
|
||||
self.component_writes.clear();
|
||||
}
|
||||
|
||||
/// Sets this as having access to all resources (i.e. `&World`).
|
||||
|
@ -298,13 +363,13 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// Returns `true` if this has access to all components (i.e. `EntityRef`).
|
||||
#[inline]
|
||||
pub fn has_read_all_components(&self) -> bool {
|
||||
self.reads_all_components
|
||||
self.component_read_and_writes_inverted && self.component_read_and_writes.is_clear()
|
||||
}
|
||||
|
||||
/// Returns `true` if this has write access to all components (i.e. `EntityMut`).
|
||||
#[inline]
|
||||
pub fn has_write_all_components(&self) -> bool {
|
||||
self.writes_all_components
|
||||
self.component_writes_inverted && self.component_writes.is_clear()
|
||||
}
|
||||
|
||||
/// Returns `true` if this has access to all resources (i.e. `EntityRef`).
|
||||
|
@ -332,7 +397,7 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// Removes all writes.
|
||||
pub fn clear_writes(&mut self) {
|
||||
self.writes_all_resources = false;
|
||||
self.writes_all_components = false;
|
||||
self.component_writes_inverted = false;
|
||||
self.component_writes.clear();
|
||||
self.resource_writes.clear();
|
||||
}
|
||||
|
@ -341,8 +406,8 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
pub fn clear(&mut self) {
|
||||
self.reads_all_resources = false;
|
||||
self.writes_all_resources = false;
|
||||
self.reads_all_components = false;
|
||||
self.writes_all_components = false;
|
||||
self.component_read_and_writes_inverted = false;
|
||||
self.component_writes_inverted = false;
|
||||
self.component_read_and_writes.clear();
|
||||
self.component_writes.clear();
|
||||
self.resource_read_and_writes.clear();
|
||||
|
@ -351,13 +416,72 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
|
||||
/// Adds all access from `other`.
|
||||
pub fn extend(&mut self, other: &Access<T>) {
|
||||
let component_read_and_writes_inverted =
|
||||
self.component_read_and_writes_inverted || other.component_read_and_writes_inverted;
|
||||
let component_writes_inverted =
|
||||
self.component_writes_inverted || other.component_writes_inverted;
|
||||
|
||||
match (
|
||||
self.component_read_and_writes_inverted,
|
||||
other.component_read_and_writes_inverted,
|
||||
) {
|
||||
(true, true) => {
|
||||
self.component_read_and_writes
|
||||
.intersect_with(&other.component_read_and_writes);
|
||||
}
|
||||
(true, false) => {
|
||||
self.component_read_and_writes
|
||||
.difference_with(&other.component_read_and_writes);
|
||||
}
|
||||
(false, true) => {
|
||||
// We have to grow here because the new bits are going to get flipped to 1.
|
||||
self.component_read_and_writes.grow(
|
||||
self.component_read_and_writes
|
||||
.len()
|
||||
.max(other.component_read_and_writes.len()),
|
||||
);
|
||||
self.component_read_and_writes.toggle_range(..);
|
||||
self.component_read_and_writes
|
||||
.intersect_with(&other.component_read_and_writes);
|
||||
}
|
||||
(false, false) => {
|
||||
self.component_read_and_writes
|
||||
.union_with(&other.component_read_and_writes);
|
||||
}
|
||||
}
|
||||
|
||||
match (
|
||||
self.component_writes_inverted,
|
||||
other.component_writes_inverted,
|
||||
) {
|
||||
(true, true) => {
|
||||
self.component_writes
|
||||
.intersect_with(&other.component_writes);
|
||||
}
|
||||
(true, false) => {
|
||||
self.component_writes
|
||||
.difference_with(&other.component_writes);
|
||||
}
|
||||
(false, true) => {
|
||||
// We have to grow here because the new bits are going to get flipped to 1.
|
||||
self.component_writes.grow(
|
||||
self.component_writes
|
||||
.len()
|
||||
.max(other.component_writes.len()),
|
||||
);
|
||||
self.component_writes.toggle_range(..);
|
||||
self.component_writes
|
||||
.intersect_with(&other.component_writes);
|
||||
}
|
||||
(false, false) => {
|
||||
self.component_writes.union_with(&other.component_writes);
|
||||
}
|
||||
}
|
||||
|
||||
self.reads_all_resources = self.reads_all_resources || other.reads_all_resources;
|
||||
self.writes_all_resources = self.writes_all_resources || other.writes_all_resources;
|
||||
self.reads_all_components = self.reads_all_components || other.reads_all_components;
|
||||
self.writes_all_components = self.writes_all_components || other.writes_all_components;
|
||||
self.component_read_and_writes
|
||||
.union_with(&other.component_read_and_writes);
|
||||
self.component_writes.union_with(&other.component_writes);
|
||||
self.component_read_and_writes_inverted = component_read_and_writes_inverted;
|
||||
self.component_writes_inverted = component_writes_inverted;
|
||||
self.resource_read_and_writes
|
||||
.union_with(&other.resource_read_and_writes);
|
||||
self.resource_writes.union_with(&other.resource_writes);
|
||||
|
@ -369,27 +493,48 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// [`Access`] instances are incompatible if one can write
|
||||
/// an element that the other can read or write.
|
||||
pub fn is_components_compatible(&self, other: &Access<T>) -> bool {
|
||||
if self.writes_all_components {
|
||||
return !other.has_any_component_read();
|
||||
// We have a conflict if we write and they read or write, or if they
|
||||
// write and we read or write.
|
||||
for (
|
||||
lhs_writes,
|
||||
rhs_reads_and_writes,
|
||||
lhs_writes_inverted,
|
||||
rhs_reads_and_writes_inverted,
|
||||
) in [
|
||||
(
|
||||
&self.component_writes,
|
||||
&other.component_read_and_writes,
|
||||
self.component_writes_inverted,
|
||||
other.component_read_and_writes_inverted,
|
||||
),
|
||||
(
|
||||
&other.component_writes,
|
||||
&self.component_read_and_writes,
|
||||
other.component_writes_inverted,
|
||||
self.component_read_and_writes_inverted,
|
||||
),
|
||||
] {
|
||||
match (lhs_writes_inverted, rhs_reads_and_writes_inverted) {
|
||||
(true, true) => return false,
|
||||
(false, true) => {
|
||||
if !lhs_writes.is_subset(rhs_reads_and_writes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
if !rhs_reads_and_writes.is_subset(lhs_writes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(false, false) => {
|
||||
if !lhs_writes.is_disjoint(rhs_reads_and_writes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if other.writes_all_components {
|
||||
return !self.has_any_component_read();
|
||||
}
|
||||
|
||||
if self.reads_all_components {
|
||||
return !other.has_any_component_write();
|
||||
}
|
||||
|
||||
if other.reads_all_components {
|
||||
return !self.has_any_component_write();
|
||||
}
|
||||
|
||||
self.component_writes
|
||||
.is_disjoint(&other.component_read_and_writes)
|
||||
&& other
|
||||
.component_writes
|
||||
.is_disjoint(&self.component_read_and_writes)
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `true` if the access and `other` can be active at the same time,
|
||||
|
@ -432,25 +577,48 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
/// Returns `true` if the set's component access is a subset of another, i.e. `other`'s component access
|
||||
/// contains at least all the values in `self`.
|
||||
pub fn is_subset_components(&self, other: &Access<T>) -> bool {
|
||||
if self.writes_all_components {
|
||||
return other.writes_all_components;
|
||||
for (
|
||||
our_components,
|
||||
their_components,
|
||||
our_components_inverted,
|
||||
their_components_inverted,
|
||||
) in [
|
||||
(
|
||||
&self.component_read_and_writes,
|
||||
&other.component_read_and_writes,
|
||||
self.component_read_and_writes_inverted,
|
||||
other.component_read_and_writes_inverted,
|
||||
),
|
||||
(
|
||||
&self.component_writes,
|
||||
&other.component_writes,
|
||||
self.component_writes_inverted,
|
||||
other.component_writes_inverted,
|
||||
),
|
||||
] {
|
||||
match (our_components_inverted, their_components_inverted) {
|
||||
(true, true) => {
|
||||
if !their_components.is_subset(our_components) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
return false;
|
||||
}
|
||||
(false, true) => {
|
||||
if !our_components.is_disjoint(their_components) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
(false, false) => {
|
||||
if !our_components.is_subset(their_components) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if other.writes_all_components {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.reads_all_components {
|
||||
return other.reads_all_components;
|
||||
}
|
||||
|
||||
if other.reads_all_components {
|
||||
return self.component_writes.is_subset(&other.component_writes);
|
||||
}
|
||||
|
||||
self.component_read_and_writes
|
||||
.is_subset(&other.component_read_and_writes)
|
||||
&& self.component_writes.is_subset(&other.component_writes)
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `true` if the set's resource access is a subset of another, i.e. `other`'s resource access
|
||||
|
@ -483,30 +651,52 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
self.is_subset_components(other) && self.is_subset_resources(other)
|
||||
}
|
||||
|
||||
fn get_component_conflicts(&self, other: &Access<T>) -> AccessConflicts {
|
||||
let mut conflicts = FixedBitSet::new();
|
||||
|
||||
// We have a conflict if we write and they read or write, or if they
|
||||
// write and we read or write.
|
||||
for (
|
||||
lhs_writes,
|
||||
rhs_reads_and_writes,
|
||||
lhs_writes_inverted,
|
||||
rhs_reads_and_writes_inverted,
|
||||
) in [
|
||||
(
|
||||
&self.component_writes,
|
||||
&other.component_read_and_writes,
|
||||
self.component_writes_inverted,
|
||||
other.component_read_and_writes_inverted,
|
||||
),
|
||||
(
|
||||
&other.component_writes,
|
||||
&self.component_read_and_writes,
|
||||
other.component_writes_inverted,
|
||||
self.component_read_and_writes_inverted,
|
||||
),
|
||||
] {
|
||||
// There's no way that I can see to do this without a temporary.
|
||||
// Neither CNF nor DNF allows us to avoid one.
|
||||
let temp_conflicts: FixedBitSet =
|
||||
match (lhs_writes_inverted, rhs_reads_and_writes_inverted) {
|
||||
(true, true) => return AccessConflicts::All,
|
||||
(false, true) => lhs_writes.difference(rhs_reads_and_writes).collect(),
|
||||
(true, false) => rhs_reads_and_writes.difference(lhs_writes).collect(),
|
||||
(false, false) => lhs_writes.intersection(rhs_reads_and_writes).collect(),
|
||||
};
|
||||
conflicts.union_with(&temp_conflicts);
|
||||
}
|
||||
|
||||
AccessConflicts::Individual(conflicts)
|
||||
}
|
||||
|
||||
/// Returns a vector of elements that the access and `other` cannot access at the same time.
|
||||
pub fn get_conflicts(&self, other: &Access<T>) -> AccessConflicts {
|
||||
let mut conflicts = FixedBitSet::new();
|
||||
if self.reads_all_components {
|
||||
if other.writes_all_components {
|
||||
return AccessConflicts::All;
|
||||
}
|
||||
conflicts.extend(other.component_writes.ones());
|
||||
}
|
||||
let mut conflicts = match self.get_component_conflicts(other) {
|
||||
AccessConflicts::All => return AccessConflicts::All,
|
||||
AccessConflicts::Individual(conflicts) => conflicts,
|
||||
};
|
||||
|
||||
if other.reads_all_components {
|
||||
if self.writes_all_components {
|
||||
return AccessConflicts::All;
|
||||
}
|
||||
conflicts.extend(self.component_writes.ones());
|
||||
}
|
||||
|
||||
if self.writes_all_components {
|
||||
conflicts.extend(other.component_read_and_writes.ones());
|
||||
}
|
||||
|
||||
if other.writes_all_components {
|
||||
conflicts.extend(self.component_read_and_writes.ones());
|
||||
}
|
||||
if self.reads_all_resources {
|
||||
if other.writes_all_resources {
|
||||
return AccessConflicts::All;
|
||||
|
@ -528,14 +718,6 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
conflicts.extend(self.resource_read_and_writes.ones());
|
||||
}
|
||||
|
||||
conflicts.extend(
|
||||
self.component_writes
|
||||
.intersection(&other.component_read_and_writes),
|
||||
);
|
||||
conflicts.extend(
|
||||
self.component_read_and_writes
|
||||
.intersection(&other.component_writes),
|
||||
);
|
||||
conflicts.extend(
|
||||
self.resource_writes
|
||||
.intersection(&other.resource_read_and_writes),
|
||||
|
@ -547,25 +729,6 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
AccessConflicts::Individual(conflicts)
|
||||
}
|
||||
|
||||
/// Returns the indices of the components this has access to.
|
||||
pub fn component_reads_and_writes(&self) -> impl Iterator<Item = T> + '_ {
|
||||
self.component_read_and_writes
|
||||
.ones()
|
||||
.map(T::get_sparse_set_index)
|
||||
}
|
||||
|
||||
/// Returns the indices of the components this has non-exclusive access to.
|
||||
pub fn component_reads(&self) -> impl Iterator<Item = T> + '_ {
|
||||
self.component_read_and_writes
|
||||
.difference(&self.component_writes)
|
||||
.map(T::get_sparse_set_index)
|
||||
}
|
||||
|
||||
/// Returns the indices of the components this has exclusive access to.
|
||||
pub fn component_writes(&self) -> impl Iterator<Item = T> + '_ {
|
||||
self.component_writes.ones().map(T::get_sparse_set_index)
|
||||
}
|
||||
|
||||
/// Returns the indices of the components that this has an archetypal access to.
|
||||
///
|
||||
/// These are components whose values are not accessed (and thus will never cause conflicts),
|
||||
|
@ -577,6 +740,40 @@ impl<T: SparseSetIndex> Access<T> {
|
|||
pub fn archetypal(&self) -> impl Iterator<Item = T> + '_ {
|
||||
self.archetypal.ones().map(T::get_sparse_set_index)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the component IDs that this `Access` either
|
||||
/// reads and writes or can't read or write.
|
||||
///
|
||||
/// The returned flag specifies whether the list consists of the components
|
||||
/// that the access *can* read or write (false) or whether the list consists
|
||||
/// of the components that the access *can't* read or write (true).
|
||||
///
|
||||
/// Because this method depends on internal implementation details of
|
||||
/// `Access`, it's not recommended. Prefer to manage your own lists of
|
||||
/// accessible components if your application needs to do that.
|
||||
#[doc(hidden)]
|
||||
#[deprecated]
|
||||
pub fn component_reads_and_writes(&self) -> (impl Iterator<Item = T> + '_, bool) {
|
||||
(
|
||||
self.component_read_and_writes
|
||||
.ones()
|
||||
.map(T::get_sparse_set_index),
|
||||
self.component_read_and_writes_inverted,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an iterator over the component IDs that this `Access` either
|
||||
/// writes or can't write.
|
||||
///
|
||||
/// The returned flag specifies whether the list consists of the components
|
||||
/// that the access *can* write (false) or whether the list consists of the
|
||||
/// components that the access *can't* write (true).
|
||||
pub(crate) fn component_writes(&self) -> (impl Iterator<Item = T> + '_, bool) {
|
||||
(
|
||||
self.component_writes.ones().map(T::get_sparse_set_index),
|
||||
self.component_writes_inverted,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`Access`] that has been filtered to include and exclude certain combinations of elements.
|
||||
|
|
|
@ -79,10 +79,14 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
|
|||
.map_or(false, |info| info.storage_type() == StorageType::Table)
|
||||
};
|
||||
|
||||
self.access
|
||||
.access()
|
||||
.component_reads_and_writes()
|
||||
.all(is_dense)
|
||||
#[allow(deprecated)]
|
||||
let (mut component_reads_and_writes, component_reads_and_writes_inverted) =
|
||||
self.access.access().component_reads_and_writes();
|
||||
if component_reads_and_writes_inverted {
|
||||
return false;
|
||||
}
|
||||
|
||||
component_reads_and_writes.all(is_dense)
|
||||
&& self.access.access().archetypal().all(is_dense)
|
||||
&& !self.access.access().has_read_all_components()
|
||||
&& self.access.with_filters().all(is_dense)
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, Archetypes},
|
||||
bundle::Bundle,
|
||||
change_detection::{MaybeThinSlicePtrLocation, Ticks, TicksMut},
|
||||
component::{Component, ComponentId, Components, StorageType, Tick},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
|
||||
storage::{ComponentSparseSet, Table, TableRow},
|
||||
world::{
|
||||
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, FilteredEntityMut,
|
||||
FilteredEntityRef, Mut, Ref, World,
|
||||
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept,
|
||||
FilteredEntityMut, FilteredEntityRef, Mut, Ref, World,
|
||||
},
|
||||
};
|
||||
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
|
||||
use bevy_utils::all_tuples;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::UnsafeCell, marker::PhantomData};
|
||||
|
||||
/// Types that can be fetched from a [`World`] using a [`Query`].
|
||||
|
@ -626,27 +628,15 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
|
|||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
state: &Self::State,
|
||||
archetype: &'w Archetype,
|
||||
_: &'w Archetype,
|
||||
_table: &Table,
|
||||
) {
|
||||
let mut access = Access::default();
|
||||
state.access.component_reads().for_each(|id| {
|
||||
if archetype.contains(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
fetch.1 = access;
|
||||
fetch.1.clone_from(&state.access);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||
let mut access = Access::default();
|
||||
state.access.component_reads().for_each(|id| {
|
||||
if table.has_column(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
fetch.1 = access;
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) {
|
||||
fetch.1.clone_from(&state.access);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -733,37 +723,15 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
|
|||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
state: &Self::State,
|
||||
archetype: &'w Archetype,
|
||||
_: &'w Archetype,
|
||||
_table: &Table,
|
||||
) {
|
||||
let mut access = Access::default();
|
||||
state.access.component_reads().for_each(|id| {
|
||||
if archetype.contains(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
state.access.component_writes().for_each(|id| {
|
||||
if archetype.contains(id) {
|
||||
access.add_component_write(id);
|
||||
}
|
||||
});
|
||||
fetch.1 = access;
|
||||
fetch.1.clone_from(&state.access);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||
let mut access = Access::default();
|
||||
state.access.component_reads().for_each(|id| {
|
||||
if table.has_column(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
state.access.component_writes().for_each(|id| {
|
||||
if table.has_column(id) {
|
||||
access.add_component_write(id);
|
||||
}
|
||||
});
|
||||
fetch.1 = access;
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) {
|
||||
fetch.1.clone_from(&state.access);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -815,6 +783,201 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> {
|
|||
type ReadOnly = FilteredEntityRef<'a>;
|
||||
}
|
||||
|
||||
/// SAFETY: `EntityRefExcept` guards access to all components in the bundle `B`
|
||||
/// and populates `Access` values so that queries that conflict with this access
|
||||
/// are rejected.
|
||||
unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
type Fetch<'w> = UnsafeWorldCell<'w>;
|
||||
type Item<'w> = EntityRefExcept<'w, B>;
|
||||
type State = SmallVec<[ComponentId; 4]>;
|
||||
|
||||
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
|
||||
item
|
||||
}
|
||||
|
||||
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
|
||||
fetch
|
||||
}
|
||||
|
||||
unsafe fn init_fetch<'w>(
|
||||
world: UnsafeWorldCell<'w>,
|
||||
_: &Self::State,
|
||||
_: Tick,
|
||||
_: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
world
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = true;
|
||||
|
||||
unsafe fn set_archetype<'w>(
|
||||
_: &mut Self::Fetch<'w>,
|
||||
_: &Self::State,
|
||||
_: &'w Archetype,
|
||||
_: &'w Table,
|
||||
) {
|
||||
}
|
||||
|
||||
unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {}
|
||||
|
||||
unsafe fn fetch<'w>(
|
||||
world: &mut Self::Fetch<'w>,
|
||||
entity: Entity,
|
||||
_: TableRow,
|
||||
) -> Self::Item<'w> {
|
||||
let cell = world.get_entity(entity).unwrap();
|
||||
EntityRefExcept::new(cell)
|
||||
}
|
||||
|
||||
fn update_component_access(
|
||||
state: &Self::State,
|
||||
filtered_access: &mut FilteredAccess<ComponentId>,
|
||||
) {
|
||||
let mut my_access = Access::new();
|
||||
my_access.read_all_components();
|
||||
for id in state {
|
||||
my_access.remove_component_read(*id);
|
||||
}
|
||||
|
||||
let access = filtered_access.access_mut();
|
||||
assert!(
|
||||
access.is_compatible(&my_access),
|
||||
"`EntityRefExcept<{}>` conflicts with a previous access in this query.",
|
||||
std::any::type_name::<B>(),
|
||||
);
|
||||
access.extend(&my_access);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> Self::State {
|
||||
Self::get_state(world.components()).unwrap()
|
||||
}
|
||||
|
||||
fn get_state(components: &Components) -> Option<Self::State> {
|
||||
let mut ids = SmallVec::new();
|
||||
B::get_component_ids(components, &mut |maybe_id| {
|
||||
if let Some(id) = maybe_id {
|
||||
ids.push(id);
|
||||
}
|
||||
});
|
||||
Some(ids)
|
||||
}
|
||||
|
||||
fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: `Self` is the same as `Self::ReadOnly`.
|
||||
unsafe impl<'a, B> QueryData for EntityRefExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
type ReadOnly = Self;
|
||||
}
|
||||
|
||||
/// SAFETY: `EntityRefExcept` enforces read-only access to its contained
|
||||
/// components.
|
||||
unsafe impl<'a, B> ReadOnlyQueryData for EntityRefExcept<'a, B> where B: Bundle {}
|
||||
|
||||
/// SAFETY: `EntityMutExcept` guards access to all components in the bundle `B`
|
||||
/// and populates `Access` values so that queries that conflict with this access
|
||||
/// are rejected.
|
||||
unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
type Fetch<'w> = UnsafeWorldCell<'w>;
|
||||
type Item<'w> = EntityMutExcept<'w, B>;
|
||||
type State = SmallVec<[ComponentId; 4]>;
|
||||
|
||||
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
|
||||
item
|
||||
}
|
||||
|
||||
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
|
||||
fetch
|
||||
}
|
||||
|
||||
unsafe fn init_fetch<'w>(
|
||||
world: UnsafeWorldCell<'w>,
|
||||
_: &Self::State,
|
||||
_: Tick,
|
||||
_: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
world
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = true;
|
||||
|
||||
unsafe fn set_archetype<'w>(
|
||||
_: &mut Self::Fetch<'w>,
|
||||
_: &Self::State,
|
||||
_: &'w Archetype,
|
||||
_: &'w Table,
|
||||
) {
|
||||
}
|
||||
|
||||
unsafe fn set_table<'w>(_: &mut Self::Fetch<'w>, _: &Self::State, _: &'w Table) {}
|
||||
|
||||
unsafe fn fetch<'w>(
|
||||
world: &mut Self::Fetch<'w>,
|
||||
entity: Entity,
|
||||
_: TableRow,
|
||||
) -> Self::Item<'w> {
|
||||
let cell = world.get_entity(entity).unwrap();
|
||||
EntityMutExcept::new(cell)
|
||||
}
|
||||
|
||||
fn update_component_access(
|
||||
state: &Self::State,
|
||||
filtered_access: &mut FilteredAccess<ComponentId>,
|
||||
) {
|
||||
let mut my_access = Access::new();
|
||||
my_access.write_all_components();
|
||||
for id in state {
|
||||
my_access.remove_component_read(*id);
|
||||
}
|
||||
|
||||
let access = filtered_access.access_mut();
|
||||
assert!(
|
||||
access.is_compatible(&my_access),
|
||||
"`EntityMutExcept<{}>` conflicts with a previous access in this query.",
|
||||
std::any::type_name::<B>()
|
||||
);
|
||||
access.extend(&my_access);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> Self::State {
|
||||
Self::get_state(world.components()).unwrap()
|
||||
}
|
||||
|
||||
fn get_state(components: &Components) -> Option<Self::State> {
|
||||
let mut ids = SmallVec::new();
|
||||
B::get_component_ids(components, &mut |maybe_id| {
|
||||
if let Some(id) = maybe_id {
|
||||
ids.push(id);
|
||||
}
|
||||
});
|
||||
Some(ids)
|
||||
}
|
||||
|
||||
fn matches_component_set(_: &Self::State, _: &impl Fn(ComponentId) -> bool) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// SAFETY: All accesses that `EntityRefExcept` provides are also accesses that
|
||||
/// `EntityMutExcept` provides.
|
||||
unsafe impl<'a, B> QueryData for EntityMutExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
type ReadOnly = EntityRefExcept<'a, B>;
|
||||
}
|
||||
|
||||
/// SAFETY:
|
||||
/// `update_component_access` and `update_archetype_component_access` do nothing.
|
||||
/// This is sound because `fetch` does not access components.
|
||||
|
@ -982,9 +1145,8 @@ unsafe impl<T: Component> WorldQuery for &T {
|
|||
) {
|
||||
fetch.table_components = Some(
|
||||
table
|
||||
.get_column(component_id)
|
||||
.get_data_slice_for(component_id)
|
||||
.debug_checked_unwrap()
|
||||
.get_data_slice()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
@ -1147,11 +1309,11 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
|||
) {
|
||||
let column = table.get_column(component_id).debug_checked_unwrap();
|
||||
fetch.table_data = Some((
|
||||
column.get_data_slice().into(),
|
||||
column.get_added_ticks_slice().into(),
|
||||
column.get_changed_ticks_slice().into(),
|
||||
column.get_data_slice(table.entity_count()).into(),
|
||||
column.get_added_ticks_slice(table.entity_count()).into(),
|
||||
column.get_changed_ticks_slice(table.entity_count()).into(),
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
column.get_changed_by_slice().into(),
|
||||
column.get_changed_by_slice(table.entity_count()).into(),
|
||||
#[cfg(not(feature = "track_change_detection"))]
|
||||
(),
|
||||
));
|
||||
|
@ -1346,11 +1508,11 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
|||
) {
|
||||
let column = table.get_column(component_id).debug_checked_unwrap();
|
||||
fetch.table_data = Some((
|
||||
column.get_data_slice().into(),
|
||||
column.get_added_ticks_slice().into(),
|
||||
column.get_changed_ticks_slice().into(),
|
||||
column.get_data_slice(table.entity_count()).into(),
|
||||
column.get_added_ticks_slice(table.entity_count()).into(),
|
||||
column.get_changed_ticks_slice(table.entity_count()).into(),
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
column.get_changed_by_slice().into(),
|
||||
column.get_changed_by_slice(table.entity_count()).into(),
|
||||
#[cfg(not(feature = "track_change_detection"))]
|
||||
(),
|
||||
));
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
component::{Component, ComponentId, Components, StorageType, Tick},
|
||||
entity::Entity,
|
||||
query::{DebugCheckedUnwrap, FilteredAccess, WorldQuery},
|
||||
storage::{Column, ComponentSparseSet, Table, TableRow},
|
||||
storage::{ComponentSparseSet, Table, TableRow},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||
};
|
||||
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
|
||||
|
@ -70,12 +70,17 @@ use std::{cell::UnsafeCell, marker::PhantomData};
|
|||
/// [`matches_component_set`]: Self::matches_component_set
|
||||
/// [`Query`]: crate::system::Query
|
||||
/// [`State`]: Self::State
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`WorldQuery`] implementation must not take any mutable access.
|
||||
/// This is the same safety requirement as [`ReadOnlyQueryData`](crate::query::ReadOnlyQueryData).
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` is not a valid `Query` filter",
|
||||
label = "invalid `Query` filter",
|
||||
note = "a `QueryFilter` typically uses a combination of `With<T>` and `Without<T>` statements"
|
||||
)]
|
||||
pub trait QueryFilter: WorldQuery {
|
||||
pub unsafe trait QueryFilter: WorldQuery {
|
||||
/// Returns true if (and only if) this Filter relies strictly on archetypes to limit which
|
||||
/// components are accessed by the Query.
|
||||
///
|
||||
|
@ -201,7 +206,8 @@ unsafe impl<T: Component> WorldQuery for With<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for With<T> {
|
||||
// SAFETY: WorldQuery impl performs no access at all
|
||||
unsafe impl<T: Component> QueryFilter for With<T> {
|
||||
const IS_ARCHETYPAL: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -311,7 +317,8 @@ unsafe impl<T: Component> WorldQuery for Without<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Without<T> {
|
||||
// SAFETY: WorldQuery impl performs no access at all
|
||||
unsafe impl<T: Component> QueryFilter for Without<T> {
|
||||
const IS_ARCHETYPAL: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -490,7 +497,8 @@ macro_rules! impl_or_query_filter {
|
|||
}
|
||||
}
|
||||
|
||||
impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> {
|
||||
// SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access.
|
||||
unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> {
|
||||
const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -512,7 +520,8 @@ macro_rules! impl_tuple_query_filter {
|
|||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::unused_unit)]
|
||||
$(#[$meta])*
|
||||
impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) {
|
||||
// SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access.
|
||||
unsafe impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) {
|
||||
const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -677,7 +686,9 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
|
|||
table: &'w Table,
|
||||
) {
|
||||
fetch.table_ticks = Some(
|
||||
Column::get_added_ticks_slice(table.get_column(component_id).debug_checked_unwrap())
|
||||
table
|
||||
.get_added_ticks_slice_for(component_id)
|
||||
.debug_checked_unwrap()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
@ -734,7 +745,8 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Added<T> {
|
||||
// SAFETY: WorldQuery impl performs only read access on ticks
|
||||
unsafe impl<T: Component> QueryFilter for Added<T> {
|
||||
const IS_ARCHETYPAL: bool = false;
|
||||
#[inline(always)]
|
||||
unsafe fn filter_fetch(
|
||||
|
@ -892,7 +904,9 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
|
|||
table: &'w Table,
|
||||
) {
|
||||
fetch.table_ticks = Some(
|
||||
Column::get_changed_ticks_slice(table.get_column(component_id).debug_checked_unwrap())
|
||||
table
|
||||
.get_changed_ticks_slice_for(component_id)
|
||||
.debug_checked_unwrap()
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
@ -949,7 +963,8 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Component> QueryFilter for Changed<T> {
|
||||
// SAFETY: WorldQuery impl performs only read access on ticks
|
||||
unsafe impl<T: Component> QueryFilter for Changed<T> {
|
||||
const IS_ARCHETYPAL: bool = false;
|
||||
|
||||
#[inline(always)]
|
||||
|
|
|
@ -146,7 +146,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
|
|||
|
||||
let range = range.unwrap_or(0..table.entity_count());
|
||||
accum =
|
||||
// SAFETY:
|
||||
// SAFETY:
|
||||
// - The fetched table matches both D and F
|
||||
// - caller ensures `range` is within `[0, table.entity_count)`
|
||||
// - The if block ensures that the query iteration is dense
|
||||
|
@ -1290,7 +1290,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: Borrow<Entity>>>
|
|||
}
|
||||
}
|
||||
|
||||
/// Safety:
|
||||
/// # Safety
|
||||
/// All arguments must stem from the same valid `QueryManyIter`.
|
||||
///
|
||||
/// The lifetime here is not restrictive enough for Fetch with &mut access,
|
||||
|
@ -1578,7 +1578,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter<
|
|||
}
|
||||
}
|
||||
|
||||
/// Safety:
|
||||
/// # Safety
|
||||
/// The lifetime here is not restrictive enough for Fetch with &mut access,
|
||||
/// as calling `fetch_next_aliased_unchecked` multiple times can produce multiple
|
||||
/// references to the same component, leading to unique reference aliasing.
|
||||
|
@ -1733,6 +1733,9 @@ impl<D: QueryData, F: QueryFilter> Clone for QueryIterationCursor<'_, '_, D, F>
|
|||
}
|
||||
|
||||
impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
|
||||
/// # Safety
|
||||
/// - `world` must have permission to access any of the components registered in `query_state`.
|
||||
/// - `world` must be the same one used to initialize `query_state`.
|
||||
unsafe fn init_empty(
|
||||
world: UnsafeWorldCell<'w>,
|
||||
query_state: &'s QueryState<D, F>,
|
||||
|
@ -1781,25 +1784,41 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
|
|||
}
|
||||
}
|
||||
|
||||
/// retrieve item returned from most recent `next` call again.
|
||||
/// Retrieve item returned from most recent `next` call again.
|
||||
///
|
||||
/// # Safety
|
||||
/// The result of `next` and any previous calls to `peek_last` with this row must have been
|
||||
/// dropped to prevent aliasing mutable references.
|
||||
#[inline]
|
||||
unsafe fn peek_last(&mut self) -> Option<D::Item<'w>> {
|
||||
if self.current_row > 0 {
|
||||
let index = self.current_row - 1;
|
||||
if self.is_dense {
|
||||
let entity = self.table_entities.get_unchecked(index);
|
||||
Some(D::fetch(
|
||||
&mut self.fetch,
|
||||
*entity,
|
||||
TableRow::from_usize(index),
|
||||
))
|
||||
// SAFETY: This must have been called previously in `next` as `current_row > 0`
|
||||
let entity = unsafe { self.table_entities.get_unchecked(index) };
|
||||
// SAFETY:
|
||||
// - `set_table` must have been called previously either in `next` or before it.
|
||||
// - `*entity` and `index` are in the current table.
|
||||
unsafe {
|
||||
Some(D::fetch(
|
||||
&mut self.fetch,
|
||||
*entity,
|
||||
TableRow::from_usize(index),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let archetype_entity = self.archetype_entities.get_unchecked(index);
|
||||
Some(D::fetch(
|
||||
&mut self.fetch,
|
||||
archetype_entity.id(),
|
||||
archetype_entity.table_row(),
|
||||
))
|
||||
// SAFETY: This must have been called previously in `next` as `current_row > 0`
|
||||
let archetype_entity = unsafe { self.archetype_entities.get_unchecked(index) };
|
||||
// SAFETY:
|
||||
// - `set_archetype` must have been called previously either in `next` or before it.
|
||||
// - `archetype_entity.id()` and `archetype_entity.table_row()` are in the current archetype.
|
||||
unsafe {
|
||||
Some(D::fetch(
|
||||
&mut self.fetch,
|
||||
archetype_entity.id(),
|
||||
archetype_entity.table_row(),
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
@ -2083,7 +2102,7 @@ mod tests {
|
|||
let mut query = world.query::<&Sparse>();
|
||||
let mut iter = query.iter(&world);
|
||||
println!(
|
||||
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
iter.cursor.archetype_entities.len(),
|
||||
iter.cursor.table_entities.len(),
|
||||
iter.cursor.current_len,
|
||||
|
@ -2091,7 +2110,7 @@ mod tests {
|
|||
);
|
||||
_ = iter.next();
|
||||
println!(
|
||||
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
iter.cursor.archetype_entities.len(),
|
||||
iter.cursor.table_entities.len(),
|
||||
iter.cursor.current_len,
|
||||
|
@ -2108,7 +2127,7 @@ mod tests {
|
|||
let mut query = world.query::<(&A, &Sparse)>();
|
||||
let mut iter = query.iter(&world);
|
||||
println!(
|
||||
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
iter.cursor.archetype_entities.len(),
|
||||
iter.cursor.table_entities.len(),
|
||||
iter.cursor.current_len,
|
||||
|
@ -2116,7 +2135,7 @@ mod tests {
|
|||
);
|
||||
_ = iter.next();
|
||||
println!(
|
||||
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
iter.cursor.archetype_entities.len(),
|
||||
iter.cursor.table_entities.len(),
|
||||
iter.cursor.current_len,
|
||||
|
@ -2136,7 +2155,7 @@ mod tests {
|
|||
let mut query = world.query::<(&A, &Sparse)>();
|
||||
let mut iter = query.iter(&world);
|
||||
println!(
|
||||
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
iter.cursor.archetype_entities.len(),
|
||||
iter.cursor.table_entities.len(),
|
||||
iter.cursor.current_len,
|
||||
|
@ -2145,7 +2164,7 @@ mod tests {
|
|||
assert!(iter.cursor.table_entities.len() | iter.cursor.archetype_entities.len() == 0);
|
||||
_ = iter.next();
|
||||
println!(
|
||||
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
|
||||
iter.cursor.archetype_entities.len(),
|
||||
iter.cursor.table_entities.len(),
|
||||
iter.cursor.current_len,
|
||||
|
|
|
@ -53,6 +53,40 @@ impl<T> DebugCheckedUnwrap for Option<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// These two impls are explicitly split to ensure that the unreachable! macro
|
||||
// does not cause inlining to fail when compiling in release mode.
|
||||
#[cfg(debug_assertions)]
|
||||
impl<T, U> DebugCheckedUnwrap for Result<T, U> {
|
||||
type Item = T;
|
||||
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
unsafe fn debug_checked_unwrap(self) -> Self::Item {
|
||||
if let Ok(inner) = self {
|
||||
inner
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These two impls are explicitly split to ensure that the unreachable! macro
|
||||
// does not cause inlining to fail when compiling in release mode.
|
||||
#[cfg(not(debug_assertions))]
|
||||
impl<T, U> DebugCheckedUnwrap for Result<T, U> {
|
||||
type Item = T;
|
||||
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
unsafe fn debug_checked_unwrap(self) -> Self::Item {
|
||||
if let Ok(inner) = self {
|
||||
inner
|
||||
} else {
|
||||
std::hint::unreachable_unchecked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
impl<T> DebugCheckedUnwrap for Option<T> {
|
||||
type Item = T;
|
||||
|
@ -69,13 +103,12 @@ impl<T> DebugCheckedUnwrap for Option<T> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_ecs_macros::{QueryData, QueryFilter};
|
||||
|
||||
use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without};
|
||||
use crate::query::{ArchetypeFilter, Has, QueryCombinationIter, ReadOnlyQueryData};
|
||||
use crate::schedule::{IntoSystemConfigs, Schedule};
|
||||
use crate::system::{IntoSystem, Query, System, SystemState};
|
||||
use crate::{self as bevy_ecs, component::Component, world::World};
|
||||
use bevy_ecs_macros::{QueryData, QueryFilter};
|
||||
use std::any::type_name;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Debug;
|
||||
|
|
|
@ -123,7 +123,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
///
|
||||
/// Consider using `as_readonly` or `as_nop` instead which are safe functions.
|
||||
///
|
||||
/// # SAFETY
|
||||
/// # Safety
|
||||
///
|
||||
/// `NewD` must have a subset of the access that `D` does and match the exact same archetypes/tables
|
||||
/// `NewF` must have a subset of the access that `F` does and match the exact same archetypes/tables
|
||||
|
@ -508,22 +508,46 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
self.component_access
|
||||
.access
|
||||
.component_reads()
|
||||
.for_each(|id| {
|
||||
// As a fast path, we can iterate directly over the components involved
|
||||
// if the `access` isn't inverted.
|
||||
#[allow(deprecated)]
|
||||
let (component_reads_and_writes, component_reads_and_writes_inverted) =
|
||||
self.component_access.access.component_reads_and_writes();
|
||||
let (component_writes, component_writes_inverted) =
|
||||
self.component_access.access.component_writes();
|
||||
|
||||
if !component_reads_and_writes_inverted && !component_writes_inverted {
|
||||
component_reads_and_writes.for_each(|id| {
|
||||
if let Some(id) = archetype.get_archetype_component_id(id) {
|
||||
access.add_component_read(id);
|
||||
}
|
||||
});
|
||||
self.component_access
|
||||
.access
|
||||
.component_writes()
|
||||
.for_each(|id| {
|
||||
component_writes.for_each(|id| {
|
||||
if let Some(id) = archetype.get_archetype_component_id(id) {
|
||||
access.add_component_write(id);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (component_id, archetype_component_id) in
|
||||
archetype.components_with_archetype_component_id()
|
||||
{
|
||||
if self
|
||||
.component_access
|
||||
.access
|
||||
.has_component_read(component_id)
|
||||
{
|
||||
access.add_component_read(archetype_component_id);
|
||||
}
|
||||
if self
|
||||
.component_access
|
||||
.access
|
||||
.has_component_write(component_id)
|
||||
{
|
||||
access.add_component_write(archetype_component_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Use this to transform a [`QueryState`] into a more generic [`QueryState`].
|
||||
|
@ -1174,14 +1198,11 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
///
|
||||
/// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items.
|
||||
#[inline]
|
||||
pub fn iter_many<'w, 's, EntityList: IntoIterator>(
|
||||
pub fn iter_many<'w, 's, EntityList: IntoIterator<Item: Borrow<Entity>>>(
|
||||
&'s mut self,
|
||||
world: &'w World,
|
||||
entities: EntityList,
|
||||
) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter>
|
||||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
{
|
||||
) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> {
|
||||
self.update_archetypes(world);
|
||||
// SAFETY: query is read only
|
||||
unsafe {
|
||||
|
@ -1209,14 +1230,11 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
/// - [`iter_many`](Self::iter_many) to update archetypes.
|
||||
/// - [`iter_manual`](Self::iter_manual) to iterate over all query items.
|
||||
#[inline]
|
||||
pub fn iter_many_manual<'w, 's, EntityList: IntoIterator>(
|
||||
pub fn iter_many_manual<'w, 's, EntityList: IntoIterator<Item: Borrow<Entity>>>(
|
||||
&'s self,
|
||||
world: &'w World,
|
||||
entities: EntityList,
|
||||
) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter>
|
||||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
{
|
||||
) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> {
|
||||
self.validate_world(world.id());
|
||||
// SAFETY: query is read only, world id is validated
|
||||
unsafe {
|
||||
|
@ -1234,14 +1252,11 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
/// Items are returned in the order of the list of entities.
|
||||
/// Entities that don't match the query are skipped.
|
||||
#[inline]
|
||||
pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>(
|
||||
pub fn iter_many_mut<'w, 's, EntityList: IntoIterator<Item: Borrow<Entity>>>(
|
||||
&'s mut self,
|
||||
world: &'w mut World,
|
||||
entities: EntityList,
|
||||
) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter>
|
||||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
{
|
||||
) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> {
|
||||
self.update_archetypes(world);
|
||||
let change_tick = world.change_tick();
|
||||
let last_change_tick = world.last_change_tick();
|
||||
|
@ -1334,7 +1349,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
/// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world`
|
||||
/// with a mismatched [`WorldId`] is unsound.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn iter_many_unchecked_manual<'w, 's, EntityList: IntoIterator>(
|
||||
pub(crate) unsafe fn iter_many_unchecked_manual<'w, 's, EntityList>(
|
||||
&'s self,
|
||||
entities: EntityList,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
|
@ -1342,7 +1357,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||
this_run: Tick,
|
||||
) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter>
|
||||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
EntityList: IntoIterator<Item: Borrow<Entity>>,
|
||||
{
|
||||
QueryManyIter::new(world, self, entities, last_run, this_run)
|
||||
}
|
||||
|
|
|
@ -2,23 +2,28 @@ use crate::prelude::Mut;
|
|||
use crate::reflect::AppTypeRegistry;
|
||||
use crate::system::{EntityCommands, Resource};
|
||||
use crate::world::Command;
|
||||
use crate::{entity::Entity, reflect::ReflectComponent, world::World};
|
||||
use crate::{
|
||||
entity::Entity,
|
||||
reflect::{ReflectBundle, ReflectComponent},
|
||||
world::World,
|
||||
};
|
||||
use bevy_reflect::{PartialReflect, TypeRegistry};
|
||||
use std::borrow::Cow;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// An extension trait for [`EntityCommands`] for reflection related functions
|
||||
pub trait ReflectCommandExt {
|
||||
/// Adds the given boxed reflect component to the entity using the reflection data in
|
||||
/// Adds the given boxed reflect component or bundle to the entity using the reflection data in
|
||||
/// [`AppTypeRegistry`].
|
||||
///
|
||||
/// This will overwrite any previous component of the same type.
|
||||
/// This will overwrite any previous component(s) of the same type.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If the entity doesn't exist.
|
||||
/// - If [`AppTypeRegistry`] does not have the reflection data for the given [`Component`](crate::component::Component).
|
||||
/// - If the component data is invalid. See [`PartialReflect::apply`] for further details.
|
||||
/// - If [`AppTypeRegistry`] does not have the reflection data for the given
|
||||
/// [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle).
|
||||
/// - If the component or bundle data is invalid. See [`PartialReflect::apply`] for further details.
|
||||
/// - If [`AppTypeRegistry`] is not present in the [`World`].
|
||||
///
|
||||
/// # Note
|
||||
|
@ -34,12 +39,12 @@ pub trait ReflectCommandExt {
|
|||
/// // or write to the TypeRegistry directly to register all your components
|
||||
///
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_ecs::reflect::ReflectCommandExt;
|
||||
/// # use bevy_ecs::reflect::{ReflectCommandExt, ReflectBundle};
|
||||
/// # use bevy_reflect::{FromReflect, FromType, Reflect, TypeRegistry};
|
||||
/// // A resource that can hold any component that implements reflect as a boxed reflect component
|
||||
/// #[derive(Resource)]
|
||||
/// struct Prefab{
|
||||
/// component: Box<dyn Reflect>,
|
||||
/// struct Prefab {
|
||||
/// data: Box<dyn Reflect>,
|
||||
/// }
|
||||
/// #[derive(Component, Reflect, Default)]
|
||||
/// #[reflect(Component)]
|
||||
|
@ -49,6 +54,13 @@ pub trait ReflectCommandExt {
|
|||
/// #[reflect(Component)]
|
||||
/// struct ComponentB(String);
|
||||
///
|
||||
/// #[derive(Bundle, Reflect, Default)]
|
||||
/// #[reflect(Bundle)]
|
||||
/// struct BundleA {
|
||||
/// a: ComponentA,
|
||||
/// b: ComponentB,
|
||||
/// }
|
||||
///
|
||||
/// fn insert_reflect_component(
|
||||
/// mut commands: Commands,
|
||||
/// mut prefab: ResMut<Prefab>
|
||||
|
@ -56,16 +68,23 @@ pub trait ReflectCommandExt {
|
|||
/// // Create a set of new boxed reflect components to use
|
||||
/// let boxed_reflect_component_a: Box<dyn Reflect> = Box::new(ComponentA(916));
|
||||
/// let boxed_reflect_component_b: Box<dyn Reflect> = Box::new(ComponentB("NineSixteen".to_string()));
|
||||
/// let boxed_reflect_bundle_a: Box<dyn Reflect> = Box::new(BundleA {
|
||||
/// a: ComponentA(24),
|
||||
/// b: ComponentB("Twenty-Four".to_string()),
|
||||
/// });
|
||||
///
|
||||
/// // You can overwrite the component in the resource with either ComponentA or ComponentB
|
||||
/// prefab.component = boxed_reflect_component_a;
|
||||
/// prefab.component = boxed_reflect_component_b;
|
||||
///
|
||||
/// // No matter which component is in the resource and without knowing the exact type, you can
|
||||
/// // use the insert_reflect entity command to insert that component into an entity.
|
||||
/// prefab.data = boxed_reflect_component_a;
|
||||
/// prefab.data = boxed_reflect_component_b;
|
||||
///
|
||||
/// // Or even with BundleA
|
||||
/// prefab.data = boxed_reflect_bundle_a;
|
||||
///
|
||||
/// // No matter which component or bundle is in the resource and without knowing the exact type, you can
|
||||
/// // use the insert_reflect entity command to insert that component/bundle into an entity.
|
||||
/// commands
|
||||
/// .spawn_empty()
|
||||
/// .insert_reflect(prefab.component.clone_value());
|
||||
/// .insert_reflect(prefab.data.clone_value());
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
|
@ -86,10 +105,15 @@ pub trait ReflectCommandExt {
|
|||
component: Box<dyn PartialReflect>,
|
||||
) -> &mut Self;
|
||||
|
||||
/// Removes from the entity the component with the given type name registered in [`AppTypeRegistry`].
|
||||
/// Removes from the entity the component or bundle with the given type name registered in [`AppTypeRegistry`].
|
||||
///
|
||||
/// Does nothing if the entity does not have a component of the same type, if [`AppTypeRegistry`]
|
||||
/// does not contain the reflection data for the given component, or if the entity does not exist.
|
||||
/// If the type is a bundle, it will remove any components in that bundle regardless if the entity
|
||||
/// contains all the components.
|
||||
///
|
||||
/// Does nothing if the type is a component and the entity does not have a component of the same type,
|
||||
/// if the type is a bundle and the entity does not contain any of the components in the bundle,
|
||||
/// if [`AppTypeRegistry`] does not contain the reflection data for the given component,
|
||||
/// or if the entity does not exist.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
|
@ -99,19 +123,19 @@ pub trait ReflectCommandExt {
|
|||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// // Note that you need to register the component type in the AppTypeRegistry prior to using
|
||||
/// // Note that you need to register the component/bundle type in the AppTypeRegistry prior to using
|
||||
/// // reflection. You can use the helpers on the App with `app.register_type::<ComponentA>()`
|
||||
/// // or write to the TypeRegistry directly to register all your components
|
||||
/// // or write to the TypeRegistry directly to register all your components and bundles
|
||||
///
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_ecs::reflect::ReflectCommandExt;
|
||||
/// # use bevy_ecs::reflect::{ReflectCommandExt, ReflectBundle};
|
||||
/// # use bevy_reflect::{FromReflect, FromType, Reflect, TypeRegistry};
|
||||
///
|
||||
/// // A resource that can hold any component that implements reflect as a boxed reflect component
|
||||
/// // A resource that can hold any component or bundle that implements reflect as a boxed reflect
|
||||
/// #[derive(Resource)]
|
||||
/// struct Prefab{
|
||||
/// entity: Entity,
|
||||
/// component: Box<dyn Reflect>,
|
||||
/// data: Box<dyn Reflect>,
|
||||
/// }
|
||||
/// #[derive(Component, Reflect, Default)]
|
||||
/// #[reflect(Component)]
|
||||
|
@ -119,16 +143,23 @@ pub trait ReflectCommandExt {
|
|||
/// #[derive(Component, Reflect, Default)]
|
||||
/// #[reflect(Component)]
|
||||
/// struct ComponentB(String);
|
||||
/// #[derive(Bundle, Reflect, Default)]
|
||||
/// #[reflect(Bundle)]
|
||||
/// struct BundleA {
|
||||
/// a: ComponentA,
|
||||
/// b: ComponentB,
|
||||
/// }
|
||||
///
|
||||
/// fn remove_reflect_component(
|
||||
/// mut commands: Commands,
|
||||
/// prefab: Res<Prefab>
|
||||
/// ) {
|
||||
/// // Prefab can hold any boxed reflect component. In this case either
|
||||
/// // ComponentA or ComponentB. No matter which component is in the resource though,
|
||||
/// // we can attempt to remove any component of that same type from an entity.
|
||||
/// // Prefab can hold any boxed reflect component or bundle. In this case either
|
||||
/// // ComponentA, ComponentB, or BundleA. No matter which component or bundle is in the resource though,
|
||||
/// // we can attempt to remove any component (or set of components in the case of a bundle)
|
||||
/// // of that same type from an entity.
|
||||
/// commands.entity(prefab.entity)
|
||||
/// .remove_reflect(prefab.component.reflect_type_path().to_owned());
|
||||
/// .remove_reflect(prefab.data.reflect_type_path().to_owned());
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
|
@ -143,7 +174,7 @@ pub trait ReflectCommandExt {
|
|||
|
||||
impl ReflectCommandExt for EntityCommands<'_> {
|
||||
fn insert_reflect(&mut self, component: Box<dyn PartialReflect>) -> &mut Self {
|
||||
self.commands.add(InsertReflect {
|
||||
self.commands.queue(InsertReflect {
|
||||
entity: self.entity,
|
||||
component,
|
||||
});
|
||||
|
@ -154,7 +185,7 @@ impl ReflectCommandExt for EntityCommands<'_> {
|
|||
&mut self,
|
||||
component: Box<dyn PartialReflect>,
|
||||
) -> &mut Self {
|
||||
self.commands.add(InsertReflectWithRegistry::<T> {
|
||||
self.commands.queue(InsertReflectWithRegistry::<T> {
|
||||
entity: self.entity,
|
||||
_t: PhantomData,
|
||||
component,
|
||||
|
@ -163,7 +194,7 @@ impl ReflectCommandExt for EntityCommands<'_> {
|
|||
}
|
||||
|
||||
fn remove_reflect(&mut self, component_type_path: impl Into<Cow<'static, str>>) -> &mut Self {
|
||||
self.commands.add(RemoveReflect {
|
||||
self.commands.queue(RemoveReflect {
|
||||
entity: self.entity,
|
||||
component_type_path: component_type_path.into(),
|
||||
});
|
||||
|
@ -174,7 +205,7 @@ impl ReflectCommandExt for EntityCommands<'_> {
|
|||
&mut self,
|
||||
component_type_name: impl Into<Cow<'static, str>>,
|
||||
) -> &mut Self {
|
||||
self.commands.add(RemoveReflectWithRegistry::<T> {
|
||||
self.commands.queue(RemoveReflectWithRegistry::<T> {
|
||||
entity: self.entity,
|
||||
_t: PhantomData,
|
||||
component_type_name: component_type_name.into(),
|
||||
|
@ -183,7 +214,7 @@ impl ReflectCommandExt for EntityCommands<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Helper function to add a reflect component to a given entity
|
||||
/// Helper function to add a reflect component or bundle to a given entity
|
||||
fn insert_reflect(
|
||||
world: &mut World,
|
||||
entity: Entity,
|
||||
|
@ -198,22 +229,27 @@ fn insert_reflect(
|
|||
panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003");
|
||||
};
|
||||
let Some(type_registration) = type_registry.get(type_info.type_id()) else {
|
||||
panic!("Could not get type registration (for component type {type_path}) because it doesn't exist in the TypeRegistry.");
|
||||
panic!("`{type_path}` should be registered in type registry via `App::register_type<{type_path}>`");
|
||||
};
|
||||
let Some(reflect_component) = type_registration.data::<ReflectComponent>() else {
|
||||
panic!("Could not get ReflectComponent data (for component type {type_path}) because it doesn't exist in this TypeRegistration.");
|
||||
};
|
||||
reflect_component.insert(&mut entity, component.as_partial_reflect(), type_registry);
|
||||
|
||||
if let Some(reflect_component) = type_registration.data::<ReflectComponent>() {
|
||||
reflect_component.insert(&mut entity, component.as_partial_reflect(), type_registry);
|
||||
} else if let Some(reflect_bundle) = type_registration.data::<ReflectBundle>() {
|
||||
reflect_bundle.insert(&mut entity, component.as_partial_reflect(), type_registry);
|
||||
} else {
|
||||
panic!("`{type_path}` should have #[reflect(Component)] or #[reflect(Bundle)]");
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that adds the boxed reflect component to an entity using the data in
|
||||
/// A [`Command`] that adds the boxed reflect component or bundle to an entity using the data in
|
||||
/// [`AppTypeRegistry`].
|
||||
///
|
||||
/// See [`ReflectCommandExt::insert_reflect`] for details.
|
||||
pub struct InsertReflect {
|
||||
/// The entity on which the component will be inserted.
|
||||
pub entity: Entity,
|
||||
/// The reflect [`Component`](crate::component::Component) that will be added to the entity.
|
||||
/// The reflect [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle)
|
||||
/// that will be added to the entity.
|
||||
pub component: Box<dyn PartialReflect>,
|
||||
}
|
||||
|
||||
|
@ -224,7 +260,7 @@ impl Command for InsertReflect {
|
|||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that adds the boxed reflect component to an entity using the data in the provided
|
||||
/// A [`Command`] that adds the boxed reflect component or bundle to an entity using the data in the provided
|
||||
/// [`Resource`] that implements [`AsRef<TypeRegistry>`].
|
||||
///
|
||||
/// See [`ReflectCommandExt::insert_reflect_with_registry`] for details.
|
||||
|
@ -245,7 +281,7 @@ impl<T: Resource + AsRef<TypeRegistry>> Command for InsertReflectWithRegistry<T>
|
|||
}
|
||||
}
|
||||
|
||||
/// Helper function to remove a reflect component from a given entity
|
||||
/// Helper function to remove a reflect component or bundle from a given entity
|
||||
fn remove_reflect(
|
||||
world: &mut World,
|
||||
entity: Entity,
|
||||
|
@ -258,20 +294,22 @@ fn remove_reflect(
|
|||
let Some(type_registration) = type_registry.get_with_type_path(&component_type_path) else {
|
||||
return;
|
||||
};
|
||||
let Some(reflect_component) = type_registration.data::<ReflectComponent>() else {
|
||||
return;
|
||||
};
|
||||
reflect_component.remove(&mut entity);
|
||||
if let Some(reflect_component) = type_registration.data::<ReflectComponent>() {
|
||||
reflect_component.remove(&mut entity);
|
||||
} else if let Some(reflect_bundle) = type_registration.data::<ReflectBundle>() {
|
||||
reflect_bundle.remove(&mut entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that removes the component of the same type as the given component type name from
|
||||
/// A [`Command`] that removes the component or bundle of the same type as the given type name from
|
||||
/// the provided entity.
|
||||
///
|
||||
/// See [`ReflectCommandExt::remove_reflect`] for details.
|
||||
pub struct RemoveReflect {
|
||||
/// The entity from which the component will be removed.
|
||||
pub entity: Entity,
|
||||
/// The [`Component`](crate::component::Component) type name that will be used to remove a component
|
||||
/// The [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle)
|
||||
/// type name that will be used to remove a component
|
||||
/// of the same type from the entity.
|
||||
pub component_type_path: Cow<'static, str>,
|
||||
}
|
||||
|
@ -288,7 +326,7 @@ impl Command for RemoveReflect {
|
|||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that removes the component of the same type as the given component type name from
|
||||
/// A [`Command`] that removes the component or bundle of the same type as the given type name from
|
||||
/// the provided entity using the provided [`Resource`] that implements [`AsRef<TypeRegistry>`].
|
||||
///
|
||||
/// See [`ReflectCommandExt::remove_reflect_with_registry`] for details.
|
||||
|
@ -296,7 +334,8 @@ pub struct RemoveReflectWithRegistry<T: Resource + AsRef<TypeRegistry>> {
|
|||
/// The entity from which the component will be removed.
|
||||
pub entity: Entity,
|
||||
pub _t: PhantomData<T>,
|
||||
/// The [`Component`](crate::component::Component) type name that will be used to remove a component
|
||||
/// The [`Component`](crate::component::Component) or [`Bundle`](crate::bundle::Bundle)
|
||||
/// type name that will be used to remove a component
|
||||
/// of the same type from the entity.
|
||||
pub component_type_name: Cow<'static, str>,
|
||||
}
|
||||
|
@ -313,9 +352,9 @@ impl<T: Resource + AsRef<TypeRegistry>> Command for RemoveReflectWithRegistry<T>
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::prelude::{AppTypeRegistry, ReflectComponent};
|
||||
use crate::reflect::ReflectCommandExt;
|
||||
use crate::reflect::{ReflectBundle, ReflectCommandExt};
|
||||
use crate::system::{Commands, SystemState};
|
||||
use crate::{self as bevy_ecs, component::Component, world::World};
|
||||
use crate::{self as bevy_ecs, bundle::Bundle, component::Component, world::World};
|
||||
use bevy_ecs_macros::Resource;
|
||||
use bevy_reflect::{PartialReflect, Reflect, TypeRegistry};
|
||||
|
||||
|
@ -334,6 +373,17 @@ mod tests {
|
|||
#[reflect(Component)]
|
||||
struct ComponentA(u32);
|
||||
|
||||
#[derive(Component, Reflect, Default, PartialEq, Eq, Debug)]
|
||||
#[reflect(Component)]
|
||||
struct ComponentB(u32);
|
||||
|
||||
#[derive(Bundle, Reflect, Default, Debug, PartialEq)]
|
||||
#[reflect(Bundle)]
|
||||
struct BundleA {
|
||||
a: ComponentA,
|
||||
b: ComponentB,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_reflected() {
|
||||
let mut world = World::new();
|
||||
|
@ -458,4 +508,140 @@ mod tests {
|
|||
|
||||
assert_eq!(world.entity(entity).get::<ComponentA>(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_reflect_bundle() {
|
||||
let mut world = World::new();
|
||||
|
||||
let type_registry = AppTypeRegistry::default();
|
||||
{
|
||||
let mut registry = type_registry.write();
|
||||
registry.register::<BundleA>();
|
||||
registry.register_type_data::<BundleA, ReflectBundle>();
|
||||
}
|
||||
world.insert_resource(type_registry);
|
||||
|
||||
let mut system_state: SystemState<Commands> = SystemState::new(&mut world);
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands.spawn_empty().id();
|
||||
let bundle = Box::new(BundleA {
|
||||
a: ComponentA(31),
|
||||
b: ComponentB(20),
|
||||
}) as Box<dyn PartialReflect>;
|
||||
commands.entity(entity).insert_reflect(bundle);
|
||||
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(world.get::<ComponentA>(entity), Some(&ComponentA(31)));
|
||||
assert_eq!(world.get::<ComponentB>(entity), Some(&ComponentB(20)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_reflect_bundle_with_registry() {
|
||||
let mut world = World::new();
|
||||
|
||||
let mut type_registry = TypeRegistryResource {
|
||||
type_registry: TypeRegistry::new(),
|
||||
};
|
||||
|
||||
type_registry.type_registry.register::<BundleA>();
|
||||
type_registry
|
||||
.type_registry
|
||||
.register_type_data::<BundleA, ReflectBundle>();
|
||||
world.insert_resource(type_registry);
|
||||
|
||||
let mut system_state: SystemState<Commands> = SystemState::new(&mut world);
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands.spawn_empty().id();
|
||||
let bundle = Box::new(BundleA {
|
||||
a: ComponentA(31),
|
||||
b: ComponentB(20),
|
||||
}) as Box<dyn PartialReflect>;
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert_reflect_with_registry::<TypeRegistryResource>(bundle);
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(world.get::<ComponentA>(entity), Some(&ComponentA(31)));
|
||||
assert_eq!(world.get::<ComponentB>(entity), Some(&ComponentB(20)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_reflected_bundle() {
|
||||
let mut world = World::new();
|
||||
|
||||
let type_registry = AppTypeRegistry::default();
|
||||
{
|
||||
let mut registry = type_registry.write();
|
||||
registry.register::<BundleA>();
|
||||
registry.register_type_data::<BundleA, ReflectBundle>();
|
||||
}
|
||||
world.insert_resource(type_registry);
|
||||
|
||||
let mut system_state: SystemState<Commands> = SystemState::new(&mut world);
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands
|
||||
.spawn(BundleA {
|
||||
a: ComponentA(31),
|
||||
b: ComponentB(20),
|
||||
})
|
||||
.id();
|
||||
|
||||
let boxed_reflect_bundle_a = Box::new(BundleA {
|
||||
a: ComponentA(1),
|
||||
b: ComponentB(23),
|
||||
}) as Box<dyn Reflect>;
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove_reflect(boxed_reflect_bundle_a.reflect_type_path().to_owned());
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(world.entity(entity).get::<ComponentA>(), None);
|
||||
assert_eq!(world.entity(entity).get::<ComponentB>(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_reflected_bundle_with_registry() {
|
||||
let mut world = World::new();
|
||||
|
||||
let mut type_registry = TypeRegistryResource {
|
||||
type_registry: TypeRegistry::new(),
|
||||
};
|
||||
|
||||
type_registry.type_registry.register::<BundleA>();
|
||||
type_registry
|
||||
.type_registry
|
||||
.register_type_data::<BundleA, ReflectBundle>();
|
||||
world.insert_resource(type_registry);
|
||||
|
||||
let mut system_state: SystemState<Commands> = SystemState::new(&mut world);
|
||||
let mut commands = system_state.get_mut(&mut world);
|
||||
|
||||
let entity = commands
|
||||
.spawn(BundleA {
|
||||
a: ComponentA(31),
|
||||
b: ComponentB(20),
|
||||
})
|
||||
.id();
|
||||
|
||||
let boxed_reflect_bundle_a = Box::new(BundleA {
|
||||
a: ComponentA(1),
|
||||
b: ComponentB(23),
|
||||
}) as Box<dyn Reflect>;
|
||||
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove_reflect_with_registry::<TypeRegistryResource>(
|
||||
boxed_reflect_bundle_a.reflect_type_path().to_owned(),
|
||||
);
|
||||
system_state.apply(&mut world);
|
||||
|
||||
assert_eq!(world.entity(entity).get::<ComponentA>(), None);
|
||||
assert_eq!(world.entity(entity).get::<ComponentB>(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,21 +33,6 @@ fn ambiguous_with(graph_info: &mut GraphInfo, set: InternedSystemSet) {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Marker, F> IntoSystemConfigs<Marker> for F
|
||||
where
|
||||
F: IntoSystem<(), (), Marker>,
|
||||
{
|
||||
fn into_configs(self) -> SystemConfigs {
|
||||
SystemConfigs::new_system(Box::new(IntoSystem::into_system(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemConfigs<()> for BoxedSystem<(), ()> {
|
||||
fn into_configs(self) -> SystemConfigs {
|
||||
SystemConfigs::new_system(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores configuration for a single generic node (a system or a system set)
|
||||
///
|
||||
/// The configuration includes the node itself, scheduling metadata
|
||||
|
@ -311,8 +296,8 @@ where
|
|||
/// If automatically inserting [`apply_deferred`](crate::schedule::apply_deferred) like
|
||||
/// this isn't desired, use [`before_ignore_deferred`](Self::before_ignore_deferred) instead.
|
||||
///
|
||||
/// Note: The given set is not implicitly added to the schedule when this system set is added.
|
||||
/// It is safe, but no dependencies will be created.
|
||||
/// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule.
|
||||
/// Please check the [caveats section of `.after`](Self::after) for details.
|
||||
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
|
||||
self.into_configs().before(set)
|
||||
}
|
||||
|
@ -323,8 +308,23 @@ where
|
|||
/// If automatically inserting [`apply_deferred`](crate::schedule::apply_deferred) like
|
||||
/// this isn't desired, use [`after_ignore_deferred`](Self::after_ignore_deferred) instead.
|
||||
///
|
||||
/// Note: The given set is not implicitly added to the schedule when this system set is added.
|
||||
/// It is safe, but no dependencies will be created.
|
||||
/// Calling [`.chain`](Self::chain) is often more convenient and ensures that all systems are added to the schedule.
|
||||
///
|
||||
/// # Caveats
|
||||
///
|
||||
/// If you configure two [`System`]s like `(GameSystem::A).after(GameSystem::B)` or `(GameSystem::A).before(GameSystem::B)`, the `GameSystem::B` will not be automatically scheduled.
|
||||
///
|
||||
/// This means that the system `GameSystem::A` and the system or systems in `GameSystem::B` will run independently of each other if `GameSystem::B` was never explicitly scheduled with [`configure_sets`]
|
||||
/// If that is the case, `.after`/`.before` will not provide the desired behaviour
|
||||
/// and the systems can run in parallel or in any order determined by the scheduler.
|
||||
/// Only use `after(GameSystem::B)` and `before(GameSystem::B)` when you know that `B` has already been scheduled for you,
|
||||
/// e.g. when it was provided by Bevy or a third-party dependency,
|
||||
/// or you manually scheduled it somewhere else in your app.
|
||||
///
|
||||
/// Another caveat is that if `GameSystem::B` is placed in a different schedule than `GameSystem::A`,
|
||||
/// any ordering calls between them—whether using `.before`, `.after`, or `.chain`—will be silently ignored.
|
||||
///
|
||||
/// [`configure_sets`]: https://docs.rs/bevy/latest/bevy/app/struct.App.html#method.configure_sets
|
||||
fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
|
||||
self.into_configs().after(set)
|
||||
}
|
||||
|
@ -517,6 +517,21 @@ impl IntoSystemConfigs<()> for SystemConfigs {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Marker, F> IntoSystemConfigs<Marker> for F
|
||||
where
|
||||
F: IntoSystem<(), (), Marker>,
|
||||
{
|
||||
fn into_configs(self) -> SystemConfigs {
|
||||
SystemConfigs::new_system(Box::new(IntoSystem::into_system(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemConfigs<()> for BoxedSystem<(), ()> {
|
||||
fn into_configs(self) -> SystemConfigs {
|
||||
SystemConfigs::new_system(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct SystemConfigTupleMarker;
|
||||
|
||||
|
|
|
@ -613,9 +613,6 @@ impl ExecutorState {
|
|||
/// # Safety
|
||||
/// Caller must ensure no systems are currently borrowed.
|
||||
unsafe fn spawn_exclusive_system_task(&mut self, context: &Context, system_index: usize) {
|
||||
// SAFETY: `can_run` returned true for this system, which means
|
||||
// that no other systems currently have access to the world.
|
||||
let world = unsafe { context.environment.world_cell.world_mut() };
|
||||
// SAFETY: this system is not running, no other reference exists
|
||||
let system = unsafe { &mut *context.environment.systems[system_index].get() };
|
||||
// Move the full context object into the new future.
|
||||
|
@ -626,6 +623,9 @@ impl ExecutorState {
|
|||
let unapplied_systems = self.unapplied_systems.clone();
|
||||
self.unapplied_systems.clear();
|
||||
let task = async move {
|
||||
// SAFETY: `can_run` returned true for this system, which means
|
||||
// that no other systems currently have access to the world.
|
||||
let world = unsafe { context.environment.world_cell.world_mut() };
|
||||
let res = apply_deferred(&unapplied_systems, context.environment.systems, world);
|
||||
context.system_completed(system_index, res, system);
|
||||
};
|
||||
|
@ -633,6 +633,9 @@ impl ExecutorState {
|
|||
context.scope.spawn_on_scope(task);
|
||||
} else {
|
||||
let task = async move {
|
||||
// SAFETY: `can_run` returned true for this system, which means
|
||||
// that no other systems currently have access to the world.
|
||||
let world = unsafe { context.environment.world_cell.world_mut() };
|
||||
let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
__rust_begin_short_backtrace::run(&mut **system, world);
|
||||
}));
|
||||
|
@ -783,4 +786,16 @@ mod tests {
|
|||
schedule.run(&mut world);
|
||||
assert!(world.get_resource::<R>().is_some());
|
||||
}
|
||||
|
||||
/// Regression test for a weird bug flagged by MIRI in
|
||||
/// `spawn_exclusive_system_task`, related to a `&mut World` being captured
|
||||
/// inside an `async` block and somehow remaining alive even after its last use.
|
||||
#[test]
|
||||
fn check_spawn_exclusive_system_task_miri() {
|
||||
let mut world = World::new();
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.set_executor_kind(ExecutorKind::MultiThreaded);
|
||||
schedule.add_systems(((|_: Commands| {}), |_: Commands| {}).chain());
|
||||
schedule.run(&mut world);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1608,7 +1608,7 @@ impl ScheduleGraph {
|
|||
}
|
||||
};
|
||||
if self.settings.use_shortnames {
|
||||
name = bevy_utils::get_short_name(&name);
|
||||
name = bevy_utils::ShortName(&name).to_string();
|
||||
}
|
||||
name
|
||||
}
|
||||
|
|
495
crates/bevy_ecs/src/storage/blob_array.rs
Normal file
495
crates/bevy_ecs/src/storage/blob_array.rs
Normal file
|
@ -0,0 +1,495 @@
|
|||
use super::blob_vec::array_layout;
|
||||
use crate::storage::blob_vec::array_layout_unchecked;
|
||||
use bevy_ptr::{OwningPtr, Ptr, PtrMut};
|
||||
use bevy_utils::OnDrop;
|
||||
use std::{
|
||||
alloc::{handle_alloc_error, Layout},
|
||||
cell::UnsafeCell,
|
||||
num::NonZeroUsize,
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
/// A flat, type-erased data storage type similar to a [`BlobVec`](super::blob_vec::BlobVec), but with the length and capacity cut out
|
||||
/// for performance reasons. This type is reliant on its owning type to store the capacity and length information.
|
||||
///
|
||||
/// Used to densely store homogeneous ECS data. A blob is usually just an arbitrary block of contiguous memory without any identity, and
|
||||
/// could be used to represent any arbitrary data (i.e. string, arrays, etc). This type only stores meta-data about the Blob that it stores,
|
||||
/// and a pointer to the location of the start of the array, similar to a C array.
|
||||
pub(super) struct BlobArray {
|
||||
item_layout: Layout,
|
||||
// the `data` ptr's layout is always `array_layout(item_layout, capacity)`
|
||||
data: NonNull<u8>,
|
||||
// None if the underlying type doesn't need to be dropped
|
||||
pub drop: Option<unsafe fn(OwningPtr<'_>)>,
|
||||
#[cfg(debug_assertions)]
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl BlobArray {
|
||||
/// Create a new [`BlobArray`] with a specified `capacity`.
|
||||
/// If `capacity` is 0, no allocations will be made.
|
||||
///
|
||||
/// `drop` is an optional function pointer that is meant to be invoked when any element in the [`BlobArray`]
|
||||
/// should be dropped. For all Rust-based types, this should match 1:1 with the implementation of [`Drop`]
|
||||
/// if present, and should be `None` if `T: !Drop`. For non-Rust based types, this should match any cleanup
|
||||
/// processes typically associated with the stored element.
|
||||
///
|
||||
/// # Safety
|
||||
/// `drop` should be safe to call with an [`OwningPtr`] pointing to any item that's been placed into this [`BlobArray`].
|
||||
/// If `drop` is `None`, the items will be leaked. This should generally be set as None based on [`needs_drop`].
|
||||
///
|
||||
/// [`needs_drop`]: core::mem::needs_drop
|
||||
pub unsafe fn with_capacity(
|
||||
item_layout: Layout,
|
||||
drop_fn: Option<unsafe fn(OwningPtr<'_>)>,
|
||||
capacity: usize,
|
||||
) -> Self {
|
||||
if capacity == 0 {
|
||||
let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0");
|
||||
let data = bevy_ptr::dangling_with_align(align);
|
||||
Self {
|
||||
item_layout,
|
||||
drop: drop_fn,
|
||||
data,
|
||||
#[cfg(debug_assertions)]
|
||||
capacity,
|
||||
}
|
||||
} else {
|
||||
let mut arr = Self::with_capacity(item_layout, drop_fn, 0);
|
||||
// SAFETY: `capacity` > 0
|
||||
unsafe { arr.alloc(NonZeroUsize::new_unchecked(capacity)) }
|
||||
arr
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Layout`] of the element type stored in the vector.
|
||||
#[inline]
|
||||
pub fn layout(&self) -> Layout {
|
||||
self.item_layout
|
||||
}
|
||||
|
||||
/// Return `true` if this [`BlobArray`] stores `ZSTs`.
|
||||
pub fn is_zst(&self) -> bool {
|
||||
self.item_layout.size() == 0
|
||||
}
|
||||
|
||||
/// Returns a reference to the element at `index`, without doing bounds checking.
|
||||
///
|
||||
/// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
|
||||
/// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).*
|
||||
///
|
||||
/// # Safety
|
||||
/// - The element at index `index` is safe to access.
|
||||
/// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `index` < `len`)
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked(&self, index: usize) -> Ptr<'_> {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(index < self.capacity);
|
||||
let size = self.item_layout.size();
|
||||
// SAFETY:
|
||||
// - The caller ensures that `index` fits in this array,
|
||||
// so this operation will not overflow the original allocation.
|
||||
// - `size` is a multiple of the erased type's alignment,
|
||||
// so adding a multiple of `size` will preserve alignment.
|
||||
unsafe { self.get_ptr().byte_add(index * size) }
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the element at `index`, without doing bounds checking.
|
||||
///
|
||||
/// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
|
||||
/// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).*
|
||||
///
|
||||
/// # Safety
|
||||
/// - The element with at index `index` is safe to access.
|
||||
/// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `index` < `len`)
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> PtrMut<'_> {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(index < self.capacity);
|
||||
let size = self.item_layout.size();
|
||||
// SAFETY:
|
||||
// - The caller ensures that `index` fits in this vector,
|
||||
// so this operation will not overflow the original allocation.
|
||||
// - `size` is a multiple of the erased type's alignment,
|
||||
// so adding a multiple of `size` will preserve alignment.
|
||||
unsafe { self.get_ptr_mut().byte_add(index * size) }
|
||||
}
|
||||
|
||||
/// Gets a [`Ptr`] to the start of the array
|
||||
#[inline]
|
||||
pub fn get_ptr(&self) -> Ptr<'_> {
|
||||
// SAFETY: the inner data will remain valid for as long as 'self.
|
||||
unsafe { Ptr::new(self.data) }
|
||||
}
|
||||
|
||||
/// Gets a [`PtrMut`] to the start of the array
|
||||
#[inline]
|
||||
pub fn get_ptr_mut(&mut self) -> PtrMut<'_> {
|
||||
// SAFETY: the inner data will remain valid for as long as 'self.
|
||||
unsafe { PtrMut::new(self.data) }
|
||||
}
|
||||
|
||||
/// Get a slice of the first `slice_len` elements in [`BlobArray`] as if it were an array with elements of type `T`
|
||||
/// To get a slice to the entire array, the caller must plug `len` in `slice_len`.
|
||||
///
|
||||
/// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
|
||||
/// Just like [`Vec::len`], or [`BlobVec::len`](super::blob_vec::BlobVec::len).*
|
||||
///
|
||||
/// # Safety
|
||||
/// - The type `T` must be the type of the items in this [`BlobArray`].
|
||||
/// - `slice_len` <= `len`
|
||||
pub unsafe fn get_sub_slice<T>(&self, slice_len: usize) -> &[UnsafeCell<T>] {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(slice_len <= self.capacity);
|
||||
// SAFETY: the inner data will remain valid for as long as 'self.
|
||||
unsafe { std::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell<T>, slice_len) }
|
||||
}
|
||||
|
||||
/// Clears the array, i.e. removing (and dropping) all of the elements.
|
||||
/// Note that this method has no effect on the allocated capacity of the vector.
|
||||
///
|
||||
/// Note that this method will behave exactly the same as [`Vec::clear`].
|
||||
///
|
||||
/// # Safety
|
||||
/// - For every element with index `i`, if `i` < `len`: It must be safe to call [`Self::get_unchecked_mut`] with `i`.
|
||||
/// (If the safety requirements of every method that has been used on `Self` have been fulfilled, the caller just needs to ensure that `len` is correct.)
|
||||
pub unsafe fn clear(&mut self, len: usize) {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(self.capacity >= len);
|
||||
if let Some(drop) = self.drop {
|
||||
// We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't
|
||||
// accidentally drop elements twice in the event of a drop impl panicking.
|
||||
self.drop = None;
|
||||
let size = self.item_layout.size();
|
||||
for i in 0..len {
|
||||
// SAFETY:
|
||||
// * 0 <= `i` < `len`, so `i * size` must be in bounds for the allocation.
|
||||
// * `size` is a multiple of the erased type's alignment,
|
||||
// so adding a multiple of `size` will preserve alignment.
|
||||
// * The item is left unreachable so it can be safely promoted to an `OwningPtr`.
|
||||
let item = unsafe { self.get_ptr_mut().byte_add(i * size).promote() };
|
||||
// SAFETY: `item` was obtained from this `BlobArray`, so its underlying type must match `drop`.
|
||||
unsafe { drop(item) };
|
||||
}
|
||||
self.drop = Some(drop);
|
||||
}
|
||||
}
|
||||
|
||||
/// Because this method needs parameters, it can't be the implementation of the `Drop` trait.
|
||||
/// The owner of this [`BlobArray`] must call this method with the correct information.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `cap` and `len` are indeed the capacity and length of this [`BlobArray`]
|
||||
/// - This [`BlobArray`] mustn't be used after calling this method.
|
||||
pub unsafe fn drop(&mut self, cap: usize, len: usize) {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert_eq!(self.capacity, cap);
|
||||
if cap != 0 {
|
||||
self.clear(len);
|
||||
if !self.is_zst() {
|
||||
let layout =
|
||||
array_layout(&self.item_layout, cap).expect("array layout should be valid");
|
||||
std::alloc::dealloc(self.data.as_ptr().cast(), layout);
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
self.capacity = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops the last element in this [`BlobArray`].
|
||||
///
|
||||
/// # Safety
|
||||
// - `last_element_index` must correspond to the last element in the array.
|
||||
// - After this method is called, the last element must not be used
|
||||
// unless [`Self::initialize_unchecked`] is called to set the value of the last element.
|
||||
pub unsafe fn drop_last_element(&mut self, last_element_index: usize) {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(self.capacity > last_element_index);
|
||||
if let Some(drop) = self.drop {
|
||||
// We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't
|
||||
// accidentally drop elements twice in the event of a drop impl panicking.
|
||||
self.drop = None;
|
||||
// SAFETY:
|
||||
let item = self.get_unchecked_mut(last_element_index).promote();
|
||||
// SAFETY:
|
||||
unsafe { drop(item) };
|
||||
self.drop = Some(drop);
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a block of memory for the array. This should be used to initialize the array, do not use this
|
||||
/// method if there are already elements stored in the array - use [`Self::realloc`] instead.
|
||||
pub(super) fn alloc(&mut self, capacity: NonZeroUsize) {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert_eq!(self.capacity, 0);
|
||||
if !self.is_zst() {
|
||||
let new_layout = array_layout(&self.item_layout, capacity.get())
|
||||
.expect("array layout should be valid");
|
||||
// SAFETY: layout has non-zero size because capacity > 0, and the blob isn't ZST (`self.is_zst` == false)
|
||||
let new_data = unsafe { std::alloc::alloc(new_layout) };
|
||||
self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout));
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
self.capacity = capacity.into();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reallocate memory for this array.
|
||||
/// For example, if the length (number of stored elements) reached the capacity (number of elements the current allocation can store),
|
||||
/// you might want to use this method to increase the allocation, so more data can be stored in the array.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `current_capacity` is indeed the current capacity of this array.
|
||||
/// - After calling this method, the caller must update their saved capacity to reflect the change.
|
||||
pub(super) unsafe fn realloc(
|
||||
&mut self,
|
||||
current_capacity: NonZeroUsize,
|
||||
new_capacity: NonZeroUsize,
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert_eq!(self.capacity, current_capacity.into());
|
||||
if !self.is_zst() {
|
||||
// SAFETY: `new_capacity` can't overflow usize
|
||||
let new_layout =
|
||||
unsafe { array_layout_unchecked(&self.item_layout, new_capacity.get()) };
|
||||
// SAFETY:
|
||||
// - ptr was be allocated via this allocator
|
||||
// - the layout used to previously allocate this array is equivalent to `array_layout(&self.item_layout, current_capacity.get())`
|
||||
// - `item_layout.size() > 0` (`self.is_zst`==false) and `new_capacity > 0`, so the layout size is non-zero
|
||||
// - "new_size, when rounded up to the nearest multiple of layout.align(), must not overflow (i.e., the rounded value must be less than usize::MAX)",
|
||||
// since the item size is always a multiple of its align, the rounding cannot happen
|
||||
// here and the overflow is handled in `array_layout`
|
||||
let new_data = unsafe {
|
||||
std::alloc::realloc(
|
||||
self.get_ptr_mut().as_ptr(),
|
||||
// SAFETY: This is the Layout of the current array, it must be valid, if it hadn't have been, there would have been a panic on a previous allocation
|
||||
array_layout_unchecked(&self.item_layout, current_capacity.get()),
|
||||
new_layout.size(),
|
||||
)
|
||||
};
|
||||
self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout));
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
self.capacity = new_capacity.into();
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the value at `index` to `value`. This function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index` must be in bounds (`index` < capacity)
|
||||
/// - The [`Layout`] of the value must match the layout of the blobs stored in this array,
|
||||
/// and it must be safe to use the `drop` function of this [`BlobArray`] to drop `value`.
|
||||
/// - `value` must not point to the same value that is being initialized.
|
||||
#[inline]
|
||||
pub unsafe fn initialize_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(self.capacity > index);
|
||||
let size = self.item_layout.size();
|
||||
let dst = self.get_unchecked_mut(index);
|
||||
std::ptr::copy::<u8>(value.as_ptr(), dst.as_ptr(), size);
|
||||
}
|
||||
|
||||
/// Replaces the value at `index` with `value`. This function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - Index must be in-bounds (`index` < `len`)
|
||||
/// - `value`'s [`Layout`] must match this [`BlobArray`]'s `item_layout`,
|
||||
/// and it must be safe to use the `drop` function of this [`BlobArray`] to drop `value`.
|
||||
/// - `value` must not point to the same value that is being replaced.
|
||||
pub unsafe fn replace_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(self.capacity > index);
|
||||
// Pointer to the value in the vector that will get replaced.
|
||||
// SAFETY: The caller ensures that `index` fits in this vector.
|
||||
let destination = NonNull::from(unsafe { self.get_unchecked_mut(index) });
|
||||
let source = value.as_ptr();
|
||||
|
||||
if let Some(drop) = self.drop {
|
||||
// We set `self.drop` to `None` before dropping elements for unwind safety. This ensures we don't
|
||||
// accidentally drop elements twice in the event of a drop impl panicking.
|
||||
self.drop = None;
|
||||
|
||||
// Transfer ownership of the old value out of the vector, so it can be dropped.
|
||||
// SAFETY:
|
||||
// - `destination` was obtained from a `PtrMut` in this vector, which ensures it is non-null,
|
||||
// well-aligned for the underlying type, and has proper provenance.
|
||||
// - The storage location will get overwritten with `value` later, which ensures
|
||||
// that the element will not get observed or double dropped later.
|
||||
// - If a panic occurs, `self.len` will remain `0`, which ensures a double-drop
|
||||
// does not occur. Instead, all elements will be forgotten.
|
||||
let old_value = unsafe { OwningPtr::new(destination) };
|
||||
|
||||
// This closure will run in case `drop()` panics,
|
||||
// which ensures that `value` does not get forgotten.
|
||||
let on_unwind = OnDrop::new(|| drop(value));
|
||||
|
||||
drop(old_value);
|
||||
|
||||
// If the above code does not panic, make sure that `value` doesn't get dropped.
|
||||
core::mem::forget(on_unwind);
|
||||
|
||||
self.drop = Some(drop);
|
||||
}
|
||||
|
||||
// Copy the new value into the vector, overwriting the previous value.
|
||||
// SAFETY:
|
||||
// - `source` and `destination` were obtained from `OwningPtr`s, which ensures they are
|
||||
// valid for both reads and writes.
|
||||
// - The value behind `source` will only be dropped if the above branch panics,
|
||||
// so it must still be initialized and it is safe to transfer ownership into the vector.
|
||||
// - `source` and `destination` were obtained from different memory locations,
|
||||
// both of which we have exclusive access to, so they are guaranteed not to overlap.
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping::<u8>(
|
||||
source,
|
||||
destination.as_ptr(),
|
||||
self.item_layout.size(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// This method will swap two elements in the array, and return the one at `index_to_remove`.
|
||||
/// It is the caller's responsibility to drop the returned pointer, if that is desirable.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` != `index_to_keep`
|
||||
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
|
||||
/// 1) initialize a different value in `index_to_keep`
|
||||
/// 2) update the saved length of the array if `index_to_keep` was the last element.
|
||||
#[inline]
|
||||
#[must_use = "The returned pointer should be used to drop the removed element"]
|
||||
pub unsafe fn swap_remove_unchecked(
|
||||
&mut self,
|
||||
index_to_remove: usize,
|
||||
index_to_keep: usize,
|
||||
) -> OwningPtr<'_> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
debug_assert!(self.capacity > index_to_keep);
|
||||
debug_assert!(self.capacity > index_to_remove);
|
||||
}
|
||||
if index_to_remove != index_to_keep {
|
||||
return self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep);
|
||||
}
|
||||
// Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
|
||||
// If we are storing ZSTs than the index doesn't actually matter because the size is 0.
|
||||
self.get_unchecked_mut(index_to_keep).promote()
|
||||
}
|
||||
|
||||
/// The same as [`Self::swap_remove_unchecked`] but the two elements must non-overlapping.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` != `index_to_keep`
|
||||
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
|
||||
/// 1) initialize a different value in `index_to_keep`
|
||||
/// 2) update the saved length of the array if `index_to_keep` was the last element.
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_unchecked_nonoverlapping(
|
||||
&mut self,
|
||||
index_to_remove: usize,
|
||||
index_to_keep: usize,
|
||||
) -> OwningPtr<'_> {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
debug_assert!(self.capacity > index_to_keep);
|
||||
debug_assert!(self.capacity > index_to_remove);
|
||||
debug_assert_ne!(index_to_keep, index_to_remove);
|
||||
}
|
||||
debug_assert_ne!(index_to_keep, index_to_remove);
|
||||
std::ptr::swap_nonoverlapping::<u8>(
|
||||
self.get_unchecked_mut(index_to_keep).as_ptr(),
|
||||
self.get_unchecked_mut(index_to_remove).as_ptr(),
|
||||
self.item_layout.size(),
|
||||
);
|
||||
// Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
|
||||
// If we are storing ZSTs than the index doesn't actually matter because the size is 0.
|
||||
self.get_unchecked_mut(index_to_keep).promote()
|
||||
}
|
||||
|
||||
/// This method will can [`Self::swap_remove_unchecked`] and drop the result.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` != `index_to_keep`
|
||||
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
|
||||
/// 1) initialize a different value in `index_to_keep`
|
||||
/// 2) update the saved length of the array if `index_to_keep` was the last element.
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_and_drop_unchecked(
|
||||
&mut self,
|
||||
index_to_remove: usize,
|
||||
index_to_keep: usize,
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
debug_assert!(self.capacity > index_to_keep);
|
||||
debug_assert!(self.capacity > index_to_remove);
|
||||
}
|
||||
let drop = self.drop;
|
||||
let value = self.swap_remove_unchecked(index_to_remove, index_to_keep);
|
||||
if let Some(drop) = drop {
|
||||
drop(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as [`Self::swap_remove_and_drop_unchecked`] but the two elements must non-overlapping.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` != `index_to_keep`
|
||||
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
|
||||
/// 1) initialize a different value in `index_to_keep`
|
||||
/// 2) update the saved length of the array if `index_to_keep` was the last element.
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_and_drop_unchecked_nonoverlapping(
|
||||
&mut self,
|
||||
index_to_remove: usize,
|
||||
index_to_keep: usize,
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
debug_assert!(self.capacity > index_to_keep);
|
||||
debug_assert!(self.capacity > index_to_remove);
|
||||
debug_assert_ne!(index_to_keep, index_to_remove);
|
||||
}
|
||||
let drop = self.drop;
|
||||
let value = self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep);
|
||||
if let Some(drop) = drop {
|
||||
drop(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as bevy_ecs;
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
struct PanicOnDrop;
|
||||
|
||||
impl Drop for PanicOnDrop {
|
||||
fn drop(&mut self) {
|
||||
panic!("PanicOnDrop is being Dropped");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "PanicOnDrop is being Dropped")]
|
||||
fn make_sure_zst_components_get_dropped() {
|
||||
let mut world = World::new();
|
||||
|
||||
world.spawn(PanicOnDrop);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
use bevy_ptr::{OwningPtr, Ptr, PtrMut};
|
||||
use bevy_utils::OnDrop;
|
||||
use std::{
|
||||
alloc::{handle_alloc_error, Layout},
|
||||
cell::UnsafeCell,
|
||||
|
@ -5,9 +7,6 @@ use std::{
|
|||
ptr::NonNull,
|
||||
};
|
||||
|
||||
use bevy_ptr::{OwningPtr, Ptr, PtrMut};
|
||||
use bevy_utils::OnDrop;
|
||||
|
||||
/// A flat, type-erased data storage type
|
||||
///
|
||||
/// Used to densely store homogeneous ECS data. A blob is usually just an arbitrary block of contiguous memory without any identity, and
|
||||
|
@ -93,12 +92,6 @@ impl BlobVec {
|
|||
self.len == 0
|
||||
}
|
||||
|
||||
/// Returns the total number of elements the vector can hold without reallocating.
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.capacity
|
||||
}
|
||||
|
||||
/// Returns the [`Layout`] of the element type stored in the vector.
|
||||
#[inline]
|
||||
pub fn layout(&self) -> Layout {
|
||||
|
@ -271,26 +264,12 @@ impl BlobVec {
|
|||
self.initialize_unchecked(index, value);
|
||||
}
|
||||
|
||||
/// Forces the length of the vector to `len`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `len` must be <= `capacity`. if length is decreased, "out of bounds" items must be dropped.
|
||||
/// Newly added items must be immediately populated with valid values and length must be
|
||||
/// increased. For better unwind safety, call [`BlobVec::set_len`] _after_ populating a new
|
||||
/// value.
|
||||
#[inline]
|
||||
pub unsafe fn set_len(&mut self, len: usize) {
|
||||
debug_assert!(len <= self.capacity());
|
||||
self.len = len;
|
||||
}
|
||||
|
||||
/// Performs a "swap remove" at the given `index`, which removes the item at `index` and moves
|
||||
/// the last item in the [`BlobVec`] to `index` (if `index` is not the last item). It is the
|
||||
/// caller's responsibility to drop the returned pointer, if that is desirable.
|
||||
///
|
||||
/// # Safety
|
||||
/// It is the caller's responsibility to ensure that `index` is less than `self.len()`.
|
||||
#[inline]
|
||||
#[must_use = "The returned pointer should be used to dropped the removed element"]
|
||||
pub unsafe fn swap_remove_and_forget_unchecked(&mut self, index: usize) -> OwningPtr<'_> {
|
||||
debug_assert!(index < self.len());
|
||||
|
@ -318,28 +297,6 @@ impl BlobVec {
|
|||
unsafe { p.promote() }
|
||||
}
|
||||
|
||||
/// Removes the value at `index` and copies the value stored into `ptr`.
|
||||
/// Does not do any bounds checking on `index`.
|
||||
/// The removed element is replaced by the last element of the `BlobVec`.
|
||||
///
|
||||
/// # Safety
|
||||
/// It is the caller's responsibility to ensure that `index` is < `self.len()`
|
||||
/// and that `self[index]` has been properly initialized.
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_unchecked(&mut self, index: usize, ptr: PtrMut<'_>) {
|
||||
debug_assert!(index < self.len());
|
||||
let last = self.get_unchecked_mut(self.len - 1).as_ptr();
|
||||
let target = self.get_unchecked_mut(index).as_ptr();
|
||||
// Copy the item at the index into the provided ptr
|
||||
std::ptr::copy_nonoverlapping::<u8>(target, ptr.as_ptr(), self.item_layout.size());
|
||||
// Recompress the storage by moving the previous last element into the
|
||||
// now-free row overwriting the previous data. The removed row may be the last
|
||||
// one so a non-overlapping copy must not be used here.
|
||||
std::ptr::copy::<u8>(last, target, self.item_layout.size());
|
||||
// Invalidate the data stored in the last row, as it has been moved
|
||||
self.len -= 1;
|
||||
}
|
||||
|
||||
/// Removes the value at `index` and drops it.
|
||||
/// Does not do any bounds checking on `index`.
|
||||
/// The removed element is replaced by the last element of the `BlobVec`.
|
||||
|
@ -438,14 +395,6 @@ impl BlobVec {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `drop` argument that was passed to `BlobVec::new`.
|
||||
///
|
||||
/// Callers can use this if they have a type-erased pointer of the correct
|
||||
/// type to add to this [`BlobVec`], which they just want to drop instead.
|
||||
pub fn get_drop(&self) -> Option<unsafe fn(OwningPtr<'_>)> {
|
||||
self.drop
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BlobVec {
|
||||
|
@ -463,7 +412,7 @@ impl Drop for BlobVec {
|
|||
}
|
||||
|
||||
/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
|
||||
fn array_layout(layout: &Layout, n: usize) -> Option<Layout> {
|
||||
pub(super) fn array_layout(layout: &Layout, n: usize) -> Option<Layout> {
|
||||
let (array_layout, offset) = repeat_layout(layout, n)?;
|
||||
debug_assert_eq!(layout.size(), offset);
|
||||
Some(array_layout)
|
||||
|
@ -489,6 +438,40 @@ fn repeat_layout(layout: &Layout, n: usize) -> Option<(Layout, usize)> {
|
|||
}
|
||||
}
|
||||
|
||||
/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
|
||||
/// # Safety
|
||||
/// The caller must ensure that:
|
||||
/// - The resulting [`Layout`] is valid, by ensuring that `(layout.size() + padding_needed_for(layout, layout.align())) * n` doesn't overflow.
|
||||
pub(super) unsafe fn array_layout_unchecked(layout: &Layout, n: usize) -> Layout {
|
||||
let (array_layout, offset) = repeat_layout_unchecked(layout, n);
|
||||
debug_assert_eq!(layout.size(), offset);
|
||||
array_layout
|
||||
}
|
||||
|
||||
// TODO: replace with `Layout::repeat` if/when it stabilizes
|
||||
/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
|
||||
/// # Safety
|
||||
/// The caller must ensure that:
|
||||
/// - The resulting [`Layout`] is valid, by ensuring that `(layout.size() + padding_needed_for(layout, layout.align())) * n` doesn't overflow.
|
||||
unsafe fn repeat_layout_unchecked(layout: &Layout, n: usize) -> (Layout, usize) {
|
||||
// This cannot overflow. Quoting from the invariant of Layout:
|
||||
// > `size`, when rounded up to the nearest multiple of `align`,
|
||||
// > must not overflow (i.e., the rounded value must be less than
|
||||
// > `usize::MAX`)
|
||||
let padded_size = layout.size() + padding_needed_for(layout, layout.align());
|
||||
// This may overflow in release builds, that's why this function is unsafe.
|
||||
let alloc_size = padded_size * n;
|
||||
|
||||
// SAFETY: self.align is already known to be valid and alloc_size has been
|
||||
// padded already.
|
||||
unsafe {
|
||||
(
|
||||
Layout::from_size_align_unchecked(alloc_size, layout.align()),
|
||||
padded_size,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// From <https://doc.rust-lang.org/beta/src/core/alloc/layout.rs.html>
|
||||
const fn padding_needed_for(layout: &Layout, align: usize) -> usize {
|
||||
let len = layout.size();
|
||||
|
@ -522,10 +505,14 @@ mod tests {
|
|||
use crate::{component::Component, ptr::OwningPtr, world::World};
|
||||
|
||||
use super::BlobVec;
|
||||
use std::{alloc::Layout, cell::RefCell, mem::align_of, rc::Rc};
|
||||
use std::{alloc::Layout, cell::RefCell, rc::Rc};
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The pointer `x` must point to a valid value of type `T` and it must be safe to drop this value.
|
||||
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
|
||||
// SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
|
||||
// SAFETY: It is guaranteed by the caller that `x` points to a
|
||||
// valid value of type `T` and it is safe to drop this value.
|
||||
unsafe {
|
||||
x.drop_as::<T>();
|
||||
}
|
||||
|
@ -571,7 +558,7 @@ mod tests {
|
|||
}
|
||||
|
||||
assert_eq!(blob_vec.len(), 1_000);
|
||||
assert_eq!(blob_vec.capacity(), 1_024);
|
||||
assert_eq!(blob_vec.capacity, 1_024);
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
|
@ -595,7 +582,7 @@ mod tests {
|
|||
let drop = drop_ptr::<Foo>;
|
||||
// SAFETY: drop is able to drop a value of its `item_layout`
|
||||
let mut blob_vec = unsafe { BlobVec::new(item_layout, Some(drop), 2) };
|
||||
assert_eq!(blob_vec.capacity(), 2);
|
||||
assert_eq!(blob_vec.capacity, 2);
|
||||
// SAFETY: the following code only deals with values of type `Foo`, which satisfies the safety requirement of `push`, `get_mut` and `swap_remove` that the
|
||||
// values have a layout compatible to the blob vec's `item_layout`.
|
||||
// Every index is in range.
|
||||
|
@ -616,7 +603,7 @@ mod tests {
|
|||
};
|
||||
push::<Foo>(&mut blob_vec, foo2.clone());
|
||||
assert_eq!(blob_vec.len(), 2);
|
||||
assert_eq!(blob_vec.capacity(), 2);
|
||||
assert_eq!(blob_vec.capacity, 2);
|
||||
assert_eq!(get_mut::<Foo>(&mut blob_vec, 0), &foo1);
|
||||
assert_eq!(get_mut::<Foo>(&mut blob_vec, 1), &foo2);
|
||||
|
||||
|
@ -631,19 +618,19 @@ mod tests {
|
|||
|
||||
push(&mut blob_vec, foo3.clone());
|
||||
assert_eq!(blob_vec.len(), 3);
|
||||
assert_eq!(blob_vec.capacity(), 4);
|
||||
assert_eq!(blob_vec.capacity, 4);
|
||||
|
||||
let last_index = blob_vec.len() - 1;
|
||||
let value = swap_remove::<Foo>(&mut blob_vec, last_index);
|
||||
assert_eq!(foo3, value);
|
||||
|
||||
assert_eq!(blob_vec.len(), 2);
|
||||
assert_eq!(blob_vec.capacity(), 4);
|
||||
assert_eq!(blob_vec.capacity, 4);
|
||||
|
||||
let value = swap_remove::<Foo>(&mut blob_vec, 0);
|
||||
assert_eq!(foo1, value);
|
||||
assert_eq!(blob_vec.len(), 1);
|
||||
assert_eq!(blob_vec.capacity(), 4);
|
||||
assert_eq!(blob_vec.capacity, 4);
|
||||
|
||||
foo2.a = 8;
|
||||
assert_eq!(get_mut::<Foo>(&mut blob_vec, 0), &foo2);
|
||||
|
@ -667,14 +654,12 @@ mod tests {
|
|||
// SAFETY: no drop is correct drop for `()`.
|
||||
let mut blob_vec = unsafe { BlobVec::new(Layout::new::<()>(), None, 0) };
|
||||
|
||||
assert_eq!(usize::MAX, blob_vec.capacity(), "Self-check");
|
||||
assert_eq!(usize::MAX, blob_vec.capacity, "Self-check");
|
||||
|
||||
// SAFETY: Because `()` is a ZST trivial drop type, and because `BlobVec` capacity
|
||||
// is always `usize::MAX` for ZSTs, we can arbitrarily set the length
|
||||
// and still be sound.
|
||||
unsafe {
|
||||
blob_vec.set_len(usize::MAX);
|
||||
}
|
||||
blob_vec.len = usize::MAX;
|
||||
|
||||
// SAFETY: `BlobVec` was initialized for `()`, so it is safe to push `()` to it.
|
||||
unsafe {
|
||||
|
@ -691,7 +676,7 @@ mod tests {
|
|||
// SAFETY: no drop is correct drop for `u32`.
|
||||
let mut blob_vec = unsafe { BlobVec::new(Layout::new::<u32>(), None, 0) };
|
||||
|
||||
assert_eq!(0, blob_vec.capacity(), "Self-check");
|
||||
assert_eq!(0, blob_vec.capacity, "Self-check");
|
||||
|
||||
OwningPtr::make(17u32, |ptr| {
|
||||
// SAFETY: we push the value of correct type.
|
||||
|
|
|
@ -20,10 +20,12 @@
|
|||
//! [`World`]: crate::world::World
|
||||
//! [`World::storages`]: crate::world::World::storages
|
||||
|
||||
mod blob_array;
|
||||
mod blob_vec;
|
||||
mod resource;
|
||||
mod sparse_set;
|
||||
mod table;
|
||||
mod thin_array_ptr;
|
||||
|
||||
pub use resource::*;
|
||||
pub use sparse_set::*;
|
||||
|
|
|
@ -25,7 +25,10 @@ pub struct ResourceData<const SEND: bool> {
|
|||
|
||||
impl<const SEND: bool> Drop for ResourceData<SEND> {
|
||||
fn drop(&mut self) {
|
||||
if self.is_present() {
|
||||
// For Non Send resources we need to validate that correct thread
|
||||
// is dropping the resource. This validation is not needed in case
|
||||
// of SEND resources. Or if there is no data.
|
||||
if !SEND && self.is_present() {
|
||||
// If this thread is already panicking, panicking again will cause
|
||||
// the entire process to abort. In this case we choose to avoid
|
||||
// dropping or checking this altogether and just leak the column.
|
||||
|
|
File diff suppressed because it is too large
Load diff
688
crates/bevy_ecs/src/storage/table/column.rs
Normal file
688
crates/bevy_ecs/src/storage/table/column.rs
Normal file
|
@ -0,0 +1,688 @@
|
|||
use super::*;
|
||||
use crate::{
|
||||
component::TickCells,
|
||||
storage::{blob_array::BlobArray, thin_array_ptr::ThinArrayPtr},
|
||||
};
|
||||
use bevy_ptr::PtrMut;
|
||||
|
||||
/// Very similar to a normal [`Column`], but with the capacities and lengths cut out for performance reasons.
|
||||
/// This type is used by [`Table`], because all of the capacities and lengths of the [`Table`]'s columns must match.
|
||||
///
|
||||
/// Like many other low-level storage types, [`ThinColumn`] has a limited and highly unsafe
|
||||
/// interface. It's highly advised to use higher level types and their safe abstractions
|
||||
/// instead of working directly with [`ThinColumn`].
|
||||
pub struct ThinColumn {
|
||||
pub(super) data: BlobArray,
|
||||
pub(super) added_ticks: ThinArrayPtr<UnsafeCell<Tick>>,
|
||||
pub(super) changed_ticks: ThinArrayPtr<UnsafeCell<Tick>>,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
pub(super) changed_by: ThinArrayPtr<UnsafeCell<&'static Location<'static>>>,
|
||||
}
|
||||
|
||||
impl ThinColumn {
|
||||
/// Create a new [`ThinColumn`] with the given `capacity`.
|
||||
pub fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self {
|
||||
Self {
|
||||
// SAFETY: The components stored in this columns will match the information in `component_info`
|
||||
data: unsafe {
|
||||
BlobArray::with_capacity(component_info.layout(), component_info.drop(), capacity)
|
||||
},
|
||||
added_ticks: ThinArrayPtr::with_capacity(capacity),
|
||||
changed_ticks: ThinArrayPtr::with_capacity(capacity),
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
changed_by: ThinArrayPtr::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Swap-remove and drop the removed element, but the component at `row` must not be the last element.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `row.as_usize()` < `len`
|
||||
/// - `last_element_index` = `len - 1`
|
||||
/// - `last_element_index` != `row.as_usize()`
|
||||
/// - The caller should update the `len` to `len - 1`, or immediately initialize another element in the `last_element_index`
|
||||
pub(crate) unsafe fn swap_remove_and_drop_unchecked_nonoverlapping(
|
||||
&mut self,
|
||||
last_element_index: usize,
|
||||
row: TableRow,
|
||||
) {
|
||||
self.data
|
||||
.swap_remove_and_drop_unchecked_nonoverlapping(row.as_usize(), last_element_index);
|
||||
self.added_ticks
|
||||
.swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index);
|
||||
self.changed_ticks
|
||||
.swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by
|
||||
.swap_remove_unchecked_nonoverlapping(row.as_usize(), last_element_index);
|
||||
}
|
||||
|
||||
/// Swap-remove and drop the removed element.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `last_element_index` must be the index of the last element—stored in the highest place in memory.
|
||||
/// - `row.as_usize()` <= `last_element_index`
|
||||
/// - The caller should update the their saved length to reflect the change (decrement it by 1).
|
||||
pub(crate) unsafe fn swap_remove_and_drop_unchecked(
|
||||
&mut self,
|
||||
last_element_index: usize,
|
||||
row: TableRow,
|
||||
) {
|
||||
self.data
|
||||
.swap_remove_and_drop_unchecked(row.as_usize(), last_element_index);
|
||||
self.added_ticks
|
||||
.swap_remove_and_drop_unchecked(row.as_usize(), last_element_index);
|
||||
self.changed_ticks
|
||||
.swap_remove_and_drop_unchecked(row.as_usize(), last_element_index);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by
|
||||
.swap_remove_and_drop_unchecked(row.as_usize(), last_element_index);
|
||||
}
|
||||
|
||||
/// Swap-remove and forget the removed element.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `last_element_index` must be the index of the last element—stored in the highest place in memory.
|
||||
/// - `row.as_usize()` <= `last_element_index`
|
||||
/// - The caller should update the their saved length to reflect the change (decrement it by 1).
|
||||
pub(crate) unsafe fn swap_remove_and_forget_unchecked(
|
||||
&mut self,
|
||||
last_element_index: usize,
|
||||
row: TableRow,
|
||||
) {
|
||||
let _ = self
|
||||
.data
|
||||
.swap_remove_unchecked(row.as_usize(), last_element_index);
|
||||
self.added_ticks
|
||||
.swap_remove_unchecked(row.as_usize(), last_element_index);
|
||||
self.changed_ticks
|
||||
.swap_remove_unchecked(row.as_usize(), last_element_index);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by
|
||||
.swap_remove_unchecked(row.as_usize(), last_element_index);
|
||||
}
|
||||
|
||||
/// Call [`realloc`](std::alloc::realloc) to expand / shrink the memory allocation for this [`ThinColumn`]
|
||||
///
|
||||
/// # Safety
|
||||
/// - `current_capacity` must be the current capacity of this column (the capacity of `self.data`, `self.added_ticks`, `self.changed_tick`)
|
||||
/// - The caller should make sure their saved `capacity` value is updated to `new_capacity` after this operation.
|
||||
pub(crate) unsafe fn realloc(
|
||||
&mut self,
|
||||
current_capacity: NonZeroUsize,
|
||||
new_capacity: NonZeroUsize,
|
||||
) {
|
||||
self.data.realloc(current_capacity, new_capacity);
|
||||
self.added_ticks.realloc(current_capacity, new_capacity);
|
||||
self.changed_ticks.realloc(current_capacity, new_capacity);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by.realloc(current_capacity, new_capacity);
|
||||
}
|
||||
|
||||
/// Call [`alloc`](std::alloc::alloc) to allocate memory for this [`ThinColumn`]
|
||||
/// The caller should make sure their saved `capacity` value is updated to `new_capacity` after this operation.
|
||||
pub(crate) fn alloc(&mut self, new_capacity: NonZeroUsize) {
|
||||
self.data.alloc(new_capacity);
|
||||
self.added_ticks.alloc(new_capacity);
|
||||
self.changed_ticks.alloc(new_capacity);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by.alloc(new_capacity);
|
||||
}
|
||||
|
||||
/// Writes component data to the column at the given row.
|
||||
/// Assumes the slot is uninitialized, drop is not called.
|
||||
/// To overwrite existing initialized value, use [`Self::replace`] instead.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `row.as_usize()` must be in bounds.
|
||||
/// - `comp_ptr` holds a component that matches the `component_id`
|
||||
#[inline]
|
||||
pub(crate) unsafe fn initialize(
|
||||
&mut self,
|
||||
row: TableRow,
|
||||
data: OwningPtr<'_>,
|
||||
tick: Tick,
|
||||
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||
) {
|
||||
self.data.initialize_unchecked(row.as_usize(), data);
|
||||
*self.added_ticks.get_unchecked_mut(row.as_usize()).get_mut() = tick;
|
||||
*self
|
||||
.changed_ticks
|
||||
.get_unchecked_mut(row.as_usize())
|
||||
.get_mut() = tick;
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
{
|
||||
*self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller;
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes component data to the column at given row. Assumes the slot is initialized, drops the previous value.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `row.as_usize()` must be in bounds.
|
||||
/// - `data` holds a component that matches the `component_id`
|
||||
#[inline]
|
||||
pub(crate) unsafe fn replace(
|
||||
&mut self,
|
||||
row: TableRow,
|
||||
data: OwningPtr<'_>,
|
||||
change_tick: Tick,
|
||||
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||
) {
|
||||
self.data.replace_unchecked(row.as_usize(), data);
|
||||
*self
|
||||
.changed_ticks
|
||||
.get_unchecked_mut(row.as_usize())
|
||||
.get_mut() = change_tick;
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
{
|
||||
*self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the element from `other` at `src_row` and inserts it
|
||||
/// into the current column to initialize the values at `dst_row`.
|
||||
/// Does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `other` must have the same data layout as `self`
|
||||
/// - `src_row` must be in bounds for `other`
|
||||
/// - `dst_row` must be in bounds for `self`
|
||||
/// - `other[src_row]` must be initialized to a valid value.
|
||||
/// - `self[dst_row]` must not be initialized yet.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn initialize_from_unchecked(
|
||||
&mut self,
|
||||
other: &mut ThinColumn,
|
||||
other_last_element_index: usize,
|
||||
src_row: TableRow,
|
||||
dst_row: TableRow,
|
||||
) {
|
||||
debug_assert!(self.data.layout() == other.data.layout());
|
||||
// Init the data
|
||||
let src_val = other
|
||||
.data
|
||||
.swap_remove_unchecked(src_row.as_usize(), other_last_element_index);
|
||||
self.data.initialize_unchecked(dst_row.as_usize(), src_val);
|
||||
// Init added_ticks
|
||||
let added_tick = other
|
||||
.added_ticks
|
||||
.swap_remove_unchecked(src_row.as_usize(), other_last_element_index);
|
||||
self.added_ticks
|
||||
.initialize_unchecked(dst_row.as_usize(), added_tick);
|
||||
// Init changed_ticks
|
||||
let changed_tick = other
|
||||
.changed_ticks
|
||||
.swap_remove_unchecked(src_row.as_usize(), other_last_element_index);
|
||||
self.changed_ticks
|
||||
.initialize_unchecked(dst_row.as_usize(), changed_tick);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
let changed_by = other
|
||||
.changed_by
|
||||
.swap_remove_unchecked(src_row.as_usize(), other_last_element_index);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by
|
||||
.initialize_unchecked(dst_row.as_usize(), changed_by);
|
||||
}
|
||||
|
||||
/// Call [`Tick::check_tick`] on all of the ticks stored in this column.
|
||||
///
|
||||
/// # Safety
|
||||
/// `len` is the actual length of this column
|
||||
#[inline]
|
||||
pub(crate) unsafe fn check_change_ticks(&mut self, len: usize, change_tick: Tick) {
|
||||
for i in 0..len {
|
||||
// SAFETY:
|
||||
// - `i` < `len`
|
||||
// we have a mutable reference to `self`
|
||||
unsafe { self.added_ticks.get_unchecked_mut(i) }
|
||||
.get_mut()
|
||||
.check_tick(change_tick);
|
||||
// SAFETY:
|
||||
// - `i` < `len`
|
||||
// we have a mutable reference to `self`
|
||||
unsafe { self.changed_ticks.get_unchecked_mut(i) }
|
||||
.get_mut()
|
||||
.check_tick(change_tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all the components from this column.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `len` must match the actual length of the column
|
||||
/// - The caller must not use the elements this column's data until [`initializing`](Self::initialize) it again (set `len` to 0).
|
||||
pub(crate) unsafe fn clear(&mut self, len: usize) {
|
||||
self.added_ticks.clear_elements(len);
|
||||
self.changed_ticks.clear_elements(len);
|
||||
self.data.clear(len);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by.clear_elements(len);
|
||||
}
|
||||
|
||||
/// Because this method needs parameters, it can't be the implementation of the `Drop` trait.
|
||||
/// The owner of this [`ThinColumn`] must call this method with the correct information.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `len` is indeed the length of the column
|
||||
/// - `cap` is indeed the capacity of the column
|
||||
/// - the data stored in `self` will never be used again
|
||||
pub(crate) unsafe fn drop(&mut self, cap: usize, len: usize) {
|
||||
self.added_ticks.drop(cap, len);
|
||||
self.changed_ticks.drop(cap, len);
|
||||
self.data.drop(cap, len);
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by.drop(cap, len);
|
||||
}
|
||||
|
||||
/// Drops the last component in this column.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `last_element_index` is indeed the index of the last element
|
||||
/// - the data stored in `last_element_index` will never be used unless properly initialized again.
|
||||
pub(crate) unsafe fn drop_last_component(&mut self, last_element_index: usize) {
|
||||
std::ptr::drop_in_place(self.added_ticks.get_unchecked_raw(last_element_index));
|
||||
std::ptr::drop_in_place(self.changed_ticks.get_unchecked_raw(last_element_index));
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
std::ptr::drop_in_place(self.changed_by.get_unchecked_raw(last_element_index));
|
||||
self.data.drop_last_element(last_element_index);
|
||||
}
|
||||
|
||||
/// Get a slice to the data stored in this [`ThinColumn`].
|
||||
///
|
||||
/// # Safety
|
||||
/// - `T` must match the type of data that's stored in this [`ThinColumn`]
|
||||
/// - `len` must match the actual length of this column (number of elements stored)
|
||||
pub unsafe fn get_data_slice<T>(&self, len: usize) -> &[UnsafeCell<T>] {
|
||||
self.data.get_sub_slice(len)
|
||||
}
|
||||
|
||||
/// Get a slice to the added [`ticks`](Tick) in this [`ThinColumn`].
|
||||
///
|
||||
/// # Safety
|
||||
/// - `len` must match the actual length of this column (number of elements stored)
|
||||
pub unsafe fn get_added_ticks_slice(&self, len: usize) -> &[UnsafeCell<Tick>] {
|
||||
self.added_ticks.as_slice(len)
|
||||
}
|
||||
|
||||
/// Get a slice to the changed [`ticks`](Tick) in this [`ThinColumn`].
|
||||
///
|
||||
/// # Safety
|
||||
/// - `len` must match the actual length of this column (number of elements stored)
|
||||
pub unsafe fn get_changed_ticks_slice(&self, len: usize) -> &[UnsafeCell<Tick>] {
|
||||
self.changed_ticks.as_slice(len)
|
||||
}
|
||||
|
||||
/// Get a slice to the calling locations that last changed each value in this [`ThinColumn`]
|
||||
///
|
||||
/// # Safety
|
||||
/// - `len` must match the actual length of this column (number of elements stored)
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
pub unsafe fn get_changed_by_slice(
|
||||
&self,
|
||||
len: usize,
|
||||
) -> &[UnsafeCell<&'static Location<'static>>] {
|
||||
self.changed_by.as_slice(len)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type-erased contiguous container for data of a homogeneous type.
|
||||
///
|
||||
/// Conceptually, a [`Column`] is very similar to a type-erased `Vec<T>`.
|
||||
/// It also stores the change detection ticks for its components, kept in two separate
|
||||
/// contiguous buffers internally. An element shares its data across these buffers by using the
|
||||
/// same index (i.e. the entity at row 3 has it's data at index 3 and its change detection ticks at index 3).
|
||||
///
|
||||
/// Like many other low-level storage types, [`Column`] has a limited and highly unsafe
|
||||
/// interface. It's highly advised to use higher level types and their safe abstractions
|
||||
/// instead of working directly with [`Column`].
|
||||
#[derive(Debug)]
|
||||
pub struct Column {
|
||||
pub(super) data: BlobVec,
|
||||
pub(super) added_ticks: Vec<UnsafeCell<Tick>>,
|
||||
pub(super) changed_ticks: Vec<UnsafeCell<Tick>>,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
changed_by: Vec<UnsafeCell<&'static Location<'static>>>,
|
||||
}
|
||||
|
||||
impl Column {
|
||||
/// Constructs a new [`Column`], configured with a component's layout and an initial `capacity`.
|
||||
#[inline]
|
||||
pub(crate) fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self {
|
||||
Column {
|
||||
// SAFETY: component_info.drop() is valid for the types that will be inserted.
|
||||
data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) },
|
||||
added_ticks: Vec::with_capacity(capacity),
|
||||
changed_ticks: Vec::with_capacity(capacity),
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
changed_by: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the [`Layout`] for the underlying type.
|
||||
#[inline]
|
||||
pub fn item_layout(&self) -> Layout {
|
||||
self.data.layout()
|
||||
}
|
||||
|
||||
/// Writes component data to the column at given row.
|
||||
/// Assumes the slot is initialized, calls drop.
|
||||
///
|
||||
/// # Safety
|
||||
/// Assumes data has already been allocated for the given row.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn replace(
|
||||
&mut self,
|
||||
row: TableRow,
|
||||
data: OwningPtr<'_>,
|
||||
change_tick: Tick,
|
||||
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||
) {
|
||||
debug_assert!(row.as_usize() < self.len());
|
||||
self.data.replace_unchecked(row.as_usize(), data);
|
||||
*self
|
||||
.changed_ticks
|
||||
.get_unchecked_mut(row.as_usize())
|
||||
.get_mut() = change_tick;
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
{
|
||||
*self.changed_by.get_unchecked_mut(row.as_usize()).get_mut() = caller;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current number of elements stored in the column.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
/// Checks if the column is empty. Returns `true` if there are no elements, `false` otherwise.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
|
||||
/// Removes an element from the [`Column`].
|
||||
///
|
||||
/// - The value will be dropped if it implements [`Drop`].
|
||||
/// - This does not preserve ordering, but is O(1).
|
||||
/// - This does not do any bounds checking.
|
||||
/// - The element is replaced with the last element in the [`Column`].
|
||||
///
|
||||
/// # Safety
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
///
|
||||
#[inline]
|
||||
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) {
|
||||
self.data.swap_remove_and_drop_unchecked(row.as_usize());
|
||||
self.added_ticks.swap_remove(row.as_usize());
|
||||
self.changed_ticks.swap_remove(row.as_usize());
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by.swap_remove(row.as_usize());
|
||||
}
|
||||
|
||||
/// Removes an element from the [`Column`] and returns it and its change detection ticks.
|
||||
/// This does not preserve ordering, but is O(1) and does not do any bounds checking.
|
||||
///
|
||||
/// The element is replaced with the last element in the [`Column`].
|
||||
///
|
||||
/// It's the caller's responsibility to ensure that the removed value is dropped or used.
|
||||
/// Failure to do so may result in resources not being released (i.e. files handles not being
|
||||
/// released, memory leaks, etc.)
|
||||
///
|
||||
/// # Safety
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
#[must_use = "The returned pointer should be used to dropped the removed component"]
|
||||
pub(crate) unsafe fn swap_remove_and_forget_unchecked(
|
||||
&mut self,
|
||||
row: TableRow,
|
||||
) -> (OwningPtr<'_>, ComponentTicks, MaybeLocation) {
|
||||
let data = self.data.swap_remove_and_forget_unchecked(row.as_usize());
|
||||
let added = self.added_ticks.swap_remove(row.as_usize()).into_inner();
|
||||
let changed = self.changed_ticks.swap_remove(row.as_usize()).into_inner();
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
let caller = self.changed_by.swap_remove(row.as_usize()).into_inner();
|
||||
#[cfg(not(feature = "track_change_detection"))]
|
||||
let caller = ();
|
||||
(data, ComponentTicks { added, changed }, caller)
|
||||
}
|
||||
|
||||
/// Pushes a new value onto the end of the [`Column`].
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must point to valid data of this column's component type
|
||||
pub(crate) unsafe fn push(
|
||||
&mut self,
|
||||
ptr: OwningPtr<'_>,
|
||||
ticks: ComponentTicks,
|
||||
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||
) {
|
||||
self.data.push(ptr);
|
||||
self.added_ticks.push(UnsafeCell::new(ticks.added));
|
||||
self.changed_ticks.push(UnsafeCell::new(ticks.changed));
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by.push(UnsafeCell::new(caller));
|
||||
}
|
||||
|
||||
/// Fetches the data pointer to the first element of the [`Column`].
|
||||
///
|
||||
/// The pointer is type erased, so using this function to fetch anything
|
||||
/// other than the first element will require computing the offset using
|
||||
/// [`Column::item_layout`].
|
||||
#[inline]
|
||||
pub fn get_data_ptr(&self) -> Ptr<'_> {
|
||||
self.data.get_ptr()
|
||||
}
|
||||
|
||||
/// Fetches the slice to the [`Column`]'s data cast to a given type.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
///
|
||||
/// # Safety
|
||||
/// The type `T` must be the type of the items in this column.
|
||||
pub unsafe fn get_data_slice<T>(&self) -> &[UnsafeCell<T>] {
|
||||
self.data.get_slice()
|
||||
}
|
||||
|
||||
/// Fetches the slice to the [`Column`]'s "added" change detection ticks.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
#[inline]
|
||||
pub fn get_added_ticks_slice(&self) -> &[UnsafeCell<Tick>] {
|
||||
&self.added_ticks
|
||||
}
|
||||
|
||||
/// Fetches the slice to the [`Column`]'s "changed" change detection ticks.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
#[inline]
|
||||
pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell<Tick>] {
|
||||
&self.changed_ticks
|
||||
}
|
||||
|
||||
/// Fetches a reference to the data and change detection ticks at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
||||
(row.as_usize() < self.data.len())
|
||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||
// accessed through a read-only reference to the column.
|
||||
.then(|| unsafe {
|
||||
(
|
||||
self.data.get_unchecked(row.as_usize()),
|
||||
TickCells {
|
||||
added: self.added_ticks.get_unchecked(row.as_usize()),
|
||||
changed: self.changed_ticks.get_unchecked(row.as_usize()),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetches a read-only reference to the data at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get_data(&self, row: TableRow) -> Option<Ptr<'_>> {
|
||||
(row.as_usize() < self.data.len()).then(|| {
|
||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||
// accessed through a read-only reference to the column.
|
||||
unsafe { self.data.get_unchecked(row.as_usize()) }
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetches a read-only reference to the data at `row`. Unlike [`Column::get`] this does not
|
||||
/// do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `row` must be within the range `[0, self.len())`.
|
||||
/// - no other mutable reference to the data of the same row can exist at the same time
|
||||
#[inline]
|
||||
pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> {
|
||||
debug_assert!(row.as_usize() < self.data.len());
|
||||
self.data.get_unchecked(row.as_usize())
|
||||
}
|
||||
|
||||
/// Fetches a mutable reference to the data at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get_data_mut(&mut self, row: TableRow) -> Option<PtrMut<'_>> {
|
||||
(row.as_usize() < self.data.len()).then(|| {
|
||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||
// accessed through an exclusive reference to the column.
|
||||
unsafe { self.data.get_unchecked_mut(row.as_usize()) }
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetches the "added" change detection tick for the value at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
#[inline]
|
||||
pub fn get_added_tick(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> {
|
||||
self.added_ticks.get(row.as_usize())
|
||||
}
|
||||
|
||||
/// Fetches the "changed" change detection tick for the value at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
#[inline]
|
||||
pub fn get_changed_tick(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> {
|
||||
self.changed_ticks.get(row.as_usize())
|
||||
}
|
||||
|
||||
/// Fetches the change detection ticks for the value at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get_ticks(&self, row: TableRow) -> Option<ComponentTicks> {
|
||||
if row.as_usize() < self.data.len() {
|
||||
// SAFETY: The size of the column has already been checked.
|
||||
Some(unsafe { self.get_ticks_unchecked(row) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the "added" change detection tick for the value at `row`. Unlike [`Column::get_added_tick`]
|
||||
/// this function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
pub unsafe fn get_added_tick_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> {
|
||||
debug_assert!(row.as_usize() < self.added_ticks.len());
|
||||
self.added_ticks.get_unchecked(row.as_usize())
|
||||
}
|
||||
|
||||
/// Fetches the "changed" change detection tick for the value at `row`. Unlike [`Column::get_changed_tick`]
|
||||
/// this function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
pub unsafe fn get_changed_tick_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> {
|
||||
debug_assert!(row.as_usize() < self.changed_ticks.len());
|
||||
self.changed_ticks.get_unchecked(row.as_usize())
|
||||
}
|
||||
|
||||
/// Fetches the change detection ticks for the value at `row`. Unlike [`Column::get_ticks`]
|
||||
/// this function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks {
|
||||
debug_assert!(row.as_usize() < self.added_ticks.len());
|
||||
debug_assert!(row.as_usize() < self.changed_ticks.len());
|
||||
ComponentTicks {
|
||||
added: self.added_ticks.get_unchecked(row.as_usize()).read(),
|
||||
changed: self.changed_ticks.get_unchecked(row.as_usize()).read(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the column, removing all values.
|
||||
///
|
||||
/// Note that this function has no effect on the allocated capacity of the [`Column`]>
|
||||
pub fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
self.added_ticks.clear();
|
||||
self.changed_ticks.clear();
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
self.changed_by.clear();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for component_ticks in &mut self.added_ticks {
|
||||
component_ticks.get_mut().check_tick(change_tick);
|
||||
}
|
||||
for component_ticks in &mut self.changed_ticks {
|
||||
component_ticks.get_mut().check_tick(change_tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches the calling location that last changed the value at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
#[inline]
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
pub fn get_changed_by(&self, row: TableRow) -> Option<&UnsafeCell<&'static Location<'static>>> {
|
||||
self.changed_by.get(row.as_usize())
|
||||
}
|
||||
|
||||
/// Fetches the calling location that last changed the value at `row`.
|
||||
///
|
||||
/// Unlike [`Column::get_changed_by`] this function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
pub unsafe fn get_changed_by_unchecked(
|
||||
&self,
|
||||
row: TableRow,
|
||||
) -> &UnsafeCell<&'static Location<'static>> {
|
||||
debug_assert!(row.as_usize() < self.changed_by.len());
|
||||
self.changed_by.get_unchecked(row.as_usize())
|
||||
}
|
||||
}
|
861
crates/bevy_ecs/src/storage/table/mod.rs
Normal file
861
crates/bevy_ecs/src/storage/table/mod.rs
Normal file
|
@ -0,0 +1,861 @@
|
|||
use crate::{
|
||||
change_detection::MaybeLocation,
|
||||
component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick},
|
||||
entity::Entity,
|
||||
query::DebugCheckedUnwrap,
|
||||
storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet},
|
||||
};
|
||||
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
|
||||
use bevy_utils::HashMap;
|
||||
pub use column::*;
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
use std::panic::Location;
|
||||
use std::{alloc::Layout, num::NonZeroUsize};
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
mod column;
|
||||
|
||||
/// An opaque unique ID for a [`Table`] within a [`World`].
|
||||
///
|
||||
/// Can be used with [`Tables::get`] to fetch the corresponding
|
||||
/// table.
|
||||
///
|
||||
/// Each [`Archetype`] always points to a table via [`Archetype::table_id`].
|
||||
/// Multiple archetypes can point to the same table so long as the components
|
||||
/// stored in the table are identical, but do not share the same sparse set
|
||||
/// components.
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
/// [`Archetype`]: crate::archetype::Archetype
|
||||
/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
// SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation
|
||||
#[repr(transparent)]
|
||||
pub struct TableId(u32);
|
||||
|
||||
impl TableId {
|
||||
pub(crate) const INVALID: TableId = TableId(u32::MAX);
|
||||
|
||||
/// Creates a new [`TableId`].
|
||||
///
|
||||
/// `index` *must* be retrieved from calling [`TableId::as_u32`] on a `TableId` you got
|
||||
/// from a table of a given [`World`] or the created ID may be invalid.
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
#[inline]
|
||||
pub const fn from_u32(index: u32) -> Self {
|
||||
Self(index)
|
||||
}
|
||||
|
||||
/// Creates a new [`TableId`].
|
||||
///
|
||||
/// `index` *must* be retrieved from calling [`TableId::as_usize`] on a `TableId` you got
|
||||
/// from a table of a given [`World`] or the created ID may be invalid.
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the provided value does not fit within a [`u32`].
|
||||
#[inline]
|
||||
pub const fn from_usize(index: usize) -> Self {
|
||||
debug_assert!(index as u32 as usize == index);
|
||||
Self(index as u32)
|
||||
}
|
||||
|
||||
/// Gets the underlying table index from the ID.
|
||||
#[inline]
|
||||
pub const fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Gets the underlying table index from the ID.
|
||||
#[inline]
|
||||
pub const fn as_usize(self) -> usize {
|
||||
// usize is at least u32 in Bevy
|
||||
self.0 as usize
|
||||
}
|
||||
|
||||
/// The [`TableId`] of the [`Table`] without any components.
|
||||
#[inline]
|
||||
pub const fn empty() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A opaque newtype for rows in [`Table`]s. Specifies a single row in a specific table.
|
||||
///
|
||||
/// Values of this type are retrievable from [`Archetype::entity_table_row`] and can be
|
||||
/// used alongside [`Archetype::table_id`] to fetch the exact table and row where an
|
||||
/// [`Entity`]'s
|
||||
///
|
||||
/// Values of this type are only valid so long as entities have not moved around.
|
||||
/// Adding and removing components from an entity, or despawning it will invalidate
|
||||
/// potentially any table row in the table the entity was previously stored in. Users
|
||||
/// should *always* fetch the appropriate row from the entity's [`Archetype`] before
|
||||
/// fetching the entity's components.
|
||||
///
|
||||
/// [`Archetype`]: crate::archetype::Archetype
|
||||
/// [`Archetype::entity_table_row`]: crate::archetype::Archetype::entity_table_row
|
||||
/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
// SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation
|
||||
#[repr(transparent)]
|
||||
pub struct TableRow(u32);
|
||||
|
||||
impl TableRow {
|
||||
pub(crate) const INVALID: TableRow = TableRow(u32::MAX);
|
||||
|
||||
/// Creates a `TableRow`.
|
||||
#[inline]
|
||||
pub const fn from_u32(index: u32) -> Self {
|
||||
Self(index)
|
||||
}
|
||||
|
||||
/// Creates a `TableRow` from a [`usize`] index.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the provided value does not fit within a [`u32`].
|
||||
#[inline]
|
||||
pub const fn from_usize(index: usize) -> Self {
|
||||
debug_assert!(index as u32 as usize == index);
|
||||
Self(index as u32)
|
||||
}
|
||||
|
||||
/// Gets the index of the row as a [`usize`].
|
||||
#[inline]
|
||||
pub const fn as_usize(self) -> usize {
|
||||
// usize is at least u32 in Bevy
|
||||
self.0 as usize
|
||||
}
|
||||
|
||||
/// Gets the index of the row as a [`usize`].
|
||||
#[inline]
|
||||
pub const fn as_u32(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder type for constructing [`Table`]s.
|
||||
///
|
||||
/// - Use [`with_capacity`] to initialize the builder.
|
||||
/// - Repeatedly call [`add_column`] to add columns for components.
|
||||
/// - Finalize with [`build`] to get the constructed [`Table`].
|
||||
///
|
||||
/// [`with_capacity`]: Self::with_capacity
|
||||
/// [`add_column`]: Self::add_column
|
||||
/// [`build`]: Self::build
|
||||
pub(crate) struct TableBuilder {
|
||||
columns: SparseSet<ComponentId, ThinColumn>,
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl TableBuilder {
|
||||
/// Start building a new [`Table`] with a specified `column_capacity` (How many components per column?) and a `capacity` (How many columns?)
|
||||
pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self {
|
||||
Self {
|
||||
columns: SparseSet::with_capacity(column_capacity),
|
||||
capacity,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new column to the [`Table`]. Specify the component which will be stored in the [`column`](ThinColumn) using its [`ComponentId`]
|
||||
#[must_use]
|
||||
pub fn add_column(mut self, component_info: &ComponentInfo) -> Self {
|
||||
self.columns.insert(
|
||||
component_info.id(),
|
||||
ThinColumn::with_capacity(component_info, self.capacity),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the [`Table`], after this operation the caller wouldn't be able to add more columns. The [`Table`] will be ready to use.
|
||||
#[must_use]
|
||||
pub fn build(self) -> Table {
|
||||
Table {
|
||||
columns: self.columns.into_immutable(),
|
||||
entities: Vec::with_capacity(self.capacity),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A column-oriented [structure-of-arrays] based storage for [`Component`]s of entities
|
||||
/// in a [`World`].
|
||||
///
|
||||
/// Conceptually, a `Table` can be thought of as an `HashMap<ComponentId, Column>`, where
|
||||
/// each [`ThinColumn`] is a type-erased `Vec<T: Component>`. Each row corresponds to a single entity
|
||||
/// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same
|
||||
/// entity). Fetching components from a table involves fetching the associated column for a
|
||||
/// component type (via its [`ComponentId`]), then fetching the entity's row within that column.
|
||||
///
|
||||
/// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays
|
||||
/// [`Component`]: crate::component::Component
|
||||
/// [`World`]: crate::world::World
|
||||
pub struct Table {
|
||||
columns: ImmutableSparseSet<ComponentId, ThinColumn>,
|
||||
entities: Vec<Entity>,
|
||||
}
|
||||
|
||||
struct AbortOnPanic;
|
||||
|
||||
impl Drop for AbortOnPanic {
|
||||
fn drop(&mut self) {
|
||||
// Panicking while unwinding will force an abort.
|
||||
panic!("Aborting due to allocator error");
|
||||
}
|
||||
}
|
||||
|
||||
impl Table {
|
||||
/// Fetches a read-only slice of the entities stored within the [`Table`].
|
||||
#[inline]
|
||||
pub fn entities(&self) -> &[Entity] {
|
||||
&self.entities
|
||||
}
|
||||
|
||||
/// Get the capacity of this table, in entities.
|
||||
/// Note that if an allocation is in process, this might not match the actual capacity of the columns, but it should once the allocation ends.
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.entities.capacity()
|
||||
}
|
||||
|
||||
/// Removes the entity at the given row and returns the entity swapped in to replace it (if an
|
||||
/// entity was swapped in)
|
||||
///
|
||||
/// # Safety
|
||||
/// `row` must be in-bounds (`row.as_usize()` < `self.len()`)
|
||||
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) -> Option<Entity> {
|
||||
debug_assert!(row.as_usize() < self.entity_count());
|
||||
let last_element_index = self.entity_count() - 1;
|
||||
if row.as_usize() != last_element_index {
|
||||
// Instead of checking this condition on every `swap_remove` call, we
|
||||
// check it here and use `swap_remove_nonoverlapping`.
|
||||
for col in self.columns.values_mut() {
|
||||
// SAFETY:
|
||||
// - `row` < `len`
|
||||
// - `last_element_index` = `len` - 1
|
||||
// - `row` != `last_element_index`
|
||||
// - the `len` is kept within `self.entities`, it will update accordingly.
|
||||
unsafe {
|
||||
col.swap_remove_and_drop_unchecked_nonoverlapping(last_element_index, row);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// If `row.as_usize()` == `last_element_index` than there's no point in removing the component
|
||||
// at `row`, but we still need to drop it.
|
||||
for col in self.columns.values_mut() {
|
||||
col.drop_last_component(last_element_index);
|
||||
}
|
||||
}
|
||||
let is_last = row.as_usize() == last_element_index;
|
||||
self.entities.swap_remove(row.as_usize());
|
||||
if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[row.as_usize()])
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the `row` column values to `new_table`, for the columns shared between both tables.
|
||||
/// Returns the index of the new row in `new_table` and the entity in this table swapped in
|
||||
/// to replace it (if an entity was swapped in). missing columns will be "forgotten". It is
|
||||
/// the caller's responsibility to drop them. Failure to do so may result in resources not
|
||||
/// being released (i.e. files handles not being released, memory leaks, etc.)
|
||||
///
|
||||
/// # Safety
|
||||
/// - `row` must be in-bounds
|
||||
pub(crate) unsafe fn move_to_and_forget_missing_unchecked(
|
||||
&mut self,
|
||||
row: TableRow,
|
||||
new_table: &mut Table,
|
||||
) -> TableMoveResult {
|
||||
debug_assert!(row.as_usize() < self.entity_count());
|
||||
let last_element_index = self.entity_count() - 1;
|
||||
let is_last = row.as_usize() == last_element_index;
|
||||
let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize()));
|
||||
for (component_id, column) in self.columns.iter_mut() {
|
||||
if let Some(new_column) = new_table.get_column_mut(*component_id) {
|
||||
new_column.initialize_from_unchecked(column, last_element_index, row, new_row);
|
||||
} else {
|
||||
// It's the caller's responsibility to drop these cases.
|
||||
column.swap_remove_and_forget_unchecked(last_element_index, row);
|
||||
}
|
||||
}
|
||||
TableMoveResult {
|
||||
new_row,
|
||||
swapped_entity: if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[row.as_usize()])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the `row` column values to `new_table`, for the columns shared between both tables.
|
||||
/// Returns the index of the new row in `new_table` and the entity in this table swapped in
|
||||
/// to replace it (if an entity was swapped in).
|
||||
///
|
||||
/// # Safety
|
||||
/// row must be in-bounds
|
||||
pub(crate) unsafe fn move_to_and_drop_missing_unchecked(
|
||||
&mut self,
|
||||
row: TableRow,
|
||||
new_table: &mut Table,
|
||||
) -> TableMoveResult {
|
||||
debug_assert!(row.as_usize() < self.entity_count());
|
||||
let last_element_index = self.entity_count() - 1;
|
||||
let is_last = row.as_usize() == last_element_index;
|
||||
let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize()));
|
||||
for (component_id, column) in self.columns.iter_mut() {
|
||||
if let Some(new_column) = new_table.get_column_mut(*component_id) {
|
||||
new_column.initialize_from_unchecked(column, last_element_index, row, new_row);
|
||||
} else {
|
||||
column.swap_remove_and_drop_unchecked(last_element_index, row);
|
||||
}
|
||||
}
|
||||
TableMoveResult {
|
||||
new_row,
|
||||
swapped_entity: if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[row.as_usize()])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the `row` column values to `new_table`, for the columns shared between both tables.
|
||||
/// Returns the index of the new row in `new_table` and the entity in this table swapped in
|
||||
/// to replace it (if an entity was swapped in).
|
||||
///
|
||||
/// # Safety
|
||||
/// - `row` must be in-bounds
|
||||
/// - `new_table` must contain every component this table has
|
||||
pub(crate) unsafe fn move_to_superset_unchecked(
|
||||
&mut self,
|
||||
row: TableRow,
|
||||
new_table: &mut Table,
|
||||
) -> TableMoveResult {
|
||||
debug_assert!(row.as_usize() < self.entity_count());
|
||||
let last_element_index = self.entity_count() - 1;
|
||||
let is_last = row.as_usize() == last_element_index;
|
||||
let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize()));
|
||||
for (component_id, column) in self.columns.iter_mut() {
|
||||
new_table
|
||||
.get_column_mut(*component_id)
|
||||
.debug_checked_unwrap()
|
||||
.initialize_from_unchecked(column, last_element_index, row, new_row);
|
||||
}
|
||||
TableMoveResult {
|
||||
new_row,
|
||||
swapped_entity: if is_last {
|
||||
None
|
||||
} else {
|
||||
Some(self.entities[row.as_usize()])
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the data of the column matching `component_id` as a slice.
|
||||
///
|
||||
/// # Safety
|
||||
/// `row.as_usize()` < `self.len()`
|
||||
/// - `T` must match the `component_id`
|
||||
pub unsafe fn get_data_slice_for<T>(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&[UnsafeCell<T>]> {
|
||||
self.get_column(component_id)
|
||||
.map(|col| col.get_data_slice(self.entity_count()))
|
||||
}
|
||||
|
||||
/// Get the added ticks of the column matching `component_id` as a slice.
|
||||
pub fn get_added_ticks_slice_for(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&[UnsafeCell<Tick>]> {
|
||||
self.get_column(component_id)
|
||||
// SAFETY: `self.len()` is guaranteed to be the len of the ticks array
|
||||
.map(|col| unsafe { col.get_added_ticks_slice(self.entity_count()) })
|
||||
}
|
||||
|
||||
/// Get the changed ticks of the column matching `component_id` as a slice.
|
||||
pub fn get_changed_ticks_slice_for(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&[UnsafeCell<Tick>]> {
|
||||
self.get_column(component_id)
|
||||
// SAFETY: `self.len()` is guaranteed to be the len of the ticks array
|
||||
.map(|col| unsafe { col.get_changed_ticks_slice(self.entity_count()) })
|
||||
}
|
||||
|
||||
/// Fetches the calling locations that last changed the each component
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
pub fn get_changed_by_slice_for(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&[UnsafeCell<&'static Location<'static>>]> {
|
||||
self.get_column(component_id)
|
||||
// SAFETY: `self.len()` is guaranteed to be the len of the locations array
|
||||
.map(|col| unsafe { col.get_changed_by_slice(self.entity_count()) })
|
||||
}
|
||||
|
||||
/// Get the specific [`change tick`](Tick) of the component matching `component_id` in `row`.
|
||||
pub fn get_changed_tick(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
row: TableRow,
|
||||
) -> Option<&UnsafeCell<Tick>> {
|
||||
(row.as_usize() < self.entity_count()).then_some(
|
||||
// SAFETY: `row.as_usize()` < `len`
|
||||
unsafe {
|
||||
self.get_column(component_id)?
|
||||
.changed_ticks
|
||||
.get_unchecked(row.as_usize())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the specific [`added tick`](Tick) of the component matching `component_id` in `row`.
|
||||
pub fn get_added_tick(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
row: TableRow,
|
||||
) -> Option<&UnsafeCell<Tick>> {
|
||||
(row.as_usize() < self.entity_count()).then_some(
|
||||
// SAFETY: `row.as_usize()` < `len`
|
||||
unsafe {
|
||||
self.get_column(component_id)?
|
||||
.added_ticks
|
||||
.get_unchecked(row.as_usize())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the specific calling location that changed the component matching `component_id` in `row`
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
pub fn get_changed_by(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
row: TableRow,
|
||||
) -> Option<&UnsafeCell<&'static Location<'static>>> {
|
||||
(row.as_usize() < self.entity_count()).then_some(
|
||||
// SAFETY: `row.as_usize()` < `len`
|
||||
unsafe {
|
||||
self.get_column(component_id)?
|
||||
.changed_by
|
||||
.get_unchecked(row.as_usize())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the [`ComponentTicks`] of the component matching `component_id` in `row`.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `row.as_usize()` < `self.len()`
|
||||
pub unsafe fn get_ticks_unchecked(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
row: TableRow,
|
||||
) -> Option<ComponentTicks> {
|
||||
self.get_column(component_id).map(|col| ComponentTicks {
|
||||
added: col.added_ticks.get_unchecked(row.as_usize()).read(),
|
||||
changed: col.changed_ticks.get_unchecked(row.as_usize()).read(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Fetches a read-only reference to the [`ThinColumn`] for a given [`Component`] within the table.
|
||||
///
|
||||
/// Returns `None` if the corresponding component does not belong to the table.
|
||||
///
|
||||
/// [`Component`]: crate::component::Component
|
||||
#[inline]
|
||||
pub fn get_column(&self, component_id: ComponentId) -> Option<&ThinColumn> {
|
||||
self.columns.get(component_id)
|
||||
}
|
||||
|
||||
/// Fetches a mutable reference to the [`ThinColumn`] for a given [`Component`] within the
|
||||
/// table.
|
||||
///
|
||||
/// Returns `None` if the corresponding component does not belong to the table.
|
||||
///
|
||||
/// [`Component`]: crate::component::Component
|
||||
#[inline]
|
||||
pub(crate) fn get_column_mut(&mut self, component_id: ComponentId) -> Option<&mut ThinColumn> {
|
||||
self.columns.get_mut(component_id)
|
||||
}
|
||||
|
||||
/// Checks if the table contains a [`ThinColumn`] for a given [`Component`].
|
||||
///
|
||||
/// Returns `true` if the column is present, `false` otherwise.
|
||||
///
|
||||
/// [`Component`]: crate::component::Component
|
||||
#[inline]
|
||||
pub fn has_column(&self, component_id: ComponentId) -> bool {
|
||||
self.columns.contains(component_id)
|
||||
}
|
||||
|
||||
/// Reserves `additional` elements worth of capacity within the table.
|
||||
pub(crate) fn reserve(&mut self, additional: usize) {
|
||||
if self.capacity() - self.entity_count() < additional {
|
||||
let column_cap = self.capacity();
|
||||
self.entities.reserve(additional);
|
||||
|
||||
// use entities vector capacity as driving capacity for all related allocations
|
||||
let new_capacity = self.entities.capacity();
|
||||
|
||||
if column_cap == 0 {
|
||||
// SAFETY: the current capacity is 0
|
||||
unsafe { self.alloc_columns(NonZeroUsize::new_unchecked(new_capacity)) };
|
||||
} else {
|
||||
// SAFETY:
|
||||
// - `column_cap` is indeed the columns' capacity
|
||||
unsafe {
|
||||
self.realloc_columns(
|
||||
NonZeroUsize::new_unchecked(column_cap),
|
||||
NonZeroUsize::new_unchecked(new_capacity),
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate memory for the columns in the [`Table`]
|
||||
///
|
||||
/// The current capacity of the columns should be 0, if it's not 0, then the previous data will be overwritten and leaked.
|
||||
fn alloc_columns(&mut self, new_capacity: NonZeroUsize) {
|
||||
// If any of these allocations trigger an unwind, the wrong capacity will be used while dropping this table - UB.
|
||||
// To avoid this, we use `AbortOnPanic`. If the allocation triggered a panic, the `AbortOnPanic`'s Drop impl will be
|
||||
// called, and abort the program.
|
||||
let _guard = AbortOnPanic;
|
||||
for col in self.columns.values_mut() {
|
||||
col.alloc(new_capacity);
|
||||
}
|
||||
core::mem::forget(_guard); // The allocation was successful, so we don't drop the guard.
|
||||
}
|
||||
|
||||
/// Reallocate memory for the columns in the [`Table`]
|
||||
///
|
||||
/// # Safety
|
||||
/// - `current_column_capacity` is indeed the capacity of the columns
|
||||
unsafe fn realloc_columns(
|
||||
&mut self,
|
||||
current_column_capacity: NonZeroUsize,
|
||||
new_capacity: NonZeroUsize,
|
||||
) {
|
||||
// If any of these allocations trigger an unwind, the wrong capacity will be used while dropping this table - UB.
|
||||
// To avoid this, we use `AbortOnPanic`. If the allocation triggered a panic, the `AbortOnPanic`'s Drop impl will be
|
||||
// called, and abort the program.
|
||||
let _guard = AbortOnPanic;
|
||||
|
||||
// SAFETY:
|
||||
// - There's no overflow
|
||||
// - `current_capacity` is indeed the capacity - safety requirement
|
||||
// - current capacity > 0
|
||||
for col in self.columns.values_mut() {
|
||||
col.realloc(current_column_capacity, new_capacity);
|
||||
}
|
||||
core::mem::forget(_guard); // The allocation was successful, so we don't drop the guard.
|
||||
}
|
||||
|
||||
/// Allocates space for a new entity
|
||||
///
|
||||
/// # Safety
|
||||
/// the allocated row must be written to immediately with valid values in each column
|
||||
pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> TableRow {
|
||||
self.reserve(1);
|
||||
let len = self.entity_count();
|
||||
self.entities.push(entity);
|
||||
for col in self.columns.values_mut() {
|
||||
col.added_ticks
|
||||
.initialize_unchecked(len, UnsafeCell::new(Tick::new(0)));
|
||||
col.changed_ticks
|
||||
.initialize_unchecked(len, UnsafeCell::new(Tick::new(0)));
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
col.changed_by
|
||||
.initialize_unchecked(len, UnsafeCell::new(Location::caller()));
|
||||
}
|
||||
TableRow::from_usize(len)
|
||||
}
|
||||
|
||||
/// Gets the number of entities currently being stored in the table.
|
||||
#[inline]
|
||||
pub fn entity_count(&self) -> usize {
|
||||
self.entities.len()
|
||||
}
|
||||
|
||||
/// Get the drop function for some component that is stored in this table.
|
||||
#[inline]
|
||||
pub fn get_drop_for(&self, component_id: ComponentId) -> Option<unsafe fn(OwningPtr<'_>)> {
|
||||
self.get_column(component_id)?.data.drop
|
||||
}
|
||||
|
||||
/// Gets the number of components being stored in the table.
|
||||
#[inline]
|
||||
pub fn component_count(&self) -> usize {
|
||||
self.columns.len()
|
||||
}
|
||||
|
||||
/// Gets the maximum number of entities the table can currently store
|
||||
/// without reallocating the underlying memory.
|
||||
#[inline]
|
||||
pub fn entity_capacity(&self) -> usize {
|
||||
self.entities.capacity()
|
||||
}
|
||||
|
||||
/// Checks if the [`Table`] is empty or not.
|
||||
///
|
||||
/// Returns `true` if the table contains no entities, `false` otherwise.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entities.is_empty()
|
||||
}
|
||||
|
||||
/// Call [`Tick::check_tick`] on all of the ticks in the [`Table`]
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
let len = self.entity_count();
|
||||
for col in self.columns.values_mut() {
|
||||
// SAFETY: `len` is the actual length of the column
|
||||
unsafe { col.check_change_ticks(len, change_tick) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over the [`ThinColumn`]s of the [`Table`].
|
||||
pub fn iter_columns(&self) -> impl Iterator<Item = &ThinColumn> {
|
||||
self.columns.values()
|
||||
}
|
||||
|
||||
/// Clears all of the stored components in the [`Table`].
|
||||
pub(crate) fn clear(&mut self) {
|
||||
let len = self.entity_count();
|
||||
// We must clear the entities first, because in the drop function causes a panic, it will result in a double free of the columns.
|
||||
self.entities.clear();
|
||||
for column in self.columns.values_mut() {
|
||||
// SAFETY: we defer `self.entities.clear()` until after clearing the columns,
|
||||
// so `self.len()` should match the columns' len
|
||||
unsafe { column.clear(len) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves component data out of the [`Table`].
|
||||
///
|
||||
/// This function leaves the underlying memory unchanged, but the component behind
|
||||
/// returned pointer is semantically owned by the caller and will not be dropped in its original location.
|
||||
/// Caller is responsible to drop component data behind returned pointer.
|
||||
///
|
||||
/// # Safety
|
||||
/// - This table must hold the component matching `component_id`
|
||||
/// - `row` must be in bounds
|
||||
/// - The row's inconsistent state that happens after taking the component must be resolved—either initialize a new component or remove the row.
|
||||
pub(crate) unsafe fn take_component(
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
row: TableRow,
|
||||
) -> OwningPtr<'_> {
|
||||
self.get_column_mut(component_id)
|
||||
.debug_checked_unwrap()
|
||||
.data
|
||||
.get_unchecked_mut(row.as_usize())
|
||||
.promote()
|
||||
}
|
||||
|
||||
/// Get the component at a given `row`, if the [`Table`] stores components with the given `component_id`
|
||||
///
|
||||
/// # Safety
|
||||
/// `row.as_usize()` < `self.len()`
|
||||
pub unsafe fn get_component(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
row: TableRow,
|
||||
) -> Option<Ptr<'_>> {
|
||||
self.get_column(component_id)
|
||||
.map(|col| col.data.get_unchecked(row.as_usize()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of [`Table`] storages, indexed by [`TableId`]
|
||||
///
|
||||
/// Can be accessed via [`Storages`](crate::storage::Storages)
|
||||
pub struct Tables {
|
||||
tables: Vec<Table>,
|
||||
table_ids: HashMap<Box<[ComponentId]>, TableId>,
|
||||
}
|
||||
|
||||
impl Default for Tables {
|
||||
fn default() -> Self {
|
||||
let empty_table = TableBuilder::with_capacity(0, 0).build();
|
||||
Tables {
|
||||
tables: vec![empty_table],
|
||||
table_ids: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TableMoveResult {
|
||||
pub swapped_entity: Option<Entity>,
|
||||
pub new_row: TableRow,
|
||||
}
|
||||
|
||||
impl Tables {
|
||||
/// Returns the number of [`Table`]s this collection contains
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.tables.len()
|
||||
}
|
||||
|
||||
/// Returns true if this collection contains no [`Table`]s
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tables.is_empty()
|
||||
}
|
||||
|
||||
/// Fetches a [`Table`] by its [`TableId`].
|
||||
///
|
||||
/// Returns `None` if `id` is invalid.
|
||||
#[inline]
|
||||
pub fn get(&self, id: TableId) -> Option<&Table> {
|
||||
self.tables.get(id.as_usize())
|
||||
}
|
||||
|
||||
/// Fetches mutable references to two different [`Table`]s.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `a` and `b` are equal.
|
||||
#[inline]
|
||||
pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) {
|
||||
if a.as_usize() > b.as_usize() {
|
||||
let (b_slice, a_slice) = self.tables.split_at_mut(a.as_usize());
|
||||
(&mut a_slice[0], &mut b_slice[b.as_usize()])
|
||||
} else {
|
||||
let (a_slice, b_slice) = self.tables.split_at_mut(b.as_usize());
|
||||
(&mut a_slice[a.as_usize()], &mut b_slice[0])
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to fetch a table based on the provided components,
|
||||
/// creating and returning a new [`Table`] if one did not already exist.
|
||||
///
|
||||
/// # Safety
|
||||
/// `component_ids` must contain components that exist in `components`
|
||||
pub(crate) unsafe fn get_id_or_insert(
|
||||
&mut self,
|
||||
component_ids: &[ComponentId],
|
||||
components: &Components,
|
||||
) -> TableId {
|
||||
let tables = &mut self.tables;
|
||||
let (_key, value) = self
|
||||
.table_ids
|
||||
.raw_entry_mut()
|
||||
.from_key(component_ids)
|
||||
.or_insert_with(|| {
|
||||
let mut table = TableBuilder::with_capacity(0, component_ids.len());
|
||||
for component_id in component_ids {
|
||||
table = table.add_column(components.get_info_unchecked(*component_id));
|
||||
}
|
||||
tables.push(table.build());
|
||||
(component_ids.into(), TableId::from_usize(tables.len() - 1))
|
||||
});
|
||||
|
||||
*value
|
||||
}
|
||||
|
||||
/// Iterates through all of the tables stored within in [`TableId`] order.
|
||||
pub fn iter(&self) -> std::slice::Iter<'_, Table> {
|
||||
self.tables.iter()
|
||||
}
|
||||
|
||||
/// Clears all data from all [`Table`]s stored within.
|
||||
pub(crate) fn clear(&mut self) {
|
||||
for table in &mut self.tables {
|
||||
table.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for table in &mut self.tables {
|
||||
table.check_change_ticks(change_tick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<TableId> for Tables {
|
||||
type Output = Table;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: TableId) -> &Self::Output {
|
||||
&self.tables[index.as_usize()]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<TableId> for Tables {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, index: TableId) -> &mut Self::Output {
|
||||
&mut self.tables[index.as_usize()]
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Table {
|
||||
fn drop(&mut self) {
|
||||
let len = self.entity_count();
|
||||
let cap = self.capacity();
|
||||
self.entities.clear();
|
||||
for col in self.columns.values_mut() {
|
||||
// SAFETY: `cap` and `len` are correct
|
||||
unsafe {
|
||||
col.drop(cap, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate as bevy_ecs;
|
||||
use crate::component::Component;
|
||||
use crate::ptr::OwningPtr;
|
||||
use crate::storage::Storages;
|
||||
use crate::{
|
||||
component::{Components, Tick},
|
||||
entity::Entity,
|
||||
storage::{TableBuilder, TableRow},
|
||||
};
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
use std::panic::Location;
|
||||
|
||||
#[derive(Component)]
|
||||
struct W<T>(T);
|
||||
|
||||
#[test]
|
||||
fn table() {
|
||||
let mut components = Components::default();
|
||||
let mut storages = Storages::default();
|
||||
let component_id = components.init_component::<W<TableRow>>(&mut storages);
|
||||
let columns = &[component_id];
|
||||
let mut table = TableBuilder::with_capacity(0, columns.len())
|
||||
.add_column(components.get_info(component_id).unwrap())
|
||||
.build();
|
||||
let entities = (0..200).map(Entity::from_raw).collect::<Vec<_>>();
|
||||
for entity in &entities {
|
||||
// SAFETY: we allocate and immediately set data afterwards
|
||||
unsafe {
|
||||
let row = table.allocate(*entity);
|
||||
let value: W<TableRow> = W(row);
|
||||
OwningPtr::make(value, |value_ptr| {
|
||||
table.get_column_mut(component_id).unwrap().initialize(
|
||||
row,
|
||||
value_ptr,
|
||||
Tick::new(0),
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
Location::caller(),
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
assert_eq!(table.entity_capacity(), 256);
|
||||
assert_eq!(table.entity_count(), 200);
|
||||
}
|
||||
}
|
314
crates/bevy_ecs/src/storage/thin_array_ptr.rs
Normal file
314
crates/bevy_ecs/src/storage/thin_array_ptr.rs
Normal file
|
@ -0,0 +1,314 @@
|
|||
use crate::query::DebugCheckedUnwrap;
|
||||
use std::alloc::{alloc, handle_alloc_error, realloc, Layout};
|
||||
use std::mem::{needs_drop, size_of};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ptr::{self, NonNull};
|
||||
|
||||
/// Similar to [`Vec<T>`], but with the capacity and length cut out for performance reasons.
|
||||
///
|
||||
/// This type can be treated as a `ManuallyDrop<Box<[T]>>` without a built in length. To avoid
|
||||
/// memory leaks, [`drop`](Self::drop) must be called when no longer in use.
|
||||
pub struct ThinArrayPtr<T> {
|
||||
data: NonNull<T>,
|
||||
#[cfg(debug_assertions)]
|
||||
capacity: usize,
|
||||
}
|
||||
|
||||
impl<T> ThinArrayPtr<T> {
|
||||
fn empty() -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
Self {
|
||||
data: NonNull::dangling(),
|
||||
capacity: 0,
|
||||
}
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
Self {
|
||||
data: NonNull::dangling(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_capacity(&mut self, _capacity: usize) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
self.capacity = _capacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`ThinArrayPtr`] with a given capacity. If the `capacity` is 0, this will no allocate any memory.
|
||||
#[inline]
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
let mut arr = Self::empty();
|
||||
if capacity > 0 {
|
||||
// SAFETY:
|
||||
// - The `current_capacity` is 0 because it was just created
|
||||
unsafe { arr.alloc(NonZeroUsize::new_unchecked(capacity)) };
|
||||
}
|
||||
arr
|
||||
}
|
||||
|
||||
/// Allocate memory for the array, this should only be used if not previous allocation has been made (capacity = 0)
|
||||
/// The caller should update their saved `capacity` value to reflect the fact that it was changed
|
||||
///
|
||||
/// # Panics
|
||||
/// - Panics if the new capacity overflows `usize`
|
||||
pub fn alloc(&mut self, capacity: NonZeroUsize) {
|
||||
self.set_capacity(capacity.get());
|
||||
if size_of::<T>() != 0 {
|
||||
let new_layout = Layout::array::<T>(capacity.get())
|
||||
.expect("layout should be valid (arithmetic overflow)");
|
||||
// SAFETY:
|
||||
// - layout has non-zero size, `capacity` > 0, `size` > 0 (`size_of::<T>() != 0`)
|
||||
self.data = NonNull::new(unsafe { alloc(new_layout) })
|
||||
.unwrap_or_else(|| handle_alloc_error(new_layout))
|
||||
.cast();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reallocate memory for the array, this should only be used if a previous allocation for this array has been made (capacity > 0).
|
||||
///
|
||||
/// # Panics
|
||||
/// - Panics if the new capacity overflows `usize`
|
||||
///
|
||||
/// # Safety
|
||||
/// - The current capacity is indeed greater than 0
|
||||
/// - The caller should update their saved `capacity` value to reflect the fact that it was changed
|
||||
pub unsafe fn realloc(&mut self, current_capacity: NonZeroUsize, new_capacity: NonZeroUsize) {
|
||||
#[cfg(debug_assertions)]
|
||||
assert_eq!(self.capacity, current_capacity.into());
|
||||
self.set_capacity(new_capacity.get());
|
||||
if size_of::<T>() != 0 {
|
||||
let new_layout =
|
||||
Layout::array::<T>(new_capacity.get()).expect("overflow while allocating memory");
|
||||
// SAFETY:
|
||||
// - ptr was be allocated via this allocator
|
||||
// - the layout of the array is the same as `Layout::array::<T>(current_capacity)`
|
||||
// - the size of `T` is non 0, and `new_capacity` > 0
|
||||
// - "new_size, when rounded up to the nearest multiple of layout.align(), must not overflow (i.e., the rounded value must be less than usize::MAX)",
|
||||
// since the item size is always a multiple of its align, the rounding cannot happen
|
||||
// here and the overflow is handled in `Layout::array`
|
||||
self.data = NonNull::new(unsafe {
|
||||
realloc(
|
||||
self.data.cast().as_ptr(),
|
||||
// We can use `unwrap_unchecked` because this is the Layout of the current allocation, it must be valid
|
||||
Layout::array::<T>(current_capacity.get()).debug_checked_unwrap(),
|
||||
new_layout.size(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| handle_alloc_error(new_layout))
|
||||
.cast();
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the value at `index` to `value`. This function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// `index` must be in bounds i.e. within the `capacity`.
|
||||
/// if `index` = `len` the caller should update their saved `len` value to reflect the fact that it was changed
|
||||
#[inline]
|
||||
pub unsafe fn initialize_unchecked(&mut self, index: usize, value: T) {
|
||||
// SAFETY: `index` is in bounds
|
||||
let ptr = unsafe { self.get_unchecked_raw(index) };
|
||||
// SAFETY: `index` is in bounds, therefore the pointer to that location in the array is valid, and aligned.
|
||||
unsafe { ptr::write(ptr, value) };
|
||||
}
|
||||
|
||||
/// Get a raw pointer to the element at `index`. This method doesn't do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index` must be safe to access.
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked_raw(&mut self, index: usize) -> *mut T {
|
||||
// SAFETY:
|
||||
// - `self.data` and the resulting pointer are in the same allocated object
|
||||
// - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either
|
||||
unsafe { self.data.as_ptr().add(index) }
|
||||
}
|
||||
|
||||
/// Get a reference to the element at `index`. This method doesn't do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index` must be safe to read.
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked(&self, index: usize) -> &'_ T {
|
||||
// SAFETY:
|
||||
// - `self.data` and the resulting pointer are in the same allocated object
|
||||
// - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either
|
||||
let ptr = unsafe { self.data.as_ptr().add(index) };
|
||||
// SAFETY:
|
||||
// - The pointer is properly aligned
|
||||
// - It is derefrancable (all in the same allocation)
|
||||
// - `index` < `len` and the element is safe to write to, so its valid
|
||||
// - We have a reference to self, so no other mutable accesses to the element can occur
|
||||
unsafe {
|
||||
ptr.as_ref()
|
||||
// SAFETY: We can use `unwarp_unchecked` because the pointer isn't null)
|
||||
.debug_checked_unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the element at `index`. This method doesn't do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index` must be safe to write to.
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> &'_ mut T {
|
||||
// SAFETY:
|
||||
// - `self.data` and the resulting pointer are in the same allocated object
|
||||
// - the memory address of the last element doesn't overflow `isize`, so if `index` is in bounds, it won't overflow either
|
||||
let ptr = unsafe { self.data.as_ptr().add(index) };
|
||||
// SAFETY:
|
||||
// - The pointer is properly aligned
|
||||
// - It is derefrancable (all in the same allocation)
|
||||
// - `index` < `len` and the element is safe to write to, so its valid
|
||||
// - We have a mutable reference to `self`
|
||||
unsafe {
|
||||
ptr.as_mut()
|
||||
// SAFETY: We can use `unwarp_unchecked` because the pointer isn't null)
|
||||
.unwrap_unchecked()
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and return the removed value.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` != `index_to_keep`
|
||||
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
|
||||
/// 1) initialize a different value in `index_to_keep`
|
||||
/// 2) update the saved length of the array if `index_to_keep` was the last element.
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_unchecked_nonoverlapping(
|
||||
&mut self,
|
||||
index_to_remove: usize,
|
||||
index_to_keep: usize,
|
||||
) -> T {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
debug_assert!(self.capacity > index_to_keep);
|
||||
debug_assert!(self.capacity > index_to_remove);
|
||||
debug_assert_ne!(index_to_keep, index_to_remove);
|
||||
}
|
||||
let base_ptr = self.data.as_ptr();
|
||||
let value = ptr::read(base_ptr.add(index_to_remove));
|
||||
ptr::copy_nonoverlapping(
|
||||
base_ptr.add(index_to_keep),
|
||||
base_ptr.add(index_to_remove),
|
||||
1,
|
||||
);
|
||||
value
|
||||
}
|
||||
|
||||
/// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and return the removed value.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` != `index_to_keep`
|
||||
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
|
||||
/// 1) initialize a different value in `index_to_keep`
|
||||
/// 2) update the saved length of the array if `index_to_keep` was the last element.
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_unchecked(
|
||||
&mut self,
|
||||
index_to_remove: usize,
|
||||
index_to_keep: usize,
|
||||
) -> T {
|
||||
if index_to_remove != index_to_keep {
|
||||
return self.swap_remove_unchecked_nonoverlapping(index_to_remove, index_to_keep);
|
||||
}
|
||||
ptr::read(self.data.as_ptr().add(index_to_remove))
|
||||
}
|
||||
|
||||
/// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and drop the removed value.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
|
||||
/// - `index_to_remove` != `index_to_keep`
|
||||
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
|
||||
/// 1) initialize a different value in `index_to_keep`
|
||||
/// 2) update the saved length of the array if `index_to_keep` was the last element.
|
||||
#[inline]
|
||||
pub unsafe fn swap_remove_and_drop_unchecked(
|
||||
&mut self,
|
||||
index_to_remove: usize,
|
||||
index_to_keep: usize,
|
||||
) {
|
||||
let val = &mut self.swap_remove_unchecked(index_to_remove, index_to_keep);
|
||||
ptr::drop_in_place(ptr::from_mut(val));
|
||||
}
|
||||
|
||||
/// Get a raw pointer to the last element of the array, return `None` if the length is 0
|
||||
///
|
||||
/// # Safety
|
||||
/// - ensure that `current_len` is indeed the len of the array
|
||||
#[inline]
|
||||
unsafe fn last_element(&mut self, current_len: usize) -> Option<*mut T> {
|
||||
(current_len != 0).then_some(self.data.as_ptr().add(current_len - 1))
|
||||
}
|
||||
|
||||
/// Clears the array, removing (and dropping) Note that this method has no effect on the allocated capacity of the vector.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `current_len` is indeed the length of the array
|
||||
/// - The caller should update their saved length value
|
||||
pub unsafe fn clear_elements(&mut self, mut current_len: usize) {
|
||||
if needs_drop::<T>() {
|
||||
while let Some(to_drop) = self.last_element(current_len) {
|
||||
ptr::drop_in_place(to_drop);
|
||||
current_len -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Drop the entire array and all its elements.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `current_len` is indeed the length of the array
|
||||
/// - `current_capacity` is indeed the capacity of the array
|
||||
/// - The caller must not use this `ThinArrayPtr` in any way after calling this function
|
||||
pub unsafe fn drop(&mut self, current_capacity: usize, current_len: usize) {
|
||||
#[cfg(debug_assertions)]
|
||||
assert_eq!(self.capacity, current_capacity);
|
||||
if current_capacity != 0 {
|
||||
self.clear_elements(current_len);
|
||||
let layout = Layout::array::<T>(current_capacity).expect("layout should be valid");
|
||||
std::alloc::dealloc(self.data.as_ptr().cast(), layout);
|
||||
}
|
||||
self.set_capacity(0);
|
||||
}
|
||||
|
||||
/// Get the [`ThinArrayPtr`] as a slice with a given length.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `slice_len` must match the actual length of the array
|
||||
#[inline]
|
||||
pub unsafe fn as_slice(&self, slice_len: usize) -> &[T] {
|
||||
// SAFETY:
|
||||
// - the data is valid - allocated with the same allocater
|
||||
// - non-null and well-aligned
|
||||
// - we have a shared reference to self - the data will not be mutated during 'a
|
||||
unsafe { std::slice::from_raw_parts(self.data.as_ptr(), slice_len) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Box<[T]>> for ThinArrayPtr<T> {
|
||||
fn from(value: Box<[T]>) -> Self {
|
||||
let _len = value.len();
|
||||
let slice_ptr = Box::<[T]>::into_raw(value);
|
||||
// SAFETY: We just got the pointer from a reference
|
||||
let first_element_ptr = unsafe { (*slice_ptr).as_mut_ptr() };
|
||||
Self {
|
||||
// SAFETY: The pointer can't be null, it came from a reference
|
||||
data: unsafe { NonNull::new_unchecked(first_element_ptr) },
|
||||
#[cfg(debug_assertions)]
|
||||
capacity: _len,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
mod parallel_scope;
|
||||
|
||||
use core::panic::Location;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource};
|
||||
use crate::{
|
||||
self as bevy_ecs,
|
||||
bundle::{Bundle, InsertMode},
|
||||
component::{ComponentId, ComponentInfo},
|
||||
change_detection::Mut,
|
||||
component::{Component, ComponentId, ComponentInfo},
|
||||
entity::{Entities, Entity},
|
||||
event::{Event, SendEvent},
|
||||
observer::{Observer, TriggerEvent, TriggerTargets},
|
||||
|
@ -54,7 +56,7 @@ pub use parallel_scope::*;
|
|||
///
|
||||
/// Each built-in command is implemented as a separate method, e.g. [`Commands::spawn`].
|
||||
/// In addition to the pre-defined command methods, you can add commands with any arbitrary
|
||||
/// behavior using [`Commands::add`], which accepts any type implementing [`Command`].
|
||||
/// behavior using [`Commands::queue`], which accepts any type implementing [`Command`].
|
||||
///
|
||||
/// Since closures and other functions implement this trait automatically, this allows one-shot,
|
||||
/// anonymous custom commands.
|
||||
|
@ -63,7 +65,7 @@ pub use parallel_scope::*;
|
|||
/// # use bevy_ecs::prelude::*;
|
||||
/// # fn foo(mut commands: Commands) {
|
||||
/// // NOTE: type inference fails here, so annotations are required on the closure.
|
||||
/// commands.add(|w: &mut World| {
|
||||
/// commands.queue(|w: &mut World| {
|
||||
/// // Mutate the world however you want...
|
||||
/// # todo!();
|
||||
/// });
|
||||
|
@ -303,7 +305,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// apps, and only when they have a scheme worked out to share an ID space (which doesn't happen
|
||||
/// by default).
|
||||
pub fn get_or_spawn(&mut self, entity: Entity) -> EntityCommands {
|
||||
self.add(move |world: &mut World| {
|
||||
self.queue(move |world: &mut World| {
|
||||
world.get_or_spawn(entity);
|
||||
});
|
||||
EntityCommands {
|
||||
|
@ -503,11 +505,43 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
I: IntoIterator + Send + Sync + 'static,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
self.push(spawn_batch(bundles_iter));
|
||||
self.queue(spawn_batch(bundles_iter));
|
||||
}
|
||||
|
||||
/// Push a [`Command`] onto the queue.
|
||||
pub fn push<C: Command>(&mut self, command: C) {
|
||||
/// Pushes a generic [`Command`] to the command queue.
|
||||
///
|
||||
/// `command` can be a built-in command, custom struct that implements [`Command`] or a closure
|
||||
/// that takes [`&mut World`](World) as an argument.
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{world::Command, prelude::*};
|
||||
/// #[derive(Resource, Default)]
|
||||
/// struct Counter(u64);
|
||||
///
|
||||
/// struct AddToCounter(u64);
|
||||
///
|
||||
/// impl Command for AddToCounter {
|
||||
/// fn apply(self, world: &mut World) {
|
||||
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
|
||||
/// counter.0 += self.0;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn add_three_to_counter_system(mut commands: Commands) {
|
||||
/// commands.queue(AddToCounter(3));
|
||||
/// }
|
||||
/// fn add_twenty_five_to_counter_system(mut commands: Commands) {
|
||||
/// commands.queue(|world: &mut World| {
|
||||
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
|
||||
/// counter.0 += 25;
|
||||
/// });
|
||||
/// }
|
||||
|
||||
/// # bevy_ecs::system::assert_is_system(add_three_to_counter_system);
|
||||
/// # bevy_ecs::system::assert_is_system(add_twenty_five_to_counter_system);
|
||||
/// ```
|
||||
pub fn queue<C: Command>(&mut self, command: C) {
|
||||
match &mut self.queue {
|
||||
InternalQueue::CommandQueue(queue) => {
|
||||
queue.push(command);
|
||||
|
@ -549,7 +583,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
I: IntoIterator<Item = (Entity, B)> + Send + Sync + 'static,
|
||||
B: Bundle,
|
||||
{
|
||||
self.push(insert_or_spawn_batch(bundles_iter));
|
||||
self.queue(insert_or_spawn_batch(bundles_iter));
|
||||
}
|
||||
|
||||
/// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with an inferred value.
|
||||
|
@ -578,7 +612,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// ```
|
||||
#[track_caller]
|
||||
pub fn init_resource<R: Resource + FromWorld>(&mut self) {
|
||||
self.push(init_resource::<R>);
|
||||
self.queue(init_resource::<R>);
|
||||
}
|
||||
|
||||
/// Pushes a [`Command`] to the queue for inserting a [`Resource`] in the [`World`] with a specific value.
|
||||
|
@ -608,7 +642,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// ```
|
||||
#[track_caller]
|
||||
pub fn insert_resource<R: Resource>(&mut self, resource: R) {
|
||||
self.push(insert_resource(resource));
|
||||
self.queue(insert_resource(resource));
|
||||
}
|
||||
|
||||
/// Pushes a [`Command`] to the queue for removing a [`Resource`] from the [`World`].
|
||||
|
@ -632,7 +666,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
pub fn remove_resource<R: Resource>(&mut self) {
|
||||
self.push(remove_resource::<R>);
|
||||
self.queue(remove_resource::<R>);
|
||||
}
|
||||
|
||||
/// Runs the system corresponding to the given [`SystemId`].
|
||||
|
@ -658,7 +692,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// execution of the system happens later. To get the output of a system, use
|
||||
/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command.
|
||||
pub fn run_system_with_input<I: 'static + Send>(&mut self, id: SystemId<I>, input: I) {
|
||||
self.push(RunSystemWithInput::new_with_input(id, input));
|
||||
self.queue(RunSystemWithInput::new_with_input(id, input));
|
||||
}
|
||||
|
||||
/// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`].
|
||||
|
@ -720,53 +754,16 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
system: S,
|
||||
) -> SystemId<I, O> {
|
||||
let entity = self.spawn_empty().id();
|
||||
self.push(RegisterSystem::new(system, entity));
|
||||
self.queue(RegisterSystem::new(system, entity));
|
||||
SystemId::from_entity(entity)
|
||||
}
|
||||
|
||||
/// Pushes a generic [`Command`] to the command queue.
|
||||
///
|
||||
/// `command` can be a built-in command, custom struct that implements [`Command`] or a closure
|
||||
/// that takes [`&mut World`](World) as an argument.
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{world::Command, prelude::*};
|
||||
/// #[derive(Resource, Default)]
|
||||
/// struct Counter(u64);
|
||||
///
|
||||
/// struct AddToCounter(u64);
|
||||
///
|
||||
/// impl Command for AddToCounter {
|
||||
/// fn apply(self, world: &mut World) {
|
||||
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
|
||||
/// counter.0 += self.0;
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn add_three_to_counter_system(mut commands: Commands) {
|
||||
/// commands.add(AddToCounter(3));
|
||||
/// }
|
||||
/// fn add_twenty_five_to_counter_system(mut commands: Commands) {
|
||||
/// commands.add(|world: &mut World| {
|
||||
/// let mut counter = world.get_resource_or_insert_with(Counter::default);
|
||||
/// counter.0 += 25;
|
||||
/// });
|
||||
/// }
|
||||
|
||||
/// # bevy_ecs::system::assert_is_system(add_three_to_counter_system);
|
||||
/// # bevy_ecs::system::assert_is_system(add_twenty_five_to_counter_system);
|
||||
/// ```
|
||||
pub fn add<C: Command>(&mut self, command: C) {
|
||||
self.push(command);
|
||||
}
|
||||
|
||||
/// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that
|
||||
/// isn't scoped to specific targets.
|
||||
///
|
||||
/// [`Trigger`]: crate::observer::Trigger
|
||||
pub fn trigger(&mut self, event: impl Event) {
|
||||
self.add(TriggerEvent { event, targets: () });
|
||||
self.queue(TriggerEvent { event, targets: () });
|
||||
}
|
||||
|
||||
/// Sends a [`Trigger`] for the given targets. This will run any [`Observer`] of the `event` that
|
||||
|
@ -778,7 +775,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
event: impl Event,
|
||||
targets: impl TriggerTargets + Send + Sync + 'static,
|
||||
) {
|
||||
self.add(TriggerEvent { event, targets });
|
||||
self.queue(TriggerEvent { event, targets });
|
||||
}
|
||||
|
||||
/// Spawns an [`Observer`] and returns the [`EntityCommands`] associated with the entity that stores the observer.
|
||||
|
@ -800,7 +797,7 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
///
|
||||
/// [`EventWriter`]: crate::event::EventWriter
|
||||
pub fn send_event<E: Event>(&mut self, event: E) -> &mut Self {
|
||||
self.add(SendEvent { event });
|
||||
self.queue(SendEvent { event });
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -848,8 +845,8 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// # assert_schedule.run(&mut world);
|
||||
///
|
||||
/// fn setup(mut commands: Commands) {
|
||||
/// commands.spawn_empty().add(count_name);
|
||||
/// commands.spawn_empty().add(count_name);
|
||||
/// commands.spawn_empty().queue(count_name);
|
||||
/// commands.spawn_empty().queue(count_name);
|
||||
/// }
|
||||
///
|
||||
/// fn assert_names(named: Query<&Name>) {
|
||||
|
@ -911,6 +908,38 @@ impl EntityCommands<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get an [`EntityEntryCommands`] for the [`Component`] `T`,
|
||||
/// allowing you to modify it or insert it if it isn't already present.
|
||||
///
|
||||
/// See also [`insert_if_new`](Self::insert_if_new), which lets you insert a [`Bundle`] without overwriting it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct PlayerEntity { entity: Entity }
|
||||
/// #[derive(Component)]
|
||||
/// struct Level(u32);
|
||||
///
|
||||
/// fn level_up_system(mut commands: Commands, player: Res<PlayerEntity>) {
|
||||
/// commands
|
||||
/// .entity(player.entity)
|
||||
/// .entry::<Level>()
|
||||
/// // Modify the component if it exists
|
||||
/// .and_modify(|mut lvl| lvl.0 += 1)
|
||||
/// // Otherwise insert a default value
|
||||
/// .or_insert(Level(0));
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(level_up_system);
|
||||
/// ```
|
||||
pub fn entry<T: Component>(&mut self) -> EntityEntryCommands<T> {
|
||||
EntityEntryCommands {
|
||||
entity_commands: self.reborrow(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a [`Bundle`] of components to the entity.
|
||||
///
|
||||
/// This will overwrite any previous value(s) of the same component type.
|
||||
|
@ -965,7 +994,7 @@ impl EntityCommands<'_> {
|
|||
/// ```
|
||||
#[track_caller]
|
||||
pub fn insert(self, bundle: impl Bundle) -> Self {
|
||||
self.add(insert(bundle, InsertMode::Replace))
|
||||
self.queue(insert(bundle, InsertMode::Replace))
|
||||
}
|
||||
|
||||
/// Similar to [`Self::insert`] but will only insert if the predicate returns true.
|
||||
|
@ -1003,7 +1032,7 @@ impl EntityCommands<'_> {
|
|||
F: FnOnce() -> bool,
|
||||
{
|
||||
if condition() {
|
||||
self.add(insert(bundle, InsertMode::Replace))
|
||||
self.queue(insert(bundle, InsertMode::Replace))
|
||||
} else {
|
||||
self
|
||||
}
|
||||
|
@ -1015,6 +1044,25 @@ impl EntityCommands<'_> {
|
|||
/// components will leave the old values instead of replacing them with new
|
||||
/// ones.
|
||||
///
|
||||
/// See also [`entry`](Self::entry), which lets you modify a [`Component`] if it's present,
|
||||
/// as well as initialize it with a default value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The command will panic when applied if the associated entity does not exist.
|
||||
///
|
||||
/// To avoid a panic in this case, use the command [`Self::try_insert_if_new`] instead.
|
||||
pub fn insert_if_new(self, bundle: impl Bundle) -> Self {
|
||||
self.queue(insert(bundle, InsertMode::Keep))
|
||||
}
|
||||
|
||||
/// Adds a [`Bundle`] of components to the entity without overwriting if the
|
||||
/// predicate returns true.
|
||||
///
|
||||
/// This is the same as [`EntityCommands::insert_if`], but in case of duplicate
|
||||
/// components will leave the old values instead of replacing them with new
|
||||
/// ones.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The command will panic when applied if the associated entity does not
|
||||
|
@ -1022,8 +1070,15 @@ impl EntityCommands<'_> {
|
|||
///
|
||||
/// To avoid a panic in this case, use the command [`Self::try_insert_if_new`]
|
||||
/// instead.
|
||||
pub fn insert_if_new(self, bundle: impl Bundle) -> Self {
|
||||
self.add(insert(bundle, InsertMode::Keep))
|
||||
pub fn insert_if_new_and<F>(self, bundle: impl Bundle, condition: F) -> Self
|
||||
where
|
||||
F: FnOnce() -> bool,
|
||||
{
|
||||
if condition() {
|
||||
self.insert_if_new(bundle)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a dynamic component to an entity.
|
||||
|
@ -1048,7 +1103,7 @@ impl EntityCommands<'_> {
|
|||
) -> Self {
|
||||
let caller = Location::caller();
|
||||
// SAFETY: same invariants as parent call
|
||||
self.add(unsafe {insert_by_id(component_id, value, move |entity| {
|
||||
self.queue(unsafe {insert_by_id(component_id, value, move |entity| {
|
||||
panic!("error[B0003]: {caller}: Could not insert a component {component_id:?} (with type {}) for entity {entity:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", std::any::type_name::<T>());
|
||||
})})
|
||||
}
|
||||
|
@ -1067,7 +1122,7 @@ impl EntityCommands<'_> {
|
|||
value: T,
|
||||
) -> Self {
|
||||
// SAFETY: same invariants as parent call
|
||||
self.add(unsafe { insert_by_id(component_id, value, |_| {}) })
|
||||
self.queue(unsafe { insert_by_id(component_id, value, |_| {}) })
|
||||
}
|
||||
|
||||
/// Tries to add a [`Bundle`] of components to the entity.
|
||||
|
@ -1120,7 +1175,7 @@ impl EntityCommands<'_> {
|
|||
/// ```
|
||||
#[track_caller]
|
||||
pub fn try_insert(self, bundle: impl Bundle) -> Self {
|
||||
self.add(try_insert(bundle, InsertMode::Replace))
|
||||
self.queue(try_insert(bundle, InsertMode::Replace))
|
||||
}
|
||||
|
||||
/// Similar to [`Self::try_insert`] but will only try to insert if the predicate returns true.
|
||||
|
@ -1155,7 +1210,53 @@ impl EntityCommands<'_> {
|
|||
F: FnOnce() -> bool,
|
||||
{
|
||||
if condition() {
|
||||
self.add(try_insert(bundle, InsertMode::Replace))
|
||||
self.queue(try_insert(bundle, InsertMode::Replace))
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to add a [`Bundle`] of components to the entity without overwriting if the
|
||||
/// predicate returns true.
|
||||
///
|
||||
/// This is the same as [`EntityCommands::try_insert_if`], but in case of duplicate
|
||||
/// components will leave the old values instead of replacing them with new
|
||||
/// ones.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Unlike [`Self::insert_if_new_and`], this will not panic if the associated entity does
|
||||
/// not exist.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource)]
|
||||
/// # struct PlayerEntity { entity: Entity }
|
||||
/// # impl PlayerEntity { fn is_spectator(&self) -> bool { true } }
|
||||
/// #[derive(Component)]
|
||||
/// struct StillLoadingStats;
|
||||
/// #[derive(Component)]
|
||||
/// struct Health(u32);
|
||||
///
|
||||
/// fn add_health_system(mut commands: Commands, player: Res<PlayerEntity>) {
|
||||
/// commands.entity(player.entity)
|
||||
/// .try_insert_if(Health(10), || player.is_spectator())
|
||||
/// .remove::<StillLoadingStats>();
|
||||
///
|
||||
/// commands.entity(player.entity)
|
||||
/// // This will not panic nor will it overwrite the component
|
||||
/// .try_insert_if_new_and(Health(5), || player.is_spectator());
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(add_health_system);
|
||||
/// ```
|
||||
pub fn try_insert_if_new_and<F>(self, bundle: impl Bundle, condition: F) -> Self
|
||||
where
|
||||
F: FnOnce() -> bool,
|
||||
{
|
||||
if condition() {
|
||||
self.try_insert_if_new(bundle)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
|
@ -1171,7 +1272,7 @@ impl EntityCommands<'_> {
|
|||
///
|
||||
/// Unlike [`Self::insert_if_new`], this will not panic if the associated entity does not exist.
|
||||
pub fn try_insert_if_new(self, bundle: impl Bundle) -> Self {
|
||||
self.add(try_insert(bundle, InsertMode::Keep))
|
||||
self.queue(try_insert(bundle, InsertMode::Keep))
|
||||
}
|
||||
|
||||
/// Removes a [`Bundle`] of components from the entity.
|
||||
|
@ -1213,17 +1314,17 @@ impl EntityCommands<'_> {
|
|||
where
|
||||
T: Bundle,
|
||||
{
|
||||
self.add(remove::<T>)
|
||||
self.queue(remove::<T>)
|
||||
}
|
||||
|
||||
/// Removes a component from the entity.
|
||||
pub fn remove_by_id(self, component_id: ComponentId) -> Self {
|
||||
self.add(remove_by_id(component_id))
|
||||
self.queue(remove_by_id(component_id))
|
||||
}
|
||||
|
||||
/// Removes all components associated with the entity.
|
||||
pub fn clear(self) -> Self {
|
||||
self.add(clear())
|
||||
self.queue(clear())
|
||||
}
|
||||
|
||||
/// Despawns the entity.
|
||||
|
@ -1255,7 +1356,7 @@ impl EntityCommands<'_> {
|
|||
/// ```
|
||||
#[track_caller]
|
||||
pub fn despawn(self) -> Self {
|
||||
self.add(despawn())
|
||||
self.queue(despawn())
|
||||
}
|
||||
|
||||
/// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`].
|
||||
|
@ -1268,15 +1369,15 @@ impl EntityCommands<'_> {
|
|||
/// commands
|
||||
/// .spawn_empty()
|
||||
/// // Closures with this signature implement `EntityCommand`.
|
||||
/// .add(|entity: EntityWorldMut| {
|
||||
/// .queue(|entity: EntityWorldMut| {
|
||||
/// println!("Executed an EntityCommand for {:?}", entity.id());
|
||||
/// });
|
||||
/// # }
|
||||
/// # bevy_ecs::system::assert_is_system(my_system);
|
||||
/// ```
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn add<M: 'static>(mut self, command: impl EntityCommand<M>) -> Self {
|
||||
self.commands.add(command.with_entity(self.entity));
|
||||
pub fn queue<M: 'static>(mut self, command: impl EntityCommand<M>) -> Self {
|
||||
self.commands.queue(command.with_entity(self.entity));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1321,7 +1422,7 @@ impl EntityCommands<'_> {
|
|||
where
|
||||
T: Bundle,
|
||||
{
|
||||
self.add(retain::<T>)
|
||||
self.queue(retain::<T>)
|
||||
}
|
||||
|
||||
/// Logs the components of the entity at the info level.
|
||||
|
@ -1330,7 +1431,7 @@ impl EntityCommands<'_> {
|
|||
///
|
||||
/// The command will panic when applied if the associated entity does not exist.
|
||||
pub fn log_components(self) -> Self {
|
||||
self.add(log_components)
|
||||
self.queue(log_components)
|
||||
}
|
||||
|
||||
/// Returns the underlying [`Commands`].
|
||||
|
@ -1349,7 +1450,114 @@ impl EntityCommands<'_> {
|
|||
|
||||
/// Creates an [`Observer`] listening for a trigger of type `T` that targets this entity.
|
||||
pub fn observe<E: Event, B: Bundle, M>(self, system: impl IntoObserverSystem<E, B, M>) -> Self {
|
||||
self.add(observe(system))
|
||||
self.queue(observe(system))
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around [`EntityCommands`] with convenience methods for working with a specified component type.
|
||||
pub struct EntityEntryCommands<'a, T> {
|
||||
entity_commands: EntityCommands<'a>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Component> EntityEntryCommands<'a, T> {
|
||||
/// Modify the component `T` if it exists, using the the function `modify`.
|
||||
pub fn and_modify(mut self, modify: impl FnOnce(Mut<T>) + Send + Sync + 'static) -> Self {
|
||||
self.entity_commands = self
|
||||
.entity_commands
|
||||
.queue(move |mut entity: EntityWorldMut| {
|
||||
if let Some(value) = entity.get_mut() {
|
||||
modify(value);
|
||||
}
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present.
|
||||
///
|
||||
/// See also [`or_insert_with`](Self::or_insert_with).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the entity does not exist.
|
||||
/// See [`or_try_insert`](Self::or_try_insert) for a non-panicking version.
|
||||
#[track_caller]
|
||||
pub fn or_insert(mut self, default: T) -> Self {
|
||||
self.entity_commands = self
|
||||
.entity_commands
|
||||
.queue(insert(default, InsertMode::Keep));
|
||||
self
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) `default` into this entity, if `T` is not already present.
|
||||
///
|
||||
/// Unlike [`or_insert`](Self::or_insert), this will not panic if the entity does not exist.
|
||||
///
|
||||
/// See also [`or_insert_with`](Self::or_insert_with).
|
||||
#[track_caller]
|
||||
pub fn or_try_insert(mut self, default: T) -> Self {
|
||||
self.entity_commands = self
|
||||
.entity_commands
|
||||
.queue(try_insert(default, InsertMode::Keep));
|
||||
self
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present.
|
||||
///
|
||||
/// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the entity does not exist.
|
||||
/// See [`or_try_insert_with`](Self::or_try_insert_with) for a non-panicking version.
|
||||
#[track_caller]
|
||||
pub fn or_insert_with(self, default: impl Fn() -> T) -> Self {
|
||||
self.or_insert(default())
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity, if `T` is not already present.
|
||||
///
|
||||
/// Unlike [`or_insert_with`](Self::or_insert_with), this will not panic if the entity does not exist.
|
||||
///
|
||||
/// See also [`or_insert`](Self::or_insert) and [`or_try_insert`](Self::or_try_insert).
|
||||
#[track_caller]
|
||||
pub fn or_try_insert_with(self, default: impl Fn() -> T) -> Self {
|
||||
self.or_try_insert(default())
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) `T::default` into this entity, if `T` is not already present.
|
||||
///
|
||||
/// See also [`or_insert`](Self::or_insert) and [`or_from_world`](Self::or_from_world).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the entity does not exist.
|
||||
#[track_caller]
|
||||
pub fn or_default(self) -> Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
#[allow(clippy::unwrap_or_default)]
|
||||
// FIXME: use `expect` once stable
|
||||
self.or_insert(T::default())
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) `T::from_world` into this entity, if `T` is not already present.
|
||||
///
|
||||
/// See also [`or_insert`](Self::or_insert) and [`or_default`](Self::or_default).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the entity does not exist.
|
||||
#[track_caller]
|
||||
pub fn or_from_world(mut self) -> Self
|
||||
where
|
||||
T: FromWorld,
|
||||
{
|
||||
self.entity_commands = self
|
||||
.entity_commands
|
||||
.queue(insert_from_world::<T>(InsertMode::Keep));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1461,6 +1669,25 @@ fn insert<T: Bundle>(bundle: T, mode: InsertMode) -> impl EntityCommand {
|
|||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that adds the component using its `FromWorld` implementation.
|
||||
#[track_caller]
|
||||
fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
|
||||
let caller = Location::caller();
|
||||
move |entity: Entity, world: &mut World| {
|
||||
let value = T::from_world(world);
|
||||
if let Some(mut entity) = world.get_entity_mut(entity) {
|
||||
entity.insert_with_caller(
|
||||
value,
|
||||
mode,
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
caller,
|
||||
);
|
||||
} else {
|
||||
panic!("error[B0003]: {caller}: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003", std::any::type_name::<T>(), entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that attempts to add the components in a [`Bundle`] to an entity.
|
||||
/// Does nothing if the entity does not exist.
|
||||
#[track_caller]
|
||||
|
@ -1596,7 +1823,7 @@ mod tests {
|
|||
self as bevy_ecs,
|
||||
component::Component,
|
||||
system::{Commands, Resource},
|
||||
world::{CommandQueue, World},
|
||||
world::{CommandQueue, FromWorld, World},
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
|
@ -1633,6 +1860,50 @@ mod tests {
|
|||
world.spawn((W(0u32), W(42u64)));
|
||||
}
|
||||
|
||||
impl FromWorld for W<String> {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let v = world.resource::<W<usize>>();
|
||||
Self("*".repeat(v.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_commands_entry() {
|
||||
let mut world = World::default();
|
||||
let mut queue = CommandQueue::default();
|
||||
let mut commands = Commands::new(&mut queue, &world);
|
||||
let entity = commands.spawn_empty().id();
|
||||
commands
|
||||
.entity(entity)
|
||||
.entry::<W<u32>>()
|
||||
.and_modify(|_| unreachable!());
|
||||
queue.apply(&mut world);
|
||||
assert!(!world.entity(entity).contains::<W<u32>>());
|
||||
let mut commands = Commands::new(&mut queue, &world);
|
||||
commands
|
||||
.entity(entity)
|
||||
.entry::<W<u32>>()
|
||||
.or_insert(W(0))
|
||||
.and_modify(|mut val| {
|
||||
val.0 = 21;
|
||||
});
|
||||
queue.apply(&mut world);
|
||||
assert_eq!(21, world.get::<W<u32>>(entity).unwrap().0);
|
||||
let mut commands = Commands::new(&mut queue, &world);
|
||||
commands
|
||||
.entity(entity)
|
||||
.entry::<W<u64>>()
|
||||
.and_modify(|_| unreachable!())
|
||||
.or_insert(W(42));
|
||||
queue.apply(&mut world);
|
||||
assert_eq!(42, world.get::<W<u64>>(entity).unwrap().0);
|
||||
world.insert_resource(W(5_usize));
|
||||
let mut commands = Commands::new(&mut queue, &world);
|
||||
commands.entity(entity).entry::<W<String>>().or_from_world();
|
||||
queue.apply(&mut world);
|
||||
assert_eq!("*****", &world.get::<W<String>>(entity).unwrap().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commands() {
|
||||
let mut world = World::default();
|
||||
|
@ -1667,12 +1938,12 @@ mod tests {
|
|||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
|
||||
// set up a simple command using a closure that adds one additional entity
|
||||
commands.add(|world: &mut World| {
|
||||
commands.queue(|world: &mut World| {
|
||||
world.spawn((W(42u32), W(0u64)));
|
||||
});
|
||||
|
||||
// set up a simple command using a function that adds one additional entity
|
||||
commands.add(simple_command);
|
||||
commands.queue(simple_command);
|
||||
}
|
||||
command_queue.apply(&mut world);
|
||||
let results3 = world
|
||||
|
@ -1684,6 +1955,45 @@ mod tests {
|
|||
assert_eq!(results3, vec![(42u32, 0u64), (0u32, 42u64)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_components() {
|
||||
let mut world = World::default();
|
||||
let mut command_queue1 = CommandQueue::default();
|
||||
|
||||
// insert components
|
||||
let entity = Commands::new(&mut command_queue1, &world)
|
||||
.spawn(())
|
||||
.insert_if(W(1u8), || true)
|
||||
.insert_if(W(2u8), || false)
|
||||
.insert_if_new(W(1u16))
|
||||
.insert_if_new(W(2u16))
|
||||
.insert_if_new_and(W(1u32), || false)
|
||||
.insert_if_new_and(W(2u32), || true)
|
||||
.insert_if_new_and(W(3u32), || true)
|
||||
.id();
|
||||
command_queue1.apply(&mut world);
|
||||
|
||||
let results = world
|
||||
.query::<(&W<u8>, &W<u16>, &W<u32>)>()
|
||||
.iter(&world)
|
||||
.map(|(a, b, c)| (a.0, b.0, c.0))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(results, vec![(1u8, 1u16, 2u32)]);
|
||||
|
||||
// try to insert components after despawning entity
|
||||
// in another command queue
|
||||
Commands::new(&mut command_queue1, &world)
|
||||
.entity(entity)
|
||||
.try_insert_if_new_and(W(1u64), || true);
|
||||
|
||||
let mut command_queue2 = CommandQueue::default();
|
||||
Commands::new(&mut command_queue2, &world)
|
||||
.entity(entity)
|
||||
.despawn();
|
||||
command_queue2.apply(&mut world);
|
||||
command_queue1.apply(&mut world);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_components() {
|
||||
let mut world = World::default();
|
||||
|
|
|
@ -1493,13 +1493,10 @@ mod tests {
|
|||
// set up system and verify its access is empty
|
||||
system.initialize(&mut world);
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
assert_eq!(
|
||||
system
|
||||
.archetype_component_access()
|
||||
.component_reads()
|
||||
.collect::<HashSet<_>>(),
|
||||
expected_ids
|
||||
);
|
||||
let archetype_component_access = system.archetype_component_access();
|
||||
assert!(expected_ids
|
||||
.iter()
|
||||
.all(|id| archetype_component_access.has_component_read(*id)));
|
||||
|
||||
// add some entities with archetypes that should match and save their ids
|
||||
expected_ids.insert(
|
||||
|
@ -1523,13 +1520,10 @@ mod tests {
|
|||
|
||||
// update system and verify its accesses are correct
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
assert_eq!(
|
||||
system
|
||||
.archetype_component_access()
|
||||
.component_reads()
|
||||
.collect::<HashSet<_>>(),
|
||||
expected_ids
|
||||
);
|
||||
let archetype_component_access = system.archetype_component_access();
|
||||
assert!(expected_ids
|
||||
.iter()
|
||||
.all(|id| archetype_component_access.has_component_read(*id)));
|
||||
|
||||
// one more round
|
||||
expected_ids.insert(
|
||||
|
@ -1541,13 +1535,10 @@ mod tests {
|
|||
);
|
||||
world.spawn((A, B, D));
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
assert_eq!(
|
||||
system
|
||||
.archetype_component_access()
|
||||
.component_reads()
|
||||
.collect::<HashSet<_>>(),
|
||||
expected_ids
|
||||
);
|
||||
let archetype_component_access = system.archetype_component_access();
|
||||
assert!(expected_ids
|
||||
.iter()
|
||||
.all(|id| archetype_component_access.has_component_read(*id)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -616,13 +616,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||
///
|
||||
/// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items.
|
||||
#[inline]
|
||||
pub fn iter_many<EntityList: IntoIterator>(
|
||||
pub fn iter_many<EntityList: IntoIterator<Item: Borrow<Entity>>>(
|
||||
&self,
|
||||
entities: EntityList,
|
||||
) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter>
|
||||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
{
|
||||
) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> {
|
||||
// SAFETY:
|
||||
// - `self.world` has permission to access the required components.
|
||||
// - The query is read-only, so it can be aliased even if it was originally mutable.
|
||||
|
@ -670,13 +667,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn iter_many_mut<EntityList: IntoIterator>(
|
||||
pub fn iter_many_mut<EntityList: IntoIterator<Item: Borrow<Entity>>>(
|
||||
&mut self,
|
||||
entities: EntityList,
|
||||
) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter>
|
||||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
{
|
||||
) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> {
|
||||
// SAFETY: `self.world` has permission to access the required components.
|
||||
unsafe {
|
||||
self.state.iter_many_unchecked_manual(
|
||||
|
@ -752,13 +746,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||
/// # See also
|
||||
///
|
||||
/// - [`iter_many_mut`](Self::iter_many_mut) to safely access the query items.
|
||||
pub unsafe fn iter_many_unsafe<EntityList: IntoIterator>(
|
||||
pub unsafe fn iter_many_unsafe<EntityList: IntoIterator<Item: Borrow<Entity>>>(
|
||||
&self,
|
||||
entities: EntityList,
|
||||
) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter>
|
||||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
{
|
||||
) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> {
|
||||
// SAFETY:
|
||||
// - `self.world` has permission to access the required components.
|
||||
// - The caller ensures that this operation will not result in any aliased mutable accesses.
|
||||
|
|
|
@ -1894,7 +1894,9 @@ unsafe impl<T: ?Sized> ReadOnlySystemParam for PhantomData<T> {}
|
|||
/// assert!(param.is::<Res<A>>());
|
||||
/// assert!(!param.is::<Res<B>>());
|
||||
/// assert!(param.downcast_mut::<Res<B>>().is_none());
|
||||
/// let foo: Res<A> = param.downcast::<Res<A>>().unwrap();
|
||||
/// let res = param.downcast_mut::<Res<A>>().unwrap();
|
||||
/// // The type parameter can be left out if it can be determined from use.
|
||||
/// let res: Res<A> = param.downcast().unwrap();
|
||||
/// }
|
||||
///
|
||||
/// let system = (
|
||||
|
@ -1921,7 +1923,7 @@ pub struct DynSystemParam<'w, 's> {
|
|||
}
|
||||
|
||||
impl<'w, 's> DynSystemParam<'w, 's> {
|
||||
/// # SAFETY
|
||||
/// # Safety
|
||||
/// - `state` must be a `ParamState<T>` for some inner `T: SystemParam`.
|
||||
/// - The passed [`UnsafeWorldCell`] must have access to any world data registered
|
||||
/// in [`init_state`](SystemParam::init_state) for the inner system param.
|
||||
|
@ -1942,13 +1944,21 @@ impl<'w, 's> DynSystemParam<'w, 's> {
|
|||
}
|
||||
|
||||
/// Returns `true` if the inner system param is the same as `T`.
|
||||
pub fn is<T: SystemParam + 'static>(&self) -> bool {
|
||||
self.state.is::<ParamState<T>>()
|
||||
pub fn is<T: SystemParam>(&self) -> bool
|
||||
// See downcast() function for an explanation of the where clause
|
||||
where
|
||||
T::Item<'static, 'static>: SystemParam<Item<'w, 's> = T> + 'static,
|
||||
{
|
||||
self.state.is::<ParamState<T::Item<'static, 'static>>>()
|
||||
}
|
||||
|
||||
/// Returns the inner system param if it is the correct type.
|
||||
/// This consumes the dyn param, so the returned param can have its original world and state lifetimes.
|
||||
pub fn downcast<T: SystemParam + 'static>(self) -> Option<T::Item<'w, 's>> {
|
||||
pub fn downcast<T: SystemParam>(self) -> Option<T>
|
||||
// See downcast() function for an explanation of the where clause
|
||||
where
|
||||
T::Item<'static, 'static>: SystemParam<Item<'w, 's> = T> + 'static,
|
||||
{
|
||||
// SAFETY:
|
||||
// - `DynSystemParam::new()` ensures `state` is a `ParamState<T>`, that the world matches,
|
||||
// and that it has access required by the inner system param.
|
||||
|
@ -1958,7 +1968,11 @@ impl<'w, 's> DynSystemParam<'w, 's> {
|
|||
|
||||
/// Returns the inner system parameter if it is the correct type.
|
||||
/// This borrows the dyn param, so the returned param is only valid for the duration of that borrow.
|
||||
pub fn downcast_mut<T: SystemParam + 'static>(&mut self) -> Option<T::Item<'_, '_>> {
|
||||
pub fn downcast_mut<'a, T: SystemParam>(&'a mut self) -> Option<T>
|
||||
// See downcast() function for an explanation of the where clause
|
||||
where
|
||||
T::Item<'static, 'static>: SystemParam<Item<'a, 'a> = T> + 'static,
|
||||
{
|
||||
// SAFETY:
|
||||
// - `DynSystemParam::new()` ensures `state` is a `ParamState<T>`, that the world matches,
|
||||
// and that it has access required by the inner system param.
|
||||
|
@ -1971,9 +1985,11 @@ impl<'w, 's> DynSystemParam<'w, 's> {
|
|||
/// but since it only performs read access it can keep the original world lifetime.
|
||||
/// This can be useful with methods like [`Query::iter_inner()`] or [`Res::into_inner()`]
|
||||
/// to obtain references with the original world lifetime.
|
||||
pub fn downcast_mut_inner<T: ReadOnlySystemParam + 'static>(
|
||||
&mut self,
|
||||
) -> Option<T::Item<'w, '_>> {
|
||||
pub fn downcast_mut_inner<'a, T: ReadOnlySystemParam>(&'a mut self) -> Option<T>
|
||||
// See downcast() function for an explanation of the where clause
|
||||
where
|
||||
T::Item<'static, 'static>: SystemParam<Item<'w, 'a> = T> + 'static,
|
||||
{
|
||||
// SAFETY:
|
||||
// - `DynSystemParam::new()` ensures `state` is a `ParamState<T>`, that the world matches,
|
||||
// and that it has access required by the inner system param.
|
||||
|
@ -1982,25 +1998,38 @@ impl<'w, 's> DynSystemParam<'w, 's> {
|
|||
}
|
||||
}
|
||||
|
||||
/// # SAFETY
|
||||
/// # Safety
|
||||
/// - `state` must be a `ParamState<T>` for some inner `T: SystemParam`.
|
||||
/// - The passed [`UnsafeWorldCell`] must have access to any world data registered
|
||||
/// in [`init_state`](SystemParam::init_state) for the inner system param.
|
||||
/// - `world` must be the same `World` that was used to initialize
|
||||
/// [`state`](SystemParam::init_state) for the inner system param.
|
||||
unsafe fn downcast<'w, 's, T: SystemParam + 'static>(
|
||||
unsafe fn downcast<'w, 's, T: SystemParam>(
|
||||
state: &'s mut dyn Any,
|
||||
system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
change_tick: Tick,
|
||||
) -> Option<T::Item<'w, 's>> {
|
||||
state.downcast_mut::<ParamState<T>>().map(|state| {
|
||||
// SAFETY:
|
||||
// - The caller ensures the world has access for the underlying system param,
|
||||
// and since the downcast succeeded, the underlying system param is T.
|
||||
// - The caller ensures the `world` matches.
|
||||
unsafe { T::get_param(&mut state.0, system_meta, world, change_tick) }
|
||||
})
|
||||
) -> Option<T>
|
||||
// We need a 'static version of the SystemParam to use with `Any::downcast_mut()`,
|
||||
// and we need a <'w, 's> version to actually return.
|
||||
// The type parameter T must be the one we return in order to get type inference from the return value.
|
||||
// So we use `T::Item<'static, 'static>` as the 'static version, and require that it be 'static.
|
||||
// That means the return value will be T::Item<'static, 'static>::Item<'w, 's>,
|
||||
// so we constrain that to be equal to T.
|
||||
// Every actual `SystemParam` implementation has `T::Item == T` up to lifetimes,
|
||||
// so they should all work with this constraint.
|
||||
where
|
||||
T::Item<'static, 'static>: SystemParam<Item<'w, 's> = T> + 'static,
|
||||
{
|
||||
state
|
||||
.downcast_mut::<ParamState<T::Item<'static, 'static>>>()
|
||||
.map(|state| {
|
||||
// SAFETY:
|
||||
// - The caller ensures the world has access for the underlying system param,
|
||||
// and since the downcast succeeded, the underlying system param is T.
|
||||
// - The caller ensures the `world` matches.
|
||||
unsafe { T::Item::get_param(&mut state.0, system_meta, world, change_tick) }
|
||||
})
|
||||
}
|
||||
|
||||
/// The [`SystemParam::State`] for a [`DynSystemParam`].
|
||||
|
@ -2323,4 +2352,12 @@ mod tests {
|
|||
schedule.add_systems((non_send_param_set, non_send_param_set, non_send_param_set));
|
||||
schedule.run(&mut world);
|
||||
}
|
||||
|
||||
fn _dyn_system_param_type_inference(mut p: DynSystemParam) {
|
||||
// Make sure the downcast() methods are able to infer their type parameters from the use of the return type.
|
||||
// This is just a compilation test, so there is nothing to run.
|
||||
let _query: Query<()> = p.downcast_mut().unwrap();
|
||||
let _query: Query<()> = p.downcast_mut_inner().unwrap();
|
||||
let _query: Query<()> = p.downcast().unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -479,20 +479,20 @@ mod test {
|
|||
fn add_index(index: usize) -> impl Command {
|
||||
move |world: &mut World| world.resource_mut::<Order>().0.push(index)
|
||||
}
|
||||
world.commands().add(add_index(1));
|
||||
world.commands().add(|world: &mut World| {
|
||||
world.commands().add(add_index(2));
|
||||
world.commands().add(PanicCommand("I panic!".to_owned()));
|
||||
world.commands().add(add_index(3));
|
||||
world.commands().queue(add_index(1));
|
||||
world.commands().queue(|world: &mut World| {
|
||||
world.commands().queue(add_index(2));
|
||||
world.commands().queue(PanicCommand("I panic!".to_owned()));
|
||||
world.commands().queue(add_index(3));
|
||||
world.flush_commands();
|
||||
});
|
||||
world.commands().add(add_index(4));
|
||||
world.commands().queue(add_index(4));
|
||||
|
||||
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
|
||||
world.flush_commands();
|
||||
}));
|
||||
|
||||
world.commands().add(add_index(5));
|
||||
world.commands().queue(add_index(5));
|
||||
world.flush_commands();
|
||||
assert_eq!(&world.resource::<Order>().0, &[1, 2, 3, 4, 5]);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
entity::{Entities, Entity, EntityLocation},
|
||||
event::Event,
|
||||
observer::{Observer, Observers},
|
||||
query::Access,
|
||||
query::{Access, ReadOnlyQueryData},
|
||||
removal_detection::RemovedComponentEvents,
|
||||
storage::Storages,
|
||||
system::IntoObserverSystem,
|
||||
|
@ -156,6 +156,22 @@ impl<'w> EntityRef<'w> {
|
|||
// SAFETY: We have read-only access to all components of this entity.
|
||||
unsafe { self.0.get_by_id(component_id) }
|
||||
}
|
||||
|
||||
/// Returns read-only components for the current entity that match the query `Q`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity does not have the components required by the query `Q`.
|
||||
pub fn components<Q: ReadOnlyQueryData>(&self) -> Q::Item<'w> {
|
||||
self.get_components::<Q>().expect(QUERY_MISMATCH_ERROR)
|
||||
}
|
||||
|
||||
/// Returns read-only components for the current entity that match the query `Q`,
|
||||
/// or `None` if the entity does not have the components required by the query `Q`.
|
||||
pub fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'w>> {
|
||||
// SAFETY: We have read-only access to all components of this entity.
|
||||
unsafe { self.0.get_components::<Q>() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w> From<EntityWorldMut<'w>> for EntityRef<'w> {
|
||||
|
@ -351,6 +367,22 @@ impl<'w> EntityMut<'w> {
|
|||
self.as_readonly().get()
|
||||
}
|
||||
|
||||
/// Returns read-only components for the current entity that match the query `Q`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity does not have the components required by the query `Q`.
|
||||
pub fn components<Q: ReadOnlyQueryData>(&self) -> Q::Item<'_> {
|
||||
self.get_components::<Q>().expect(QUERY_MISMATCH_ERROR)
|
||||
}
|
||||
|
||||
/// Returns read-only components for the current entity that match the query `Q`,
|
||||
/// or `None` if the entity does not have the components required by the query `Q`.
|
||||
pub fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'_>> {
|
||||
// SAFETY: We have read-only access to all components of this entity.
|
||||
unsafe { self.0.get_components::<Q>() }
|
||||
}
|
||||
|
||||
/// Consumes `self` and gets access to the component of type `T` with the
|
||||
/// world `'w` lifetime for the current entity.
|
||||
///
|
||||
|
@ -648,6 +680,23 @@ impl<'w> EntityWorldMut<'w> {
|
|||
EntityRef::from(self).get()
|
||||
}
|
||||
|
||||
/// Returns read-only components for the current entity that match the query `Q`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity does not have the components required by the query `Q`.
|
||||
#[inline]
|
||||
pub fn components<Q: ReadOnlyQueryData>(&self) -> Q::Item<'_> {
|
||||
EntityRef::from(self).components::<Q>()
|
||||
}
|
||||
|
||||
/// Returns read-only components for the current entity that match the query `Q`,
|
||||
/// or `None` if the entity does not have the components required by the query `Q`.
|
||||
#[inline]
|
||||
pub fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'_>> {
|
||||
EntityRef::from(self).get_components::<Q>()
|
||||
}
|
||||
|
||||
/// Consumes `self` and gets access to the component of type `T` with
|
||||
/// the world `'w` lifetime for the current entity.
|
||||
/// Returns `None` if the entity does not have a component of type `T`.
|
||||
|
@ -1081,7 +1130,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||
|
||||
/// Remove the components of `bundle` from `entity`.
|
||||
///
|
||||
/// SAFETY:
|
||||
/// # Safety
|
||||
/// - A `BundleInfo` with the corresponding `BundleId` must have been initialized.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn remove_bundle(&mut self, bundle: BundleId) -> EntityLocation {
|
||||
|
@ -1470,7 +1519,8 @@ impl<'w> EntityWorldMut<'w> {
|
|||
}
|
||||
}
|
||||
|
||||
/// SAFETY: all components in the archetype must exist in world
|
||||
/// # Safety
|
||||
/// All components in the archetype must exist in world
|
||||
unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers(
|
||||
deferred_world: &mut DeferredWorld,
|
||||
archetype: &Archetype,
|
||||
|
@ -1491,6 +1541,8 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers(
|
|||
}
|
||||
}
|
||||
|
||||
const QUERY_MISMATCH_ERROR: &str = "Query does not match the current entity";
|
||||
|
||||
/// A view into a single entity and component in a world, which may either be vacant or occupied.
|
||||
///
|
||||
/// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`].
|
||||
|
@ -1876,12 +1928,6 @@ impl<'w> FilteredEntityRef<'w> {
|
|||
self.entity.archetype()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the component ids that are accessed by self.
|
||||
#[inline]
|
||||
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
|
||||
self.access.component_reads_and_writes()
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [`Access`].
|
||||
#[inline]
|
||||
pub fn access(&self) -> &Access<ComponentId> {
|
||||
|
@ -2133,12 +2179,6 @@ impl<'w> FilteredEntityMut<'w> {
|
|||
self.entity.archetype()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the component ids that are accessed by self.
|
||||
#[inline]
|
||||
pub fn components(&self) -> impl Iterator<Item = ComponentId> + '_ {
|
||||
self.access.component_reads_and_writes()
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying [`Access`].
|
||||
#[inline]
|
||||
pub fn access(&self) -> &Access<ComponentId> {
|
||||
|
@ -2333,6 +2373,184 @@ pub enum TryFromFilteredError {
|
|||
MissingWriteAllAccess,
|
||||
}
|
||||
|
||||
/// Provides read-only access to a single entity and all its components, save
|
||||
/// for an explicitly-enumerated set.
|
||||
#[derive(Clone)]
|
||||
pub struct EntityRefExcept<'w, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
entity: UnsafeEntityCell<'w>,
|
||||
phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<'w, B> EntityRefExcept<'w, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
/// # Safety
|
||||
/// Other users of `UnsafeEntityCell` must only have mutable access to the components in `B`.
|
||||
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets access to the component of type `C` for the current entity. Returns
|
||||
/// `None` if the component doesn't have a component of that type or if the
|
||||
/// type is one of the excluded components.
|
||||
#[inline]
|
||||
pub fn get<C>(&self) -> Option<&'w C>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
let components = self.entity.world().components();
|
||||
let id = components.component_id::<C>()?;
|
||||
if bundle_contains_component::<B>(components, id) {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We have read access for all components that weren't
|
||||
// covered by the `contains` check above.
|
||||
unsafe { self.entity.get() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets access to the component of type `C` for the current entity,
|
||||
/// including change detection information. Returns `None` if the component
|
||||
/// doesn't have a component of that type or if the type is one of the
|
||||
/// excluded components.
|
||||
#[inline]
|
||||
pub fn get_ref<C>(&self) -> Option<Ref<'w, C>>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
let components = self.entity.world().components();
|
||||
let id = components.component_id::<C>()?;
|
||||
if bundle_contains_component::<B>(components, id) {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We have read access for all components that weren't
|
||||
// covered by the `contains` check above.
|
||||
unsafe { self.entity.get_ref() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
fn from(entity_mut: &'a EntityMutExcept<'_, B>) -> Self {
|
||||
// SAFETY: All accesses that `EntityRefExcept` provides are also
|
||||
// accesses that `EntityMutExcept` provides.
|
||||
unsafe { EntityRefExcept::new(entity_mut.entity) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides mutable access to all components of an entity, with the exception
|
||||
/// of an explicit set.
|
||||
///
|
||||
/// This is a rather niche type that should only be used if you need access to
|
||||
/// *all* components of an entity, while still allowing you to consult other
|
||||
/// queries that might match entities that this query also matches. If you don't
|
||||
/// need access to all components, prefer a standard query with a
|
||||
/// [`crate::query::Without`] filter.
|
||||
#[derive(Clone)]
|
||||
pub struct EntityMutExcept<'w, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
entity: UnsafeEntityCell<'w>,
|
||||
phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<'w, B> EntityMutExcept<'w, B>
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
/// # Safety
|
||||
/// Other users of `UnsafeEntityCell` must not have access to any components not in `B`.
|
||||
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new instance with a shorter lifetime.
|
||||
///
|
||||
/// This is useful if you have `&mut EntityMutExcept`, but you need
|
||||
/// `EntityMutExcept`.
|
||||
pub fn reborrow(&mut self) -> EntityMutExcept<'_, B> {
|
||||
// SAFETY: We have exclusive access to the entire entity and the
|
||||
// applicable components.
|
||||
unsafe { Self::new(self.entity) }
|
||||
}
|
||||
|
||||
/// Gets read-only access to all of the entity's components, except for the
|
||||
/// ones in `CL`.
|
||||
#[inline]
|
||||
pub fn as_readonly(&self) -> EntityRefExcept<'_, B> {
|
||||
EntityRefExcept::from(self)
|
||||
}
|
||||
|
||||
/// Gets access to the component of type `C` for the current entity. Returns
|
||||
/// `None` if the component doesn't have a component of that type or if the
|
||||
/// type is one of the excluded components.
|
||||
#[inline]
|
||||
pub fn get<C>(&self) -> Option<&'_ C>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
self.as_readonly().get()
|
||||
}
|
||||
|
||||
/// Gets access to the component of type `C` for the current entity,
|
||||
/// including change detection information. Returns `None` if the component
|
||||
/// doesn't have a component of that type or if the type is one of the
|
||||
/// excluded components.
|
||||
#[inline]
|
||||
pub fn get_ref<C>(&self) -> Option<Ref<'_, C>>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
self.as_readonly().get_ref()
|
||||
}
|
||||
|
||||
/// Gets mutable access to the component of type `C` for the current entity.
|
||||
/// Returns `None` if the component doesn't have a component of that type or
|
||||
/// if the type is one of the excluded components.
|
||||
#[inline]
|
||||
pub fn get_mut<C>(&mut self) -> Option<Mut<'_, C>>
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
let components = self.entity.world().components();
|
||||
let id = components.component_id::<C>()?;
|
||||
if bundle_contains_component::<B>(components, id) {
|
||||
None
|
||||
} else {
|
||||
// SAFETY: We have write access for all components that weren't
|
||||
// covered by the `contains` check above.
|
||||
unsafe { self.entity.get_mut() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bundle_contains_component<B>(components: &Components, query_id: ComponentId) -> bool
|
||||
where
|
||||
B: Bundle,
|
||||
{
|
||||
let mut found = false;
|
||||
B::get_component_ids(components, &mut |maybe_id| {
|
||||
if let Some(id) = maybe_id {
|
||||
found = found || id == query_id;
|
||||
}
|
||||
});
|
||||
found
|
||||
}
|
||||
|
||||
/// Inserts a dynamic [`Bundle`] into the entity.
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -2527,16 +2745,11 @@ pub(crate) unsafe fn take_component<'a>(
|
|||
match component_info.storage_type() {
|
||||
StorageType::Table => {
|
||||
let table = &mut storages.tables[location.table_id];
|
||||
let components = table.get_column_mut(component_id).unwrap();
|
||||
// SAFETY:
|
||||
// - archetypes only store valid table_rows
|
||||
// - index is in bounds as promised by caller
|
||||
// - promote is safe because the caller promises to remove the table row without dropping it immediately afterwards
|
||||
unsafe {
|
||||
components
|
||||
.get_data_unchecked_mut(location.table_row)
|
||||
.promote()
|
||||
}
|
||||
unsafe { table.take_component(component_id, location.table_row) }
|
||||
}
|
||||
StorageType::SparseSet => storages
|
||||
.sparse_sets
|
||||
|
@ -2552,9 +2765,12 @@ mod tests {
|
|||
use bevy_ptr::OwningPtr;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
|
||||
use crate::system::RunSystemOnce as _;
|
||||
use crate::world::{FilteredEntityMut, FilteredEntityRef};
|
||||
use crate::{self as bevy_ecs, component::ComponentId, prelude::*, system::assert_is_system};
|
||||
|
||||
use super::{EntityMutExcept, EntityRefExcept};
|
||||
|
||||
#[test]
|
||||
fn sorted_remove() {
|
||||
let mut a = vec![1, 2, 3, 4, 5, 6, 7];
|
||||
|
@ -2947,6 +3163,164 @@ mod tests {
|
|||
world.spawn_empty().remove_by_id(test_component_id);
|
||||
}
|
||||
|
||||
/// Tests that components can be accessed through an `EntityRefExcept`.
|
||||
#[test]
|
||||
fn entity_ref_except() {
|
||||
let mut world = World::new();
|
||||
world.init_component::<TestComponent>();
|
||||
world.init_component::<TestComponent2>();
|
||||
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
let mut query = world.query::<EntityRefExcept<TestComponent>>();
|
||||
|
||||
let mut found = false;
|
||||
for entity_ref in query.iter_mut(&mut world) {
|
||||
found = true;
|
||||
assert!(entity_ref.get::<TestComponent>().is_none());
|
||||
assert!(entity_ref.get_ref::<TestComponent>().is_none());
|
||||
assert!(matches!(
|
||||
entity_ref.get::<TestComponent2>(),
|
||||
Some(TestComponent2(0))
|
||||
));
|
||||
}
|
||||
|
||||
assert!(found);
|
||||
}
|
||||
|
||||
// Test that a single query can't both contain a mutable reference to a
|
||||
// component C and an `EntityRefExcept` that doesn't include C among its
|
||||
// exclusions.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn entity_ref_except_conflicts_with_self() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
// This should panic, because we have a mutable borrow on
|
||||
// `TestComponent` but have a simultaneous indirect immutable borrow on
|
||||
// that component via `EntityRefExcept`.
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<(&mut TestComponent, EntityRefExcept<TestComponent2>)>) {}
|
||||
}
|
||||
|
||||
// Test that an `EntityRefExcept` that doesn't include a component C among
|
||||
// its exclusions can't coexist with a mutable query for that component.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn entity_ref_except_conflicts_with_other() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
// This should panic, because we have a mutable borrow on
|
||||
// `TestComponent` but have a simultaneous indirect immutable borrow on
|
||||
// that component via `EntityRefExcept`.
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, _: Query<EntityRefExcept<TestComponent2>>) {}
|
||||
}
|
||||
|
||||
// Test that an `EntityRefExcept` with an exception for some component C can
|
||||
// coexist with a query for that component C.
|
||||
#[test]
|
||||
fn entity_ref_except_doesnt_conflict() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, query: Query<EntityRefExcept<TestComponent>>) {
|
||||
for entity_ref in query.iter() {
|
||||
assert!(matches!(
|
||||
entity_ref.get::<TestComponent2>(),
|
||||
Some(TestComponent2(0))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that components can be mutably accessed through an
|
||||
/// `EntityMutExcept`.
|
||||
#[test]
|
||||
fn entity_mut_except() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
let mut query = world.query::<EntityMutExcept<TestComponent>>();
|
||||
|
||||
let mut found = false;
|
||||
for mut entity_mut in query.iter_mut(&mut world) {
|
||||
found = true;
|
||||
assert!(entity_mut.get::<TestComponent>().is_none());
|
||||
assert!(entity_mut.get_ref::<TestComponent>().is_none());
|
||||
assert!(entity_mut.get_mut::<TestComponent>().is_none());
|
||||
assert!(matches!(
|
||||
entity_mut.get::<TestComponent2>(),
|
||||
Some(TestComponent2(0))
|
||||
));
|
||||
}
|
||||
|
||||
assert!(found);
|
||||
}
|
||||
|
||||
// Test that a single query can't both contain a mutable reference to a
|
||||
// component C and an `EntityMutExcept` that doesn't include C among its
|
||||
// exclusions.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn entity_mut_except_conflicts_with_self() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
// This should panic, because we have a mutable borrow on
|
||||
// `TestComponent` but have a simultaneous indirect immutable borrow on
|
||||
// that component via `EntityRefExcept`.
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<(&mut TestComponent, EntityMutExcept<TestComponent2>)>) {}
|
||||
}
|
||||
|
||||
// Test that an `EntityMutExcept` that doesn't include a component C among
|
||||
// its exclusions can't coexist with a query for that component.
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn entity_mut_except_conflicts_with_other() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
// This should panic, because we have a mutable borrow on
|
||||
// `TestComponent` but have a simultaneous indirect immutable borrow on
|
||||
// that component via `EntityRefExcept`.
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, mut query: Query<EntityMutExcept<TestComponent2>>) {
|
||||
for mut entity_mut in query.iter_mut() {
|
||||
assert!(entity_mut
|
||||
.get_mut::<TestComponent2>()
|
||||
.is_some_and(|component| component.0 == 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that an `EntityMutExcept` with an exception for some component C can
|
||||
// coexist with a query for that component C.
|
||||
#[test]
|
||||
fn entity_mut_except_doesnt_conflict() {
|
||||
let mut world = World::new();
|
||||
world.spawn(TestComponent(0)).insert(TestComponent2(0));
|
||||
|
||||
world.run_system_once(system);
|
||||
|
||||
fn system(_: Query<&mut TestComponent>, mut query: Query<EntityMutExcept<TestComponent>>) {
|
||||
for mut entity_mut in query.iter_mut() {
|
||||
assert!(entity_mut
|
||||
.get_mut::<TestComponent2>()
|
||||
.is_some_and(|component| component.0 == 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct A;
|
||||
|
||||
|
@ -3115,4 +3489,24 @@ mod tests {
|
|||
assert!(e.get_mut_by_id(a_id).is_none());
|
||||
assert!(e.get_change_ticks_by_id(a_id).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_components() {
|
||||
#[derive(Component, PartialEq, Eq, Debug)]
|
||||
struct X(usize);
|
||||
|
||||
#[derive(Component, PartialEq, Eq, Debug)]
|
||||
struct Y(usize);
|
||||
let mut world = World::default();
|
||||
let e1 = world.spawn((X(7), Y(10))).id();
|
||||
let e2 = world.spawn(X(8)).id();
|
||||
let e3 = world.spawn_empty().id();
|
||||
|
||||
assert_eq!(
|
||||
Some((&X(7), &Y(10))),
|
||||
world.entity(e1).get_components::<(&X, &Y)>()
|
||||
);
|
||||
assert_eq!(None, world.entity(e2).get_components::<(&X, &Y)>());
|
||||
assert_eq!(None, world.entity(e3).get_components::<(&X, &Y)>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ pub use crate::{
|
|||
pub use component_constants::*;
|
||||
pub use deferred_world::DeferredWorld;
|
||||
pub use entity_ref::{
|
||||
EntityMut, EntityRef, EntityWorldMut, Entry, FilteredEntityMut, FilteredEntityRef,
|
||||
OccupiedEntry, VacantEntry,
|
||||
EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, EntityWorldMut, Entry,
|
||||
FilteredEntityMut, FilteredEntityRef, OccupiedEntry, VacantEntry,
|
||||
};
|
||||
pub use identifier::WorldId;
|
||||
pub use spawn_batch::*;
|
||||
|
@ -61,7 +61,7 @@ use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
|
|||
|
||||
/// A [`World`] mutation.
|
||||
///
|
||||
/// Should be used with [`Commands::add`].
|
||||
/// Should be used with [`Commands::queue`].
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
|
@ -83,7 +83,7 @@ use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
|
|||
/// }
|
||||
///
|
||||
/// fn some_system(mut commands: Commands) {
|
||||
/// commands.add(AddToCounter(42));
|
||||
/// commands.queue(AddToCounter(42));
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Command: Send + 'static {
|
||||
|
@ -162,6 +162,8 @@ impl Drop for World {
|
|||
drop(unsafe { Box::from_raw(self.command_queue.bytes.as_ptr()) });
|
||||
// SAFETY: Pointers in internal command queue are only invalidated here
|
||||
drop(unsafe { Box::from_raw(self.command_queue.cursor.as_ptr()) });
|
||||
// SAFETY: Pointers in internal command queue are only invalidated here
|
||||
drop(unsafe { Box::from_raw(self.command_queue.panic_recovery.as_ptr()) });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2500,7 +2502,7 @@ impl World {
|
|||
/// total += info.layout().size();
|
||||
/// }
|
||||
/// println!("Total size: {} bytes", total);
|
||||
/// # assert_eq!(total, std::mem::size_of::<A>() + std::mem::size_of::<B>());
|
||||
/// # assert_eq!(total, size_of::<A>() + size_of::<B>());
|
||||
/// ```
|
||||
///
|
||||
/// ## Dynamically running closures for resources matching specific `TypeId`s
|
||||
|
|
|
@ -11,8 +11,9 @@ use crate::{
|
|||
entity::{Entities, Entity, EntityLocation},
|
||||
observer::Observers,
|
||||
prelude::Component,
|
||||
query::{DebugCheckedUnwrap, ReadOnlyQueryData},
|
||||
removal_detection::RemovedComponentEvents,
|
||||
storage::{Column, ComponentSparseSet, Storages},
|
||||
storage::{ComponentSparseSet, Storages, Table},
|
||||
system::{Res, Resource},
|
||||
world::RawCommandQueue,
|
||||
};
|
||||
|
@ -247,7 +248,7 @@ impl<'w> UnsafeWorldCell<'w> {
|
|||
}
|
||||
|
||||
/// Retrieves this world's [`Observers`] collection.
|
||||
pub(crate) unsafe fn observers(self) -> &'w Observers {
|
||||
pub(crate) fn observers(self) -> &'w Observers {
|
||||
// SAFETY:
|
||||
// - we only access world metadata
|
||||
&unsafe { self.world_metadata() }.observers
|
||||
|
@ -882,6 +883,55 @@ impl<'w> UnsafeEntityCell<'w> {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns read-only components for the current entity that match the query `Q`,
|
||||
/// or `None` if the entity does not have the components required by the query `Q`.
|
||||
///
|
||||
/// # Safety
|
||||
/// It is the callers responsibility to ensure that
|
||||
/// - the [`UnsafeEntityCell`] has permission to access the queried data immutably
|
||||
/// - no mutable references to the queried data exist at the same time
|
||||
pub(crate) unsafe fn get_components<Q: ReadOnlyQueryData>(&self) -> Option<Q::Item<'w>> {
|
||||
// SAFETY: World is only used to access query data and initialize query state
|
||||
let state = unsafe {
|
||||
let world = self.world().world();
|
||||
Q::get_state(world.components())?
|
||||
};
|
||||
let location = self.location();
|
||||
// SAFETY: Location is guaranteed to exist
|
||||
let archetype = unsafe {
|
||||
self.world
|
||||
.archetypes()
|
||||
.get(location.archetype_id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
if Q::matches_component_set(&state, &|id| archetype.contains(id)) {
|
||||
// SAFETY: state was initialized above using the world passed into this function
|
||||
let mut fetch = unsafe {
|
||||
Q::init_fetch(
|
||||
self.world,
|
||||
&state,
|
||||
self.world.last_change_tick(),
|
||||
self.world.change_tick(),
|
||||
)
|
||||
};
|
||||
// SAFETY: Table is guaranteed to exist
|
||||
let table = unsafe {
|
||||
self.world
|
||||
.storages()
|
||||
.tables
|
||||
.get(location.table_id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
// SAFETY: Archetype and table are from the same world used to initialize state and fetch.
|
||||
// Table corresponds to archetype. State is the same state used to init fetch above.
|
||||
unsafe { Q::set_archetype(&mut fetch, &state, archetype, table) }
|
||||
// SAFETY: Called after set_archetype above. Entity and location are guaranteed to exist.
|
||||
unsafe { Some(Q::fetch(&mut fetch, self.id(), location.table_row)) }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w> UnsafeEntityCell<'w> {
|
||||
|
@ -952,22 +1002,18 @@ impl<'w> UnsafeEntityCell<'w> {
|
|||
|
||||
impl<'w> UnsafeWorldCell<'w> {
|
||||
#[inline]
|
||||
/// # Safety:
|
||||
/// - the returned `Column` is only used in ways that this [`UnsafeWorldCell`] has permission for.
|
||||
/// - the returned `Column` is only used in ways that would not conflict with any existing
|
||||
/// borrows of world data.
|
||||
unsafe fn fetch_table(
|
||||
self,
|
||||
location: EntityLocation,
|
||||
component_id: ComponentId,
|
||||
) -> Option<&'w Column> {
|
||||
// SAFETY: caller ensures returned data is not misused and we have not created any borrows
|
||||
// of component/resource data
|
||||
unsafe { self.storages() }.tables[location.table_id].get_column(component_id)
|
||||
/// # Safety
|
||||
/// - the returned `Table` is only used in ways that this [`UnsafeWorldCell`] has permission for.
|
||||
/// - the returned `Table` is only used in ways that would not conflict with any existing borrows of world data.
|
||||
unsafe fn fetch_table(self, location: EntityLocation) -> Option<&'w Table> {
|
||||
// SAFETY:
|
||||
// - caller ensures returned data is not misused and we have not created any borrows of component/resource data
|
||||
// - `location` contains a valid `TableId`, so getting the table won't fail
|
||||
unsafe { self.storages().tables.get(location.table_id) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// # Safety:
|
||||
/// # Safety
|
||||
/// - the returned `ComponentSparseSet` is only used in ways that this [`UnsafeWorldCell`] has permission for.
|
||||
/// - the returned `ComponentSparseSet` is only used in ways that would not conflict with any existing
|
||||
/// borrows of world data.
|
||||
|
@ -998,9 +1044,9 @@ unsafe fn get_component(
|
|||
// SAFETY: component_id exists and is therefore valid
|
||||
match storage_type {
|
||||
StorageType::Table => {
|
||||
let components = world.fetch_table(location, component_id)?;
|
||||
let table = world.fetch_table(location)?;
|
||||
// SAFETY: archetypes only store valid table_rows and caller ensure aliasing rules
|
||||
Some(components.get_data_unchecked(location.table_row))
|
||||
table.get_component(component_id, location.table_row)
|
||||
}
|
||||
StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get(entity),
|
||||
}
|
||||
|
@ -1024,17 +1070,23 @@ unsafe fn get_component_and_ticks(
|
|||
) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> {
|
||||
match storage_type {
|
||||
StorageType::Table => {
|
||||
let components = world.fetch_table(location, component_id)?;
|
||||
let table = world.fetch_table(location)?;
|
||||
|
||||
// SAFETY: archetypes only store valid table_rows and caller ensure aliasing rules
|
||||
Some((
|
||||
components.get_data_unchecked(location.table_row),
|
||||
table.get_component(component_id, location.table_row)?,
|
||||
TickCells {
|
||||
added: components.get_added_tick_unchecked(location.table_row),
|
||||
changed: components.get_changed_tick_unchecked(location.table_row),
|
||||
added: table
|
||||
.get_added_tick(component_id, location.table_row)
|
||||
.debug_checked_unwrap(),
|
||||
changed: table
|
||||
.get_changed_tick(component_id, location.table_row)
|
||||
.debug_checked_unwrap(),
|
||||
},
|
||||
#[cfg(feature = "track_change_detection")]
|
||||
components.get_changed_by_unchecked(location.table_row),
|
||||
table
|
||||
.get_changed_by(component_id, location.table_row)
|
||||
.debug_checked_unwrap(),
|
||||
#[cfg(not(feature = "track_change_detection"))]
|
||||
(),
|
||||
))
|
||||
|
@ -1062,9 +1114,9 @@ unsafe fn get_ticks(
|
|||
) -> Option<ComponentTicks> {
|
||||
match storage_type {
|
||||
StorageType::Table => {
|
||||
let components = world.fetch_table(location, component_id)?;
|
||||
let table = world.fetch_table(location)?;
|
||||
// SAFETY: archetypes only store valid table_rows and caller ensure aliasing rules
|
||||
Some(components.get_ticks_unchecked(location.table_row))
|
||||
table.get_ticks_unchecked(component_id, location.table_row)
|
||||
}
|
||||
StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_ticks(entity),
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" }
|
|||
bevy_time = { path = "../bevy_time", version = "0.15.0-dev" }
|
||||
|
||||
# other
|
||||
gilrs = "0.10.1"
|
||||
gilrs = "0.11.0"
|
||||
thiserror = "1.0"
|
||||
|
||||
[lints]
|
||||
|
|
|
@ -64,7 +64,7 @@ pub struct AabbGizmoConfigGroup {
|
|||
|
||||
/// Add this [`Component`] to an entity to draw its [`Aabb`] component.
|
||||
#[derive(Component, Reflect, Default, Debug)]
|
||||
#[reflect(Component, Default)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct ShowAabbGizmo {
|
||||
/// The color of the box.
|
||||
///
|
||||
|
|
|
@ -114,8 +114,7 @@ fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator<I
|
|||
(0..=resolution)
|
||||
.map(move |n| arc_angle * n as f32 / resolution as f32)
|
||||
.map(|angle| angle + FRAC_PI_2)
|
||||
.map(f32::sin_cos)
|
||||
.map(|(sin, cos)| Vec2::new(cos, sin))
|
||||
.map(Vec2::from_angle)
|
||||
.map(move |vec2| vec2 * radius)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
use crate::prelude::{GizmoConfigGroup, Gizmos};
|
||||
use bevy_color::Color;
|
||||
use bevy_math::{Isometry2d, Isometry3d};
|
||||
use bevy_math::{ops, Isometry2d, Isometry3d};
|
||||
use bevy_math::{Quat, Vec2, Vec3};
|
||||
use std::f32::consts::TAU;
|
||||
|
||||
|
@ -14,7 +14,7 @@ pub(crate) const DEFAULT_CIRCLE_RESOLUTION: u32 = 32;
|
|||
fn ellipse_inner(half_size: Vec2, resolution: u32) -> impl Iterator<Item = Vec2> {
|
||||
(0..resolution + 1).map(move |i| {
|
||||
let angle = i as f32 * TAU / resolution as f32;
|
||||
let (x, y) = angle.sin_cos();
|
||||
let (x, y) = ops::sin_cos(angle);
|
||||
Vec2::new(x, y) * half_size
|
||||
})
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue