Merge branch 'main' into ui-stack-system-walk

This commit is contained in:
ickshonpe 2024-09-20 01:55:25 +01:00
commit eae57fef91
402 changed files with 13986 additions and 6614 deletions

View file

@ -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: |

View file

@ -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"

View file

@ -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"

View file

@ -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
View file

@ -6,6 +6,7 @@ Cargo.lock
.cargo/config.toml
/.idea
/.vscode
.zed
/benches/target
/tools/compile_fail_utils/target
dxcompiler.dll

View file

@ -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"

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

View file

@ -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;

View 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>)>();
}
}
}

View file

@ -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));

View file

@ -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);
});

View file

@ -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" },
]

View file

@ -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

View file

@ -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

View file

@ -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},

View file

@ -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!(

View file

@ -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()
}

View file

@ -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>(

View file

@ -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(())
});
}

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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.

View file

@ -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)
}
}

View file

@ -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

View file

@ -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
};

View file

@ -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

View file

@ -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_;

View file

@ -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)
}

View file

@ -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
}
}
}

View file

@ -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,

View file

@ -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(),
});
}
}

View file

@ -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 }),

View file

@ -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,

View file

@ -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,

View file

@ -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
}

View file

@ -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,
}

View file

@ -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,
},
);

View file

@ -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 {

View file

@ -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));

View file

@ -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)
///

View file

@ -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.

View file

@ -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 {

View file

@ -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,

View file

@ -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,
};
}
}

View file

@ -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.
}

View file

@ -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)]

View file

@ -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)) = (

View file

@ -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 {

View file

@ -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,

View file

@ -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;

View file

@ -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,
});
}
}

View file

@ -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,

View file

@ -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"

View file

@ -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)*;

View file

@ -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`].

View file

@ -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)

View file

@ -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 {

View file

@ -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() {

View file

@ -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`.

View file

@ -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);
/// });
/// }

View file

@ -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

View file

@ -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);
}
}

View file

@ -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()
});
}
}
});
}

View file

@ -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.

View file

@ -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)

View file

@ -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"))]
(),
));

View file

@ -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)]

View file

@ -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,

View file

@ -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;

View file

@ -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)
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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
}

View 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);
}
}

View file

@ -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.

View file

@ -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::*;

View file

@ -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

View 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())
}
}

View 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);
}
}

View 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,
}
}
}

View file

@ -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();

View file

@ -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]

View file

@ -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.

View file

@ -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();
}
}

View file

@ -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]);
}

View file

@ -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)>());
}
}

View file

@ -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

View file

@ -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),
}

View file

@ -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]

View file

@ -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.
///

View file

@ -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)
}

View file

@ -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