Merge branch 'main' into issue18981

This commit is contained in:
Lucas Farias 2025-05-20 09:19:00 -03:00
commit 5192ce3201
318 changed files with 8185 additions and 5200 deletions

View File

@ -13,8 +13,9 @@ env:
CARGO_PROFILE_TEST_DEBUG: 0
CARGO_PROFILE_DEV_DEBUG: 0
# If nightly is breaking CI, modify this variable to target a specific nightly version.
NIGHTLY_TOOLCHAIN: nightly
NIGHTLY_TOOLCHAIN: nightly-2025-05-16 # pinned until a fix for https://github.com/rust-lang/miri/issues/4323 is released
RUSTFLAGS: "-D warnings"
BINSTALL_VERSION: "v1.12.3"
concurrency:
group: ${{github.workflow}}-${{github.ref}}
@ -271,9 +272,9 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: cargo-bins/cargo-binstall@v1.12.3
- name: Install taplo
run: cargo install taplo-cli --locked
run: cargo binstall taplo-cli@0.9.3 --locked
- name: Run Taplo
id: taplo
run: taplo fmt --check --diff
@ -292,7 +293,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.31.1
uses: crate-ci/typos@v1.32.0
- name: Typos info
if: failure()
run: |

View File

@ -291,9 +291,6 @@ bevy_log = ["bevy_internal/bevy_log"]
# Enable input focus subsystem
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
# Use the configurable global error handler as the default error handler.
configurable_error_handler = ["bevy_internal/configurable_error_handler"]
# Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation)
spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
@ -2215,7 +2212,6 @@ wasm = false
name = "fallible_params"
path = "examples/ecs/fallible_params.rs"
doc-scrape-examples = true
required-features = ["configurable_error_handler"]
[package.metadata.example.fallible_params]
name = "Fallible System Parameters"
@ -2227,7 +2223,7 @@ wasm = false
name = "error_handling"
path = "examples/ecs/error_handling.rs"
doc-scrape-examples = true
required-features = ["bevy_mesh_picking_backend", "configurable_error_handler"]
required-features = ["bevy_mesh_picking_backend"]
[package.metadata.example.error_handling]
name = "Error handling"
@ -2305,6 +2301,17 @@ description = "Pipe the output of one system into a second, allowing you to hand
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "state_scoped"
path = "examples/ecs/state_scoped.rs"
doc-scrape-examples = true
[package.metadata.example.state_scoped]
name = "State Scoped"
description = "Shows how to spawn entities that are automatically despawned either when entering or exiting specific game states."
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "system_closure"
path = "examples/ecs/system_closure.rs"
@ -3344,6 +3351,17 @@ description = "Illustrates creating and updating text"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "text_background_colors"
path = "examples/ui/text_background_colors.rs"
doc-scrape-examples = true
[package.metadata.example.text_background_colors]
name = "Text Background Colors"
description = "Demonstrates text background colors"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "text_debug"
path = "examples/ui/text_debug.rs"
@ -3499,6 +3517,17 @@ description = "An example for debugging viewport coordinates"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "viewport_node"
path = "examples/ui/viewport_node.rs"
doc-scrape-examples = true
[package.metadata.example.viewport_node]
name = "Viewport Node"
description = "Demonstrates how to create a viewport node with picking support"
category = "UI (User Interface)"
wasm = true
# Window
[[example]]
name = "clear_color"

View File

@ -5,7 +5,7 @@
),
},
entities: {
4294967296: (
4294967297: (
components: {
"bevy_ecs::name::Name": "joe",
"bevy_transform::components::global_transform::GlobalTransform": ((1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)),
@ -23,7 +23,7 @@
),
},
),
4294967297: (
4294967298: (
components: {
"scene::ComponentA": (
x: 3.0,
@ -32,4 +32,4 @@
},
),
},
)
)

View File

@ -32,6 +32,7 @@ bevy_platform = { path = "../crates/bevy_platform", default-features = false, fe
glam = "0.29"
rand = "0.8"
rand_chacha = "0.3"
nonmax = { version = "0.5", default-features = false }
# Make `bevy_render` compile on Linux with x11 windowing. x11 vs. Wayland does not matter here
# because the benches do not actually open any windows.

View File

@ -51,8 +51,7 @@ fn add_archetypes(world: &mut World, count: u16) {
pub fn no_archetypes(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("no_archetypes");
for i in 0..=5 {
let system_count = i * 20;
for system_count in [0, 10, 100] {
let (mut world, mut schedule) = setup(system_count);
group.bench_with_input(
BenchmarkId::new("system_count", system_count),
@ -69,7 +68,7 @@ pub fn no_archetypes(criterion: &mut Criterion) {
pub fn added_archetypes(criterion: &mut Criterion) {
const SYSTEM_COUNT: usize = 100;
let mut group = criterion.benchmark_group("added_archetypes");
for archetype_count in [100, 200, 500, 1000, 2000, 5000, 10000] {
for archetype_count in [100, 1_000, 10_000] {
group.bench_with_input(
BenchmarkId::new("archetype_count", archetype_count),
&archetype_count,

View File

@ -155,7 +155,7 @@ fn add_archetypes(world: &mut World, count: u16) {
fn empty_archetypes(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("empty_archetypes");
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
for archetype_count in [10, 100, 1_000, 10_000] {
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_systems(iter);
});
@ -186,7 +186,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
},
);
}
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
for archetype_count in [10, 100, 1_000, 10_000] {
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_systems(for_each);
});
@ -217,7 +217,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
},
);
}
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
for archetype_count in [10, 100, 1_000, 10_000] {
let (mut world, mut schedule) = setup(true, |schedule| {
schedule.add_systems(par_for_each);
});

View File

@ -9,19 +9,19 @@ fn send(c: &mut Criterion) {
let mut group = c.benchmark_group("events_send");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for count in [100, 1000, 10000, 50000] {
for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_4_events_{}", count), |b| {
let mut bench = send::Benchmark::<4>::new(count);
b.iter(move || bench.run());
});
}
for count in [100, 1000, 10000, 50000] {
for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_16_events_{}", count), |b| {
let mut bench = send::Benchmark::<16>::new(count);
b.iter(move || bench.run());
});
}
for count in [100, 1000, 10000, 50000] {
for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_512_events_{}", count), |b| {
let mut bench = send::Benchmark::<512>::new(count);
b.iter(move || bench.run());
@ -34,19 +34,19 @@ fn iter(c: &mut Criterion) {
let mut group = c.benchmark_group("events_iter");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for count in [100, 1000, 10000, 50000] {
for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_4_events_{}", count), |b| {
let mut bench = iter::Benchmark::<4>::new(count);
b.iter(move || bench.run());
});
}
for count in [100, 1000, 10000, 50000] {
for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_16_events_{}", count), |b| {
let mut bench = iter::Benchmark::<4>::new(count);
b.iter(move || bench.run());
});
}
for count in [100, 1000, 10000, 50000] {
for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_512_events_{}", count), |b| {
let mut bench = iter::Benchmark::<512>::new(count);
b.iter(move || bench.run());

View File

@ -17,15 +17,14 @@ pub fn run_condition_yes(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(3));
fn empty() {}
for amount in 0..21 {
for amount in [10, 100, 1_000] {
let mut schedule = Schedule::default();
schedule.add_systems(empty.run_if(yes));
for _ in 0..amount {
for _ in 0..(amount / 5) {
schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(yes));
}
// run once to initialize systems
schedule.run(&mut world);
group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| {
group.bench_function(format!("{}_systems", amount), |bencher| {
bencher.iter(|| {
schedule.run(&mut world);
});
@ -40,15 +39,14 @@ pub fn run_condition_no(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(3));
fn empty() {}
for amount in 0..21 {
for amount in [10, 100, 1_000] {
let mut schedule = Schedule::default();
schedule.add_systems(empty.run_if(no));
for _ in 0..amount {
for _ in 0..(amount / 5) {
schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(no));
}
// run once to initialize systems
schedule.run(&mut world);
group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| {
group.bench_function(format!("{}_systems", amount), |bencher| {
bencher.iter(|| {
schedule.run(&mut world);
});
@ -70,17 +68,16 @@ pub fn run_condition_yes_with_query(criterion: &mut Criterion) {
fn yes_with_query(query: Single<&TestBool>) -> bool {
query.0
}
for amount in 0..21 {
for amount in [10, 100, 1_000] {
let mut schedule = Schedule::default();
schedule.add_systems(empty.run_if(yes_with_query));
for _ in 0..amount {
for _ in 0..(amount / 5) {
schedule.add_systems(
(empty, empty, empty, empty, empty).distributive_run_if(yes_with_query),
);
}
// run once to initialize systems
schedule.run(&mut world);
group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| {
group.bench_function(format!("{}_systems", amount), |bencher| {
bencher.iter(|| {
schedule.run(&mut world);
});
@ -99,17 +96,16 @@ pub fn run_condition_yes_with_resource(criterion: &mut Criterion) {
fn yes_with_resource(res: Res<TestBool>) -> bool {
res.0
}
for amount in 0..21 {
for amount in [10, 100, 1_000] {
let mut schedule = Schedule::default();
schedule.add_systems(empty.run_if(yes_with_resource));
for _ in 0..amount {
for _ in 0..(amount / 5) {
schedule.add_systems(
(empty, empty, empty, empty, empty).distributive_run_if(yes_with_resource),
);
}
// run once to initialize systems
schedule.run(&mut world);
group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| {
group.bench_function(format!("{}_systems", amount), |bencher| {
bencher.iter(|| {
schedule.run(&mut world);
});

View File

@ -20,25 +20,25 @@ pub fn empty_systems(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(3));
fn empty() {}
for amount in 0..5 {
for amount in [0, 2, 4] {
let mut schedule = Schedule::default();
for _ in 0..amount {
schedule.add_systems(empty);
}
schedule.run(&mut world);
group.bench_function(format!("{:03}_systems", amount), |bencher| {
group.bench_function(format!("{}_systems", amount), |bencher| {
bencher.iter(|| {
schedule.run(&mut world);
});
});
}
for amount in 1..21 {
for amount in [10, 100, 1_000] {
let mut schedule = Schedule::default();
for _ in 0..amount {
for _ in 0..(amount / 5) {
schedule.add_systems((empty, empty, empty, empty, empty));
}
schedule.run(&mut world);
group.bench_function(format!("{:03}_systems", 5 * amount), |bencher| {
group.bench_function(format!("{}_systems", amount), |bencher| {
bencher.iter(|| {
schedule.run(&mut world);
});
@ -67,23 +67,21 @@ pub fn busy_systems(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("busy_systems");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(3));
for entity_bunches in 1..6 {
for entity_bunches in [1, 3, 5] {
world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0))));
world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0))));
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0))));
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0))));
for system_amount in 0..5 {
for system_amount in [3, 9, 15] {
let mut schedule = Schedule::default();
schedule.add_systems((ab, cd, ce));
for _ in 0..system_amount {
for _ in 0..(system_amount / 3) {
schedule.add_systems((ab, cd, ce));
}
schedule.run(&mut world);
group.bench_function(
format!(
"{:02}x_entities_{:02}_systems",
entity_bunches,
3 * system_amount + 3
entity_bunches, system_amount
),
|bencher| {
bencher.iter(|| {
@ -119,22 +117,20 @@ pub fn contrived(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("contrived");
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(3));
for entity_bunches in 1..6 {
for entity_bunches in [1, 3, 5] {
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0))));
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0))));
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (C(0.0), D(0.0))));
for system_amount in 0..5 {
for system_amount in [3, 9, 15] {
let mut schedule = Schedule::default();
schedule.add_systems((s_0, s_1, s_2));
for _ in 0..system_amount {
for _ in 0..(system_amount / 3) {
schedule.add_systems((s_0, s_1, s_2));
}
schedule.run(&mut world);
group.bench_function(
format!(
"{:02}x_entities_{:02}_systems",
entity_bunches,
3 * system_amount + 3
entity_bunches, system_amount
),
|bencher| {
bencher.iter(|| {

View File

@ -137,6 +137,7 @@ pub fn empty_schedule_run(criterion: &mut Criterion) {
});
let mut schedule = Schedule::default();
#[expect(deprecated, reason = "We still need to test/bench this.")]
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::Simple);
group.bench_function("Simple", |bencher| {
bencher.iter(|| schedule.run(app.world_mut()));

View File

@ -36,7 +36,7 @@ pub fn spawn_commands(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for entity_count in (1..5).map(|i| i * 2 * 1000) {
for entity_count in [100, 1_000, 10_000] {
group.bench_function(format!("{}_entities", entity_count), |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
@ -92,28 +92,6 @@ pub fn insert_commands(criterion: &mut Criterion) {
command_queue.apply(&mut world);
});
});
group.bench_function("insert_or_spawn_batch", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
let mut entities = Vec::new();
for _ in 0..entity_count {
entities.push(world.spawn_empty().id());
}
bencher.iter(|| {
let mut commands = Commands::new(&mut command_queue, &world);
let mut values = Vec::with_capacity(entity_count);
for entity in &entities {
values.push((*entity, (Matrix::default(), Vec3::default())));
}
#[expect(
deprecated,
reason = "This needs to be supported for now, and therefore still needs the benchmark."
)]
commands.insert_or_spawn_batch(values);
command_queue.apply(&mut world);
});
});
group.bench_function("insert_batch", |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
@ -158,7 +136,7 @@ pub fn fake_commands(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for command_count in (1..5).map(|i| i * 2 * 1000) {
for command_count in [100, 1_000, 10_000] {
group.bench_function(format!("{}_commands", command_count), |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();
@ -203,7 +181,7 @@ pub fn sized_commands_impl<T: Default + Command>(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for command_count in (1..5).map(|i| i * 2 * 1000) {
for command_count in [100, 1_000, 10_000] {
group.bench_function(format!("{}_commands", command_count), |bencher| {
let mut world = World::default();
let mut command_queue = CommandQueue::default();

View File

@ -12,7 +12,7 @@ pub fn world_despawn(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for entity_count in (0..5).map(|i| 10_u32.pow(i)) {
for entity_count in [1, 100, 10_000] {
group.bench_function(format!("{}_entities", entity_count), |bencher| {
bencher.iter_batched_ref(
|| {

View File

@ -12,7 +12,7 @@ pub fn world_despawn_recursive(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for entity_count in (0..5).map(|i| 10_u32.pow(i)) {
for entity_count in [1, 100, 10_000] {
group.bench_function(format!("{}_entities", entity_count), |bencher| {
bencher.iter_batched_ref(
|| {

View File

@ -1,4 +1,4 @@
use bevy_ecs::entity::{Entity, EntityHashSet};
use bevy_ecs::entity::{Entity, EntityGeneration, EntityHashSet};
use criterion::{BenchmarkId, Criterion, Throughput};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
@ -17,10 +17,14 @@ fn make_entity(rng: &mut impl Rng, size: usize) -> Entity {
let generation = 1.0 + -(1.0 - x).log2() * 2.0;
// this is not reliable, but we're internal so a hack is ok
let bits = ((generation as u64) << 32) | (id as u64);
let id = id as u64 + 1;
let bits = ((generation as u64) << 32) | id;
let e = Entity::from_bits(bits);
assert_eq!(e.index(), id as u32);
assert_eq!(e.generation(), generation as u32);
assert_eq!(e.index(), !(id as u32));
assert_eq!(
e.generation(),
EntityGeneration::FIRST.after_versions(generation as u32)
);
e
}

View File

@ -12,7 +12,7 @@ pub fn world_spawn(criterion: &mut Criterion) {
group.warm_up_time(core::time::Duration::from_millis(500));
group.measurement_time(core::time::Duration::from_secs(4));
for entity_count in (0..5).map(|i| 10_u32.pow(i)) {
for entity_count in [1, 100, 10_000] {
group.bench_function(format!("{}_entities", entity_count), |bencher| {
let mut world = World::default();
bencher.iter(|| {

View File

@ -1,9 +1,10 @@
use core::hint::black_box;
use nonmax::NonMaxU32;
use bevy_ecs::{
bundle::{Bundle, NoBundleEffect},
component::Component,
entity::Entity,
entity::{Entity, EntityRow},
system::{Query, SystemState},
world::World,
};
@ -53,7 +54,9 @@ pub fn world_entity(criterion: &mut Criterion) {
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
let entity =
// SAFETY: Range is exclusive.
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
black_box(world.entity(entity));
}
});
@ -74,7 +77,9 @@ pub fn world_get(criterion: &mut Criterion) {
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
let entity =
// SAFETY: Range is exclusive.
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
assert!(world.get::<Table>(entity).is_some());
}
});
@ -84,7 +89,9 @@ pub fn world_get(criterion: &mut Criterion) {
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
let entity =
// SAFETY: Range is exclusive.
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
assert!(world.get::<Sparse>(entity).is_some());
}
});
@ -106,7 +113,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
let entity =
// SAFETY: Range is exclusive.
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
assert!(query.get(&world, entity).is_ok());
}
});
@ -131,7 +140,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
let entity =
// SAFETY: Range is exclusive.
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
assert!(query.get(&world, entity).is_ok());
}
});
@ -142,7 +153,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
let entity =
// SAFETY: Range is exclusive.
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
assert!(query.get(&world, entity).is_ok());
}
});
@ -169,7 +182,10 @@ pub fn world_query_get(criterion: &mut Criterion) {
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::from_raw(i);
// SAFETY: Range is exclusive.
let entity = Entity::from_raw(EntityRow::new(unsafe {
NonMaxU32::new_unchecked(i)
}));
assert!(query.get(&world, entity).is_ok());
}
});

View File

@ -37,7 +37,7 @@ fn create_mesh(vertices_per_side: u32) -> SimpleMesh {
for p in 0..vertices_per_side.pow(2) {
let (x, z) = p_to_xz_norm(p, vertices_per_side);
// Push a new vertice to the mesh. We translate all vertices so the final square is
// Push a new vertex to the mesh. We translate all vertices so the final square is
// centered at (0, 0), instead of (0.5, 0.5).
positions.push([x - 0.5, 0.0, z - 0.5]);

View File

@ -41,7 +41,6 @@ disallowed-methods = [
{ 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" },
{ path = "criterion::black_box", reason = "use core::hint::black_box instead" },
]
# Require `bevy_ecs::children!` to use `[]` braces, instead of `()` or `{}`.

View File

@ -137,11 +137,15 @@ impl From<Node> for AccessibilityNode {
all(feature = "bevy_reflect", feature = "serialize"),
reflect(Serialize, Deserialize, Clone)
)]
pub enum AccessibilitySystem {
pub enum AccessibilitySystems {
/// Update the accessibility tree
Update,
}
/// Deprecated alias for [`AccessibilitySystems`].
#[deprecated(since = "0.17.0", note = "Renamed to `AccessibilitySystems`.")]
pub type AccessibilitySystem = AccessibilitySystems;
/// Plugin managing non-GUI aspects of integrating with accessibility APIs.
#[derive(Default)]
pub struct AccessibilityPlugin;

View File

@ -31,14 +31,14 @@ use crate::{
prelude::EvaluatorId,
};
use bevy_app::{Animation, App, Plugin, PostUpdate};
use bevy_asset::{Asset, AssetApp, AssetEvents, Assets};
use bevy_app::{AnimationSystems, App, Plugin, PostUpdate};
use bevy_asset::{Asset, AssetApp, AssetEventSystems, Assets};
use bevy_ecs::{prelude::*, world::EntityMutExcept};
use bevy_math::FloatOrd;
use bevy_platform::{collections::HashMap, hash::NoOpHash};
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
use bevy_time::Time;
use bevy_transform::TransformSystem;
use bevy_transform::TransformSystems;
use bevy_utils::{PreHashMap, PreHashMapExt, TypeIdMap};
use petgraph::graph::NodeIndex;
use serde::{Deserialize, Serialize};
@ -1244,7 +1244,7 @@ impl Plugin for AnimationPlugin {
.add_systems(
PostUpdate,
(
graph::thread_animation_graphs.before(AssetEvents),
graph::thread_animation_graphs.before(AssetEventSystems),
advance_transitions,
advance_animations,
// TODO: `animate_targets` can animate anything, so
@ -1260,8 +1260,8 @@ impl Plugin for AnimationPlugin {
expire_completed_transitions,
)
.chain()
.in_set(Animation)
.before(TransformSystem::TransformPropagate),
.in_set(AnimationSystems)
.before(TransformSystems::Propagate),
);
}
}

View File

@ -18,7 +18,7 @@ use bevy_render::{
},
renderer::RenderDevice,
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
mod node;
@ -121,7 +121,7 @@ impl Plugin for CasPlugin {
};
render_app
.init_resource::<SpecializedRenderPipelines<CasPipeline>>()
.add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare));
.add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare));
{
render_app

View File

@ -18,7 +18,7 @@ use bevy_render::{
},
renderer::RenderDevice,
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use bevy_utils::default;
@ -96,7 +96,10 @@ impl Plugin for FxaaPlugin {
};
render_app
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
.add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare))
.add_systems(
Render,
prepare_fxaa_pipelines.in_set(RenderSystems::Prepare),
)
.add_render_graph_node::<ViewNodeRunner<FxaaNode>>(Core3d, Node3d::Fxaa)
.add_render_graph_edges(
Core3d,

View File

@ -76,7 +76,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::{CachedTexture, GpuImage, TextureCache},
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use bevy_utils::prelude::default;
@ -346,10 +346,10 @@ impl Plugin for SmaaPlugin {
.add_systems(
Render,
(
prepare_smaa_pipelines.in_set(RenderSet::Prepare),
prepare_smaa_uniforms.in_set(RenderSet::PrepareResources),
prepare_smaa_textures.in_set(RenderSet::PrepareResources),
prepare_smaa_bind_groups.in_set(RenderSet::PrepareBindGroups),
prepare_smaa_pipelines.in_set(RenderSystems::Prepare),
prepare_smaa_uniforms.in_set(RenderSystems::PrepareResources),
prepare_smaa_textures.in_set(RenderSystems::PrepareResources),
prepare_smaa_bind_groups.in_set(RenderSystems::PrepareBindGroups),
),
)
.add_render_graph_node::<ViewNodeRunner<SmaaNode>>(Core3d, Node3d::Smaa)

View File

@ -36,7 +36,7 @@ use bevy_render::{
sync_world::RenderEntity,
texture::{CachedTexture, TextureCache},
view::{ExtractedView, Msaa, ViewTarget},
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems,
};
use tracing::warn;
@ -64,9 +64,9 @@ impl Plugin for TemporalAntiAliasPlugin {
.add_systems(
Render,
(
prepare_taa_jitter_and_mip_bias.in_set(RenderSet::ManageViews),
prepare_taa_pipelines.in_set(RenderSet::Prepare),
prepare_taa_history_textures.in_set(RenderSet::PrepareResources),
prepare_taa_jitter_and_mip_bias.in_set(RenderSystems::ManageViews),
prepare_taa_pipelines.in_set(RenderSystems::Prepare),
prepare_taa_history_textures.in_set(RenderSystems::PrepareResources),
),
)
.add_render_graph_node::<ViewNodeRunner<TemporalAntiAliasNode>>(Core3d, Node3d::Taa)

View File

@ -10,6 +10,7 @@ use alloc::{
pub use bevy_derive::AppLabel;
use bevy_ecs::{
component::RequiredComponentsError,
error::{DefaultErrorHandler, ErrorHandler},
event::{event_update_system, EventCursor},
intern::Interned,
prelude::*,
@ -85,6 +86,7 @@ pub struct App {
/// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html
/// [`ScheduleRunnerPlugin`]: https://docs.rs/bevy/latest/bevy/app/struct.ScheduleRunnerPlugin.html
pub(crate) runner: RunnerFn,
default_error_handler: Option<ErrorHandler>,
}
impl Debug for App {
@ -117,7 +119,7 @@ impl Default for App {
app.add_systems(
First,
event_update_system
.in_set(bevy_ecs::event::EventUpdates)
.in_set(bevy_ecs::event::EventUpdateSystems)
.run_if(bevy_ecs::event::event_update_condition),
);
app.add_event::<AppExit>();
@ -143,6 +145,7 @@ impl App {
sub_apps: HashMap::default(),
},
runner: Box::new(run_once),
default_error_handler: None,
}
}
@ -1115,7 +1118,12 @@ impl App {
}
/// Inserts a [`SubApp`] with the given label.
pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) {
pub fn insert_sub_app(&mut self, label: impl AppLabel, mut sub_app: SubApp) {
if let Some(handler) = self.default_error_handler {
sub_app
.world_mut()
.get_resource_or_insert_with(|| DefaultErrorHandler(handler));
}
self.sub_apps.sub_apps.insert(label.intern(), sub_app);
}
@ -1334,6 +1342,49 @@ impl App {
self.world_mut().add_observer(observer);
self
}
/// Gets the error handler to set for new supapps.
///
/// Note that the error handler of existing subapps may differ.
pub fn get_error_handler(&self) -> Option<ErrorHandler> {
self.default_error_handler
}
/// Set the [default error handler] for the all subapps (including the main one and future ones)
/// that do not have one.
///
/// May only be called once and should be set by the application, not by libraries.
///
/// The handler will be called when an error is produced and not otherwise handled.
///
/// # Panics
/// Panics if called multiple times.
///
/// # Example
/// ```
/// # use bevy_app::*;
/// # use bevy_ecs::error::warn;
/// # fn MyPlugins(_: &mut App) {}
/// App::new()
/// .set_error_handler(warn)
/// .add_plugins(MyPlugins)
/// .run();
/// ```
///
/// [default error handler]: bevy_ecs::error::DefaultErrorHandler
pub fn set_error_handler(&mut self, handler: ErrorHandler) -> &mut Self {
assert!(
self.default_error_handler.is_none(),
"`set_error_handler` called multiple times on same `App`"
);
self.default_error_handler = Some(handler);
for sub_app in self.sub_apps.iter_mut() {
sub_app
.world_mut()
.get_resource_or_insert_with(|| DefaultErrorHandler(handler));
}
self
}
}
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;

View File

@ -55,7 +55,7 @@ pub mod prelude {
main_schedule::{
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
PostStartup, PostUpdate, PreStartup, PreUpdate, RunFixedMainLoop,
RunFixedMainLoopSystem, SpawnScene, Startup, Update,
RunFixedMainLoopSystems, SpawnScene, Startup, Update,
},
sub_app::SubApp,
Plugin, PluginGroup, TaskPoolOptions, TaskPoolPlugin,

View File

@ -92,8 +92,8 @@ pub struct PreUpdate;
/// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
///
/// If you need to order your variable timestep systems
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
/// If you need to order your variable timestep systems before or after
/// the fixed update logic, use the [`RunFixedMainLoopSystems`] system set.
///
/// Note that in contrast to most other Bevy schedules, systems added directly to
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
@ -149,7 +149,7 @@ pub struct FixedLast;
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
///
/// This is run by the [`RunFixedMainLoop`] schedule. If you need to order your variable timestep systems
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystems`] system set.
///
/// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default.
/// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs).
@ -196,7 +196,11 @@ pub struct Last;
/// Animation system set. This exists in [`PostUpdate`].
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub struct Animation;
pub struct AnimationSystems;
/// Deprecated alias for [`AnimationSystems`].
#[deprecated(since = "0.17.0", note = "Renamed to `AnimationSystems`.")]
pub type Animation = AnimationSystems;
/// Defines the schedules to be run for the [`Main`] schedule, including
/// their order.
@ -318,9 +322,9 @@ impl Plugin for MainSchedulePlugin {
.configure_sets(
RunFixedMainLoop,
(
RunFixedMainLoopSystem::BeforeFixedMainLoop,
RunFixedMainLoopSystem::FixedMainLoop,
RunFixedMainLoopSystem::AfterFixedMainLoop,
RunFixedMainLoopSystems::BeforeFixedMainLoop,
RunFixedMainLoopSystems::FixedMainLoop,
RunFixedMainLoopSystems::AfterFixedMainLoop,
)
.chain(),
);
@ -400,7 +404,7 @@ impl FixedMain {
/// Note that in contrast to most other Bevy schedules, systems added directly to
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone, SystemSet)]
pub enum RunFixedMainLoopSystem {
pub enum RunFixedMainLoopSystems {
/// Runs before the fixed update logic.
///
/// A good example of a system that fits here
@ -419,7 +423,7 @@ pub enum RunFixedMainLoopSystem {
/// App::new()
/// .add_systems(
/// RunFixedMainLoop,
/// update_camera_rotation.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop))
/// update_camera_rotation.in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop))
/// .add_systems(FixedUpdate, update_physics);
///
/// # fn update_camera_rotation() {}
@ -432,7 +436,7 @@ pub enum RunFixedMainLoopSystem {
///
/// Don't place systems here, use [`FixedUpdate`] and friends instead.
/// Use this system instead to order your systems to run specifically inbetween the fixed update logic and all
/// other systems that run in [`RunFixedMainLoopSystem::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystem::AfterFixedMainLoop`].
/// other systems that run in [`RunFixedMainLoopSystems::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystems::AfterFixedMainLoop`].
///
/// [`Time<Virtual>`]: https://docs.rs/bevy/latest/bevy/prelude/struct.Virtual.html
/// [`Time::overstep`]: https://docs.rs/bevy/latest/bevy/time/struct.Time.html#method.overstep
@ -448,8 +452,8 @@ pub enum RunFixedMainLoopSystem {
/// // This system will be called before all interpolation systems
/// // that third-party plugins might add.
/// prepare_for_interpolation
/// .after(RunFixedMainLoopSystem::FixedMainLoop)
/// .before(RunFixedMainLoopSystem::AfterFixedMainLoop),
/// .after(RunFixedMainLoopSystems::FixedMainLoop)
/// .before(RunFixedMainLoopSystems::AfterFixedMainLoop),
/// )
/// );
///
@ -473,10 +477,14 @@ pub enum RunFixedMainLoopSystem {
/// .add_systems(FixedUpdate, update_physics)
/// .add_systems(
/// RunFixedMainLoop,
/// interpolate_transforms.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop));
/// interpolate_transforms.in_set(RunFixedMainLoopSystems::AfterFixedMainLoop));
///
/// # fn interpolate_transforms() {}
/// # fn update_physics() {}
/// ```
AfterFixedMainLoop,
}
/// Deprecated alias for [`RunFixedMainLoopSystems`].
#[deprecated(since = "0.17.0", note = "Renamed to `RunFixedMainLoopSystems`.")]
pub type RunFixedMainLoopSystem = RunFixedMainLoopSystems;

View File

@ -11,7 +11,7 @@ keywords = ["bevy"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
file_watcher = ["notify-debouncer-full", "watch"]
file_watcher = ["notify-debouncer-full", "watch", "multi_threaded"]
embedded_watcher = ["file_watcher"]
multi_threaded = ["bevy_tasks/multi_threaded"]
asset_processor = []
@ -19,38 +19,50 @@ watch = []
trace = []
[dependencies]
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
"bevy_reflect",
] }
bevy_asset_macros = { path = "macros", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
"uuid",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
"async_executor",
] }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
"std",
] }
stackfuture = "0.3"
atomicow = "1.0"
async-broadcast = "0.7.2"
async-fs = "2.0"
async-lock = "3.0"
bitflags = { version = "2.3", features = ["serde"] }
crossbeam-channel = "0.5"
downcast-rs = { version = "2", default-features = false, features = ["std"] }
disqualified = "1.0"
either = "1.13"
futures-io = "0.3"
futures-lite = "2.0.1"
blake3 = "1.5"
parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] }
ron = "0.8"
serde = { version = "1", features = ["derive"] }
stackfuture = { version = "0.3", default-features = false }
atomicow = { version = "1.1", default-features = false, features = ["std"] }
async-broadcast = { version = "0.7.2", default-features = false }
async-fs = { version = "2.0", default-features = false }
async-lock = { version = "3.0", default-features = false }
bitflags = { version = "2.3", default-features = false }
crossbeam-channel = { version = "0.5", default-features = false, features = [
"std",
] }
downcast-rs = { version = "2", default-features = false }
disqualified = { version = "1.0", default-features = false }
either = { version = "1.13", default-features = false }
futures-io = { version = "0.3", default-features = false }
futures-lite = { version = "2.0.1", default-features = false }
blake3 = { version = "1.5", default-features = false }
parking_lot = { version = "0.12", default-features = false, features = [
"arc_lock",
"send_guard",
] }
ron = { version = "0.8", default-features = false }
serde = { version = "1", default-features = false, features = ["derive"] }
thiserror = { version = "2", default-features = false }
derive_more = { version = "1", default-features = false, features = ["from"] }
uuid = { version = "1.13.1", features = ["v4"] }
tracing = { version = "0.1", default-features = false, features = ["std"] }
uuid = { version = "1.13.1", default-features = false, features = [
"v4",
"serde",
] }
tracing = { version = "0.1", default-features = false }
[target.'cfg(target_os = "android")'.dependencies]
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
@ -77,7 +89,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-featu
] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
notify-debouncer-full = { version = "0.5.0", optional = true }
notify-debouncer-full = { version = "0.5.0", default-features = false, optional = true }
[lints]
workspace = true

View File

@ -22,10 +22,10 @@ use tracing::error;
/// the [`AssetChanged`] filter to determine if an asset has changed since the last time
/// a query ran.
///
/// This resource is automatically managed by the [`AssetEvents`](crate::AssetEvents) schedule and
/// should not be exposed to the user in order to maintain safety guarantees. Any additional uses of
/// this resource should be carefully audited to ensure that they do not introduce any safety
/// issues.
/// This resource is automatically managed by the [`AssetEventSystems`](crate::AssetEventSystems)
/// system set and should not be exposed to the user in order to maintain safety guarantees.
/// Any additional uses of this resource should be carefully audited to ensure that they do not
/// introduce any safety issues.
#[derive(Resource)]
pub(crate) struct AssetChanges<A: Asset> {
change_ticks: HashMap<AssetId<A>, Tick>,
@ -102,14 +102,13 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> {
///
/// # Quirks
///
/// - Asset changes are registered in the [`AssetEvents`] schedule.
/// - Asset changes are registered in the [`AssetEventSystems`] system set.
/// - Removed assets are not detected.
///
/// The list of changed assets only gets updated in the
/// [`AssetEvents`] schedule which runs in `Last`. Therefore, `AssetChanged`
/// will only pick up asset changes in schedules following `AssetEvents` or the
/// next frame. Consider adding the system in the `Last` schedule after [`AssetEvents`] if you need
/// to react without frame delay to asset changes.
/// The list of changed assets only gets updated in the [`AssetEventSystems`] system set,
/// which runs in `Last`. Therefore, `AssetChanged` will only pick up asset changes in schedules
/// following [`AssetEventSystems`] or the next frame. Consider adding the system in the `Last` schedule
/// after [`AssetEventSystems`] if you need to react without frame delay to asset changes.
///
/// # Performance
///
@ -120,7 +119,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> {
///
/// If no `A` asset updated since the last time the system ran, then no lookups occur.
///
/// [`AssetEvents`]: crate::AssetEvents
/// [`AssetEventSystems`]: crate::AssetEventSystems
/// [`Assets<Mesh>::get_mut`]: crate::Assets::get_mut
pub struct AssetChanged<A: AsAssetId>(PhantomData<A>);
@ -166,7 +165,7 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
this_run: Tick,
) -> Self::Fetch<'w> {
// SAFETY:
// - `AssetChanges` is private and only accessed mutably in the `AssetEvents` schedule
// - `AssetChanges` is private and only accessed mutably in the `AssetEventSystems` system set.
// - `resource_id` was obtained from the type ID of `AssetChanges<A::Asset>`.
let Some(changes) = (unsafe {
world
@ -283,7 +282,7 @@ unsafe impl<A: AsAssetId> QueryFilter for AssetChanged<A> {
#[cfg(test)]
#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
mod tests {
use crate::{AssetEvents, AssetPlugin, Handle};
use crate::{AssetEventSystems, AssetPlugin, Handle};
use alloc::{vec, vec::Vec};
use core::num::NonZero;
use std::println;
@ -406,7 +405,7 @@ mod tests {
.init_asset::<MyAsset>()
.insert_resource(Counter(vec![0, 0, 0, 0]))
.add_systems(Update, add_some)
.add_systems(PostUpdate, count_update.after(AssetEvents));
.add_systems(PostUpdate, count_update.after(AssetEventSystems));
// First run of the app, `add_systems(Startup…)` runs.
app.update(); // run_count == 0
@ -441,7 +440,7 @@ mod tests {
},
)
.add_systems(Update, update_some)
.add_systems(PostUpdate, count_update.after(AssetEvents));
.add_systems(PostUpdate, count_update.after(AssetEventSystems));
// First run of the app, `add_systems(Startup…)` runs.
app.update(); // run_count == 0

View File

@ -11,7 +11,6 @@ use core::{
use crossbeam_channel::{Receiver, Sender};
use disqualified::ShortName;
use thiserror::Error;
use uuid::Uuid;
/// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_.
/// This should _only_ be used for one specific asset type.
@ -149,17 +148,6 @@ impl<T: Asset> Clone for Handle<T> {
}
impl<A: Asset> Handle<A> {
/// Create a new [`Handle::Weak`] with the given [`u128`] encoding of a [`Uuid`].
#[deprecated(
since = "0.16.0",
note = "use the `weak_handle!` macro with a UUID string instead"
)]
pub const fn weak_from_u128(value: u128) -> Self {
Handle::Weak(AssetId::Uuid {
uuid: Uuid::from_u128(value),
})
}
/// Returns the [`AssetId`] of this [`Asset`].
#[inline]
pub fn id(&self) -> AssetId<A> {
@ -554,6 +542,7 @@ mod tests {
use bevy_platform::hash::FixedHasher;
use bevy_reflect::PartialReflect;
use core::hash::BuildHasher;
use uuid::Uuid;
use super::*;

View File

@ -8,8 +8,10 @@ use crate::io::{
memory::{Dir, MemoryAssetReader, Value},
AssetSource, AssetSourceBuilders,
};
use crate::AssetServer;
use alloc::boxed::Box;
use bevy_ecs::resource::Resource;
use bevy_app::App;
use bevy_ecs::{resource::Resource, world::World};
use std::path::{Path, PathBuf};
#[cfg(feature = "embedded_watcher")]
@ -132,6 +134,71 @@ impl EmbeddedAssetRegistry {
}
}
/// Trait for the [`load_embedded_asset!`] macro, to access [`AssetServer`]
/// from arbitrary things.
///
/// [`load_embedded_asset!`]: crate::load_embedded_asset
pub trait GetAssetServer {
fn get_asset_server(&self) -> &AssetServer;
}
impl GetAssetServer for App {
fn get_asset_server(&self) -> &AssetServer {
self.world().get_asset_server()
}
}
impl GetAssetServer for World {
fn get_asset_server(&self) -> &AssetServer {
self.resource()
}
}
impl GetAssetServer for AssetServer {
fn get_asset_server(&self) -> &AssetServer {
self
}
}
/// Load an [embedded asset](crate::embedded_asset).
///
/// This is useful if the embedded asset in question is not publicly exposed, but
/// you need to use it internally.
///
/// # Syntax
///
/// This macro takes two arguments and an optional third one:
/// 1. The asset source. It may be `AssetServer`, `World` or `App`.
/// 2. The path to the asset to embed, as a string literal.
/// 3. Optionally, a closure of the same type as in [`AssetServer::load_with_settings`].
/// Consider explicitly typing the closure argument in case of type error.
///
/// # Usage
///
/// The advantage compared to using directly [`AssetServer::load`] is:
/// - This also accepts [`World`] and [`App`] arguments.
/// - This uses the exact same path as `embedded_asset!`, so you can keep it
/// consistent.
///
/// As a rule of thumb:
/// - If the asset in used in the same module as it is declared using `embedded_asset!`,
/// use this macro.
/// - Otherwise, use `AssetServer::load`.
#[macro_export]
macro_rules! load_embedded_asset {
(@get: $path: literal, $provider: expr) => {{
let path = $crate::embedded_path!($path);
let path = $crate::AssetPath::from_path_buf(path).with_source("embedded");
let asset_server = $crate::io::embedded::GetAssetServer::get_asset_server($provider);
(path, asset_server)
}};
($provider: expr, $path: literal, $settings: expr) => {{
let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);
asset_server.load_with_settings(path, $settings)
}};
($provider: expr, $path: literal) => {{
let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);
asset_server.load(path)
}};
}
/// Returns the [`Path`] for a given `embedded` asset.
/// This is used internally by [`embedded_asset`] and can be used to get a [`Path`]
/// that matches the [`AssetPath`](crate::AssetPath) used by that asset.
@ -140,7 +207,7 @@ impl EmbeddedAssetRegistry {
#[macro_export]
macro_rules! embedded_path {
($path_str: expr) => {{
embedded_path!("src", $path_str)
$crate::embedded_path!("src", $path_str)
}};
($source_path: expr, $path_str: expr) => {{
@ -168,6 +235,13 @@ pub fn _embedded_asset_path(
file_path: &Path,
asset_path: &Path,
) -> PathBuf {
let file_path = if cfg!(not(target_family = "windows")) {
// Work around bug: https://github.com/bevyengine/bevy/issues/14246
// Note, this will break any paths on Linux/Mac containing "\"
PathBuf::from(file_path.to_str().unwrap().replace("\\", "/"))
} else {
PathBuf::from(file_path)
};
let mut maybe_parent = file_path.parent();
let after_src = loop {
let Some(parent) = maybe_parent else {
@ -185,7 +259,7 @@ pub fn _embedded_asset_path(
/// Creates a new `embedded` asset by embedding the bytes of the given path into the current binary
/// and registering those bytes with the `embedded` [`AssetSource`].
///
/// This accepts the current [`App`](bevy_app::App) as the first parameter and a path `&str` (relative to the current file) as the second.
/// This accepts the current [`App`] as the first parameter and a path `&str` (relative to the current file) as the second.
///
/// By default this will generate an [`AssetPath`] using the following rules:
///
@ -210,14 +284,19 @@ pub fn _embedded_asset_path(
///
/// `embedded_asset!(app, "rock.wgsl")`
///
/// `rock.wgsl` can now be loaded by the [`AssetServer`](crate::AssetServer) with the following path:
/// `rock.wgsl` can now be loaded by the [`AssetServer`] as follows:
///
/// ```no_run
/// # use bevy_asset::{Asset, AssetServer};
/// # use bevy_asset::{Asset, AssetServer, load_embedded_asset};
/// # use bevy_reflect::TypePath;
/// # let asset_server: AssetServer = panic!();
/// # #[derive(Asset, TypePath)]
/// # struct Shader;
/// // If we are loading the shader in the same module we used `embedded_asset!`:
/// let shader = load_embedded_asset!(&asset_server, "rock.wgsl");
/// # let _: bevy_asset::Handle<Shader> = shader;
///
/// // If the goal is to expose the asset **to the end user**:
/// let shader = asset_server.load::<Shader>("embedded://bevy_rock/render/rock.wgsl");
/// ```
///
@ -251,11 +330,11 @@ pub fn _embedded_asset_path(
/// [`embedded_path`]: crate::embedded_path
#[macro_export]
macro_rules! embedded_asset {
($app: ident, $path: expr) => {{
($app: expr, $path: expr) => {{
$crate::embedded_asset!($app, "src", $path)
}};
($app: ident, $source_path: expr, $path: expr) => {{
($app: expr, $source_path: expr, $path: expr) => {{
let mut embedded = $app
.world_mut()
.resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>();

View File

@ -74,6 +74,15 @@ impl AssetReader for FileAssetReader {
return None;
}
}
// filter out hidden files. they are not listed by default but are directly targetable
if path
.file_name()
.and_then(|file_name| file_name.to_str())
.map(|file_name| file_name.starts_with('.'))
.unwrap_or_default()
{
return None;
}
let relative_path = path.strip_prefix(&root_path).unwrap();
Some(relative_path.to_owned())
})

View File

@ -145,6 +145,16 @@ impl AssetReader for FileAssetReader {
return None;
}
}
// filter out hidden files. they are not listed by default but are directly targetable
if path
.file_name()
.and_then(|file_name| file_name.to_str())
.map(|file_name| file_name.starts_with('.'))
.unwrap_or_default()
{
return None;
}
let relative_path = path.strip_prefix(&root_path).unwrap();
Some(relative_path.to_owned())
})

View File

@ -228,13 +228,6 @@ use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath};
use core::any::TypeId;
use tracing::error;
#[cfg(all(feature = "file_watcher", not(feature = "multi_threaded")))]
compile_error!(
"The \"file_watcher\" feature for hot reloading requires the \
\"multi_threaded\" feature to be functional.\n\
Consider either disabling the \"file_watcher\" feature or enabling \"multi_threaded\""
);
/// Provides "asset" loading and processing functionality. An [`Asset`] is a "runtime value" that is loaded from an [`AssetSource`],
/// which can be something like a filesystem, a network, etc.
///
@ -421,7 +414,10 @@ impl Plugin for AssetPlugin {
.init_asset::<LoadedUntypedAsset>()
.init_asset::<()>()
.add_event::<UntypedAssetLoadFailedEvent>()
.configure_sets(PreUpdate, TrackAssets.after(handle_internal_asset_events))
.configure_sets(
PreUpdate,
AssetTrackingSystems.after(handle_internal_asset_events),
)
// `handle_internal_asset_events` requires the use of `&mut World`,
// and as a result has ambiguous system ordering with all other systems in `PreUpdate`.
// This is virtually never a real problem: asset loading is async and so anything that interacts directly with it
@ -618,9 +614,12 @@ impl AssetApp for App {
PostUpdate,
Assets::<A>::asset_events
.run_if(Assets::<A>::asset_events_condition)
.in_set(AssetEvents),
.in_set(AssetEventSystems),
)
.add_systems(
PreUpdate,
Assets::<A>::track_assets.in_set(AssetTrackingSystems),
)
.add_systems(PreUpdate, Assets::<A>::track_assets.in_set(TrackAssets))
}
fn register_asset_reflect<A>(&mut self) -> &mut Self
@ -650,13 +649,21 @@ impl AssetApp for App {
/// A system set that holds all "track asset" operations.
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
pub struct TrackAssets;
pub struct AssetTrackingSystems;
/// Deprecated alias for [`AssetTrackingSystems`].
#[deprecated(since = "0.17.0", note = "Renamed to `AssetTrackingSystems`.")]
pub type TrackAssets = AssetTrackingSystems;
/// A system set where events accumulated in [`Assets`] are applied to the [`AssetEvent`] [`Events`] resource.
///
/// [`Events`]: bevy_ecs::event::Events
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub struct AssetEvents;
pub struct AssetEventSystems;
/// Deprecated alias for [`AssetEventSystems`].
#[deprecated(since = "0.17.0", note = "Renamed to `AssetEventSystems`.")]
pub type AssetEvents = AssetEventSystems;
#[cfg(test)]
mod tests {

View File

@ -223,6 +223,16 @@ impl<'a> AssetPath<'a> {
Ok((source, path, label))
}
/// Creates a new [`AssetPath`] from a [`PathBuf`].
#[inline]
pub fn from_path_buf(path_buf: PathBuf) -> AssetPath<'a> {
AssetPath {
path: CowArc::Owned(path_buf.into()),
source: AssetSourceId::Default,
label: None,
}
}
/// Creates a new [`AssetPath`] from a [`Path`].
#[inline]
pub fn from_path(path: &'a Path) -> AssetPath<'a> {

View File

@ -58,13 +58,13 @@ pub use sinks::*;
use bevy_app::prelude::*;
use bevy_asset::{Asset, AssetApp};
use bevy_ecs::prelude::*;
use bevy_transform::TransformSystem;
use bevy_transform::TransformSystems;
use audio_output::*;
/// Set for the audio playback systems, so they can share a run condition
#[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
struct AudioPlaySet;
struct AudioPlaybackSystems;
/// Adds support for audio playback to a Bevy Application
///
@ -90,13 +90,13 @@ impl Plugin for AudioPlugin {
.insert_resource(DefaultSpatialScale(self.default_spatial_scale))
.configure_sets(
PostUpdate,
AudioPlaySet
AudioPlaybackSystems
.run_if(audio_output_available)
.after(TransformSystem::TransformPropagate), // For spatial audio transforms
.after(TransformSystems::Propagate), // For spatial audio transforms
)
.add_systems(
PostUpdate,
(update_emitter_positions, update_listener_positions).in_set(AudioPlaySet),
(update_emitter_positions, update_listener_positions).in_set(AudioPlaybackSystems),
)
.init_resource::<AudioOutput>();
@ -118,7 +118,8 @@ impl AddAudioSource for App {
{
self.init_asset::<T>().add_systems(
PostUpdate,
(play_queued_audio_system::<T>, cleanup_finished_audio::<T>).in_set(AudioPlaySet),
(play_queued_audio_system::<T>, cleanup_finished_audio::<T>)
.in_set(AudioPlaybackSystems),
);
self
}

View File

@ -1,10 +1,11 @@
use crate::Volume;
use bevy_ecs::component::Component;
use bevy_math::Vec3;
use bevy_transform::prelude::Transform;
use core::time::Duration;
pub use rodio::source::SeekError;
use rodio::{Sink, SpatialSink};
use crate::Volume;
/// Common interactions with an audio sink.
pub trait AudioSinkPlayback {
/// Gets the volume of the sound as a [`Volume`].
@ -41,6 +42,26 @@ pub trait AudioSinkPlayback {
/// No effect if not paused.
fn play(&self);
/// Attempts to seek to a given position in the current source.
///
/// This blocks between 0 and ~5 milliseconds.
///
/// As long as the duration of the source is known, seek is guaranteed to saturate
/// at the end of the source. For example given a source that reports a total duration
/// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to
/// 42 seconds.
///
/// # Errors
/// This function will return [`SeekError::NotSupported`] if one of the underlying
/// sources does not support seeking.
///
/// It will return an error if an implementation ran
/// into one during the seek.
///
/// When seeking beyond the end of a source, this
/// function might return an error if the duration of the source is not known.
fn try_seek(&self, pos: Duration) -> Result<(), SeekError>;
/// Pauses playback of this sink.
///
/// No effect if already paused.
@ -160,6 +181,10 @@ impl AudioSinkPlayback for AudioSink {
self.sink.play();
}
fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {
self.sink.try_seek(pos)
}
fn pause(&self) {
self.sink.pause();
}
@ -256,6 +281,10 @@ impl AudioSinkPlayback for SpatialAudioSink {
self.sink.play();
}
fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {
self.sink.try_seek(pos)
}
fn pause(&self) {
self.sink.pause();
}

View File

@ -77,6 +77,20 @@ pub trait Alpha: Sized {
}
}
impl Alpha for f32 {
fn with_alpha(&self, alpha: f32) -> Self {
alpha
}
fn alpha(&self) -> f32 {
*self
}
fn set_alpha(&mut self, alpha: f32) {
*self = alpha;
}
}
/// Trait for manipulating the hue of a color.
pub trait Hue: Sized {
/// Return a new version of this color with the hue channel set to the given value.

View File

@ -0,0 +1,37 @@
//! TODO: Implement for non-linear colors.
#[cfg(test)]
mod test {
use bevy_math::StableInterpolate;
use crate::{Gray, Laba, LinearRgba, Oklaba, Srgba, Xyza};
#[test]
pub fn test_color_stable_interpolate() {
let b = Srgba::BLACK;
let w = Srgba::WHITE;
assert_eq!(
b.interpolate_stable(&w, 0.5),
Srgba::new(0.5, 0.5, 0.5, 1.0),
);
let b = LinearRgba::BLACK;
let w = LinearRgba::WHITE;
assert_eq!(
b.interpolate_stable(&w, 0.5),
LinearRgba::new(0.5, 0.5, 0.5, 1.0),
);
let b = Xyza::BLACK;
let w = Xyza::WHITE;
assert_eq!(b.interpolate_stable(&w, 0.5), Xyza::gray(0.5),);
let b = Laba::BLACK;
let w = Laba::WHITE;
assert_eq!(b.interpolate_stable(&w, 0.5), Laba::new(0.5, 0.0, 0.0, 1.0),);
let b = Oklaba::BLACK;
let w = Oklaba::WHITE;
assert_eq!(b.interpolate_stable(&w, 0.5), Oklaba::gray(0.5),);
}
}

View File

@ -105,6 +105,7 @@ mod color_range;
mod hsla;
mod hsva;
mod hwba;
mod interpolate;
mod laba;
mod lcha;
mod linear_rgba;
@ -265,6 +266,12 @@ macro_rules! impl_componentwise_vector_space {
$($element: 0.0,)+
};
}
impl bevy_math::StableInterpolate for $ty {
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
bevy_math::VectorSpace::lerp(*self, *other, t)
}
}
};
}

View File

@ -9,7 +9,7 @@ use bevy_render::{
Buffer, BufferDescriptor, BufferUsages, PipelineCache, Shader, SpecializedComputePipelines,
},
renderer::RenderDevice,
ExtractSchedule, Render, RenderApp, RenderSet,
ExtractSchedule, Render, RenderApp, RenderSystems,
};
mod buffers;
@ -72,8 +72,8 @@ impl Plugin for AutoExposurePlugin {
.add_systems(
Render,
(
prepare_buffers.in_set(RenderSet::Prepare),
queue_view_auto_exposure_pipelines.in_set(RenderSet::Queue),
prepare_buffers.in_set(RenderSystems::Prepare),
queue_view_auto_exposure_pipelines.in_set(RenderSystems::Queue),
),
)
.add_render_graph_node::<AutoExposureNode>(Core3d, node::AutoExposure)

View File

@ -24,7 +24,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice},
texture::{CachedTexture, TextureCache},
view::ViewTarget,
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use downsampling_pipeline::{
prepare_downsampling_pipeline, BloomDownsamplingPipeline, BloomDownsamplingPipelineIds,
@ -63,10 +63,10 @@ impl Plugin for BloomPlugin {
.add_systems(
Render,
(
prepare_downsampling_pipeline.in_set(RenderSet::Prepare),
prepare_upsampling_pipeline.in_set(RenderSet::Prepare),
prepare_bloom_textures.in_set(RenderSet::PrepareResources),
prepare_bloom_bind_groups.in_set(RenderSet::PrepareBindGroups),
prepare_downsampling_pipeline.in_set(RenderSystems::Prepare),
prepare_upsampling_pipeline.in_set(RenderSystems::Prepare),
prepare_bloom_textures.in_set(RenderSystems::PrepareResources),
prepare_bloom_bind_groups.in_set(RenderSystems::PrepareBindGroups),
),
)
// Add bloom to the 3d render graph

View File

@ -65,7 +65,7 @@ use bevy_render::{
sync_world::MainEntity,
texture::TextureCache,
view::{Msaa, ViewDepthTexture},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
};
use self::graph::{Core2d, Node2d};
@ -93,8 +93,8 @@ impl Plugin for Core2dPlugin {
.add_systems(
Render,
(
sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
prepare_core_2d_depth_textures.in_set(RenderSet::PrepareResources),
sort_phase_system::<Transparent2d>.in_set(RenderSystems::PhaseSort),
prepare_core_2d_depth_textures.in_set(RenderSystems::PrepareResources),
),
);

View File

@ -106,7 +106,7 @@ use bevy_render::{
sync_world::{MainEntity, RenderEntity},
texture::{ColorAttachment, TextureCache},
view::{ExtractedView, ViewDepthTexture, ViewTarget},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
};
use nonmax::NonMaxU32;
use tracing::warn;
@ -167,14 +167,14 @@ impl Plugin for Core3dPlugin {
.add_systems(
Render,
(
sort_phase_system::<Transmissive3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
sort_phase_system::<Transmissive3d>.in_set(RenderSystems::PhaseSort),
sort_phase_system::<Transparent3d>.in_set(RenderSystems::PhaseSort),
configure_occlusion_culling_view_targets
.after(prepare_view_targets)
.in_set(RenderSet::ManageViews),
prepare_core_3d_depth_textures.in_set(RenderSet::PrepareResources),
prepare_core_3d_transmission_textures.in_set(RenderSet::PrepareResources),
prepare_prepass_textures.in_set(RenderSet::PrepareResources),
.in_set(RenderSystems::ManageViews),
prepare_core_3d_depth_textures.in_set(RenderSystems::PrepareResources),
prepare_core_3d_transmission_textures.in_set(RenderSystems::PrepareResources),
prepare_prepass_textures.in_set(RenderSystems::PrepareResources),
),
);

View File

@ -12,7 +12,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::{CachedTexture, TextureCache},
view::ViewTarget,
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use bevy_ecs::query::QueryItem;
@ -40,7 +40,7 @@ impl Plugin for CopyDeferredLightingIdPlugin {
};
render_app.add_systems(
Render,
(prepare_deferred_lighting_id_textures.in_set(RenderSet::PrepareResources),),
(prepare_deferred_lighting_id_textures.in_set(RenderSystems::PrepareResources),),
);
}

View File

@ -55,7 +55,7 @@ use bevy_render::{
prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
ViewUniformOffset, ViewUniforms,
},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
};
use bevy_utils::{default, once};
use smallvec::SmallVec;
@ -229,7 +229,7 @@ impl Plugin for DepthOfFieldPlugin {
prepare_auxiliary_depth_of_field_textures,
)
.after(prepare_view_targets)
.in_set(RenderSet::ManageViews),
.in_set(RenderSystems::ManageViews),
)
.add_systems(
Render,
@ -238,11 +238,11 @@ impl Plugin for DepthOfFieldPlugin {
prepare_depth_of_field_pipelines,
)
.chain()
.in_set(RenderSet::Prepare),
.in_set(RenderSystems::Prepare),
)
.add_systems(
Render,
prepare_depth_of_field_global_bind_group.in_set(RenderSet::PrepareBindGroups),
prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups),
)
.add_render_graph_node::<ViewNodeRunner<DepthOfFieldNode>>(Core3d, Node3d::DepthOfField)
.add_render_graph_edges(

View File

@ -44,7 +44,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice},
texture::TextureCache,
view::{ExtractedView, NoIndirectDrawing, ViewDepthTexture},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use bitflags::bitflags;
use tracing::debug;
@ -103,7 +103,7 @@ impl Plugin for MipGenerationPlugin {
)
.add_systems(
Render,
create_downsample_depth_pipelines.in_set(RenderSet::Prepare),
create_downsample_depth_pipelines.in_set(RenderSystems::Prepare),
)
.add_systems(
Render,
@ -112,7 +112,7 @@ impl Plugin for MipGenerationPlugin {
prepare_downsample_depth_view_bind_groups,
)
.chain()
.in_set(RenderSet::PrepareResources)
.in_set(RenderSystems::PrepareResources)
.run_if(resource_exists::<DownsampleDepthPipelines>)
.after(prepare_core_3d_depth_textures),
);

View File

@ -20,7 +20,7 @@ use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},
render_graph::{RenderGraphApp, ViewNodeRunner},
render_resource::{Shader, ShaderType, SpecializedRenderPipelines},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
pub mod node;
@ -152,7 +152,7 @@ impl Plugin for MotionBlurPlugin {
.init_resource::<SpecializedRenderPipelines<pipeline::MotionBlurPipeline>>()
.add_systems(
Render,
pipeline::prepare_motion_blur_pipelines.in_set(RenderSet::Prepare),
pipeline::prepare_motion_blur_pipelines.in_set(RenderSystems::Prepare),
);
render_app

View File

@ -12,7 +12,7 @@ use bevy_render::{
render_resource::*,
renderer::RenderContext,
view::{Msaa, ViewTarget},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
/// This enables "msaa writeback" support for the `core_2d` and `core_3d` pipelines, which can be enabled on cameras
@ -26,7 +26,7 @@ impl Plugin for MsaaWritebackPlugin {
};
render_app.add_systems(
Render,
prepare_msaa_writeback_pipelines.in_set(RenderSet::Prepare),
prepare_msaa_writeback_pipelines.in_set(RenderSystems::Prepare),
);
{
render_app

View File

@ -16,7 +16,7 @@ use bevy_render::{
},
renderer::{RenderDevice, RenderQueue},
view::Msaa,
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use bevy_window::PrimaryWindow;
use resolve::{
@ -126,7 +126,7 @@ impl Plugin for OrderIndependentTransparencyPlugin {
render_app.add_systems(
Render,
prepare_oit_buffers.in_set(RenderSet::PrepareResources),
prepare_oit_buffers.in_set(RenderSystems::PrepareResources),
);
render_app

View File

@ -20,7 +20,7 @@ use bevy_render::{
},
renderer::{RenderAdapter, RenderDevice},
view::{ExtractedView, ViewTarget, ViewUniform, ViewUniforms},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use tracing::warn;
@ -65,8 +65,8 @@ impl Plugin for OitResolvePlugin {
.add_systems(
Render,
(
queue_oit_resolve_pipeline.in_set(RenderSet::Queue),
prepare_oit_resolve_bind_group.in_set(RenderSet::PrepareBindGroups),
queue_oit_resolve_pipeline.in_set(RenderSystems::Queue),
prepare_oit_resolve_bind_group.in_set(RenderSystems::PrepareBindGroups),
),
)
.init_resource::<OitResolvePipeline>();

View File

@ -36,7 +36,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::GpuImage,
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use bevy_utils::prelude::default;
@ -234,7 +234,7 @@ impl Plugin for PostProcessingPlugin {
prepare_post_processing_pipelines,
prepare_post_processing_uniforms,
)
.in_set(RenderSet::Prepare),
.in_set(RenderSystems::Prepare),
)
.add_render_graph_node::<ViewNodeRunner<PostProcessingNode>>(
Core3d,

View File

@ -25,7 +25,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::GpuImage,
view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniforms},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use bevy_transform::components::Transform;
use prepass::{SkyboxPrepassPipeline, SKYBOX_PREPASS_SHADER_HANDLE};
@ -63,11 +63,11 @@ impl Plugin for SkyboxPlugin {
.add_systems(
Render,
(
prepare_skybox_pipelines.in_set(RenderSet::Prepare),
prepass::prepare_skybox_prepass_pipelines.in_set(RenderSet::Prepare),
prepare_skybox_bind_groups.in_set(RenderSet::PrepareBindGroups),
prepare_skybox_pipelines.in_set(RenderSystems::Prepare),
prepass::prepare_skybox_prepass_pipelines.in_set(RenderSystems::Prepare),
prepare_skybox_bind_groups.in_set(RenderSystems::PrepareBindGroups),
prepass::prepare_skybox_prepass_bind_groups
.in_set(RenderSet::PrepareBindGroups),
.in_set(RenderSystems::PrepareBindGroups),
),
);
}

View File

@ -16,7 +16,7 @@ use bevy_render::{
renderer::RenderDevice,
texture::{FallbackImage, GpuImage},
view::{ExtractedView, ViewTarget, ViewUniform},
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
use bitflags::bitflags;
#[cfg(not(feature = "tonemapping_luts"))]
@ -118,7 +118,7 @@ impl Plugin for TonemappingPlugin {
.init_resource::<SpecializedRenderPipelines<TonemappingPipeline>>()
.add_systems(
Render,
prepare_view_tonemapping_pipelines.in_set(RenderSet::Prepare),
prepare_view_tonemapping_pipelines.in_set(RenderSystems::Prepare),
);
}

View File

@ -6,7 +6,7 @@ use bevy_render::{
camera::{CameraOutputMode, ExtractedCamera},
render_resource::*,
view::ViewTarget,
Render, RenderApp, RenderSet,
Render, RenderApp, RenderSystems,
};
mod node;
@ -26,7 +26,7 @@ impl Plugin for UpscalingPlugin {
// and aversion to extensive and intrusive system ordering.
// See https://github.com/bevyengine/bevy/issues/14770 for more context.
prepare_view_upscaling_pipelines
.in_set(RenderSet::Prepare)
.in_set(RenderSystems::Prepare)
.ambiguous_with_all(),
);
}

View File

@ -57,7 +57,7 @@ impl Plugin for CiTestingPlugin {
systems::send_events
.before(trigger_screenshots)
.before(bevy_window::close_when_requested)
.in_set(SendEvents)
.in_set(EventSenderSystems)
.ambiguous_with_all(),
);
@ -66,10 +66,10 @@ impl Plugin for CiTestingPlugin {
#[cfg(any(unix, windows))]
app.configure_sets(
Update,
SendEvents.before(bevy_app::TerminalCtrlCHandlerPlugin::exit_on_flag),
EventSenderSystems.before(bevy_app::TerminalCtrlCHandlerPlugin::exit_on_flag),
);
}
}
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
struct SendEvents;
struct EventSenderSystems;

View File

@ -8,7 +8,7 @@ use bevy_picking::backend::HitData;
use bevy_picking::hover::HoverMap;
use bevy_picking::pointer::{Location, PointerId, PointerPress};
use bevy_picking::prelude::*;
use bevy_picking::{pointer, PickSet};
use bevy_picking::{pointer, PickingSystems};
use bevy_reflect::prelude::*;
use bevy_render::prelude::*;
use bevy_text::prelude::*;
@ -85,7 +85,7 @@ impl Plugin for DebugPickingPlugin {
app.init_resource::<DebugPickingMode>()
.add_systems(
PreUpdate,
pointer_debug_visibility.in_set(PickSet::PostHover),
pointer_debug_visibility.in_set(PickingSystems::PostHover),
)
.add_systems(
PreUpdate,
@ -108,7 +108,7 @@ impl Plugin for DebugPickingPlugin {
log_pointer_event_debug::<DragDrop>,
)
.distributive_run_if(DebugPickingMode::is_enabled)
.in_set(PickSet::Last),
.in_set(PickingSystems::Last),
);
app.add_systems(
@ -116,7 +116,7 @@ impl Plugin for DebugPickingPlugin {
(add_pointer_debug, update_debug_data, debug_draw)
.chain()
.distributive_run_if(DebugPickingMode::is_enabled)
.in_set(PickSet::Last),
.in_set(PickingSystems::Last),
);
}
}

View File

@ -77,14 +77,14 @@ log = { version = "0.4", default-features = false }
# macOS
[target.'cfg(all(target_os="macos"))'.dependencies]
# Some features of sysinfo are not supported by apple. This will disable those features on apple devices
sysinfo = { version = "0.34.0", optional = true, default-features = false, features = [
sysinfo = { version = "0.35.0", optional = true, default-features = false, features = [
"apple-app-store",
"system",
] }
# Only include when on linux/windows/android/freebsd
[target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "android", target_os = "freebsd"))'.dependencies]
sysinfo = { version = "0.34.0", optional = true, default-features = false, features = [
sysinfo = { version = "0.35.0", optional = true, default-features = false, features = [
"system",
] }

View File

@ -33,13 +33,6 @@ bevy_reflect = ["dep:bevy_reflect"]
## Extends reflection support to functions.
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
## Use the configurable global error handler as the default error handler.
##
## This is typically used to turn panics from the ECS into loggable errors.
## This may be useful for production builds,
## but can result in a measurable performance impact, especially for commands.
configurable_error_handler = []
## Enables automatic backtrace capturing in BevyError
backtrace = ["std"]

View File

@ -290,7 +290,7 @@ struct MyEvent {
}
fn writer(mut writer: EventWriter<MyEvent>) {
writer.send(MyEvent {
writer.write(MyEvent {
message: "hello!".to_string(),
});
}

View File

@ -29,11 +29,11 @@ fn main() {
// Add systems to the Schedule to execute our app logic
// We can label our systems to force a specific run-order between some of them
schedule.add_systems((
spawn_entities.in_set(SimulationSet::Spawn),
print_counter_when_changed.after(SimulationSet::Spawn),
age_all_entities.in_set(SimulationSet::Age),
remove_old_entities.after(SimulationSet::Age),
print_changed_entities.after(SimulationSet::Age),
spawn_entities.in_set(SimulationSystems::Spawn),
print_counter_when_changed.after(SimulationSystems::Spawn),
age_all_entities.in_set(SimulationSystems::Age),
remove_old_entities.after(SimulationSystems::Age),
print_changed_entities.after(SimulationSystems::Age),
));
// Simulate 10 frames in our world
@ -57,7 +57,7 @@ struct Age {
// System sets can be used to group systems and configured to control relative ordering
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
enum SimulationSet {
enum SimulationSystems {
Spawn,
Age,
}

View File

@ -19,13 +19,13 @@ fn main() {
// This update should happen before we use the events.
// Here, we use system sets to control the ordering.
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct FlushEvents;
pub struct EventFlusherSystems;
schedule.add_systems(bevy_ecs::event::event_update_system.in_set(FlushEvents));
schedule.add_systems(bevy_ecs::event::event_update_system.in_set(EventFlusherSystems));
// Add systems sending and receiving events after the events are flushed.
schedule.add_systems((
sending_system.after(FlushEvents),
sending_system.after(EventFlusherSystems),
receiving_system.after(sending_system),
));

View File

@ -251,6 +251,8 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
let clone_behavior = if relationship_target.is_some() {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
} else if let Some(behavior) = attrs.clone_behavior {
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::#behavior)
} else {
quote!(
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
@ -396,6 +398,7 @@ pub const ON_REMOVE: &str = "on_remove";
pub const ON_DESPAWN: &str = "on_despawn";
pub const IMMUTABLE: &str = "immutable";
pub const CLONE_BEHAVIOR: &str = "clone_behavior";
/// All allowed attribute value expression kinds for component hooks
#[derive(Debug)]
@ -458,6 +461,7 @@ struct Attrs {
relationship: Option<Relationship>,
relationship_target: Option<RelationshipTarget>,
immutable: bool,
clone_behavior: Option<Expr>,
}
#[derive(Clone, Copy)]
@ -496,6 +500,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
relationship: None,
relationship_target: None,
immutable: false,
clone_behavior: None,
};
let mut require_paths = HashSet::new();
@ -531,6 +536,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
} else if nested.path.is_ident(IMMUTABLE) {
attrs.immutable = true;
Ok(())
} else if nested.path.is_ident(CLONE_BEHAVIOR) {
attrs.clone_behavior = Some(nested.value()?.parse()?);
Ok(())
} else {
Err(nested.error("Unsupported attribute"))
}
@ -560,6 +568,13 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
}
}
if attrs.relationship_target.is_some() && attrs.clone_behavior.is_some() {
return Err(syn::Error::new(
attrs.clone_behavior.span(),
"A Relationship Target already has its own clone behavior, please remove `clone_behavior = ...`",
));
}
Ok(attrs)
}

View File

@ -6,7 +6,6 @@ extern crate proc_macro;
mod component;
mod query_data;
mod query_filter;
mod states;
mod world_query;
use crate::{
@ -455,7 +454,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
<#field_types as #path::system::SystemParam>::validate_param(#field_locals, _system_meta, _world)
.map_err(|err| #path::system::SystemParamValidationError::new::<Self>(err.skipped, #field_messages, #field_names))?;
)*
Ok(())
Result::Ok(())
}
#[inline]
@ -548,16 +547,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
component::derive_component(input)
}
#[proc_macro_derive(States)]
pub fn derive_states(input: TokenStream) -> TokenStream {
states::derive_states(input)
}
#[proc_macro_derive(SubStates, attributes(source))]
pub fn derive_substates(input: TokenStream) -> TokenStream {
states::derive_substates(input)
}
#[proc_macro_derive(FromWorld, attributes(from_world))]
pub fn derive_from_world(input: TokenStream) -> TokenStream {
let bevy_ecs_path = bevy_ecs_path();

View File

@ -268,6 +268,14 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
}
}
fn provide_extra_access(
state: &mut Self::State,
access: &mut #path::query::Access<#path::component::ComponentId>,
available_access: &#path::query::Access<#path::component::ComponentId>,
) {
#(<#field_types>::provide_extra_access(&mut state.#named_field_idents, access, available_access);)*
}
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
#[inline(always)]
unsafe fn fetch<'__w>(
@ -305,6 +313,14 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
}
}
fn provide_extra_access(
state: &mut Self::State,
access: &mut #path::query::Access<#path::component::ComponentId>,
available_access: &#path::query::Access<#path::component::ComponentId>,
) {
#(<#field_types>::provide_extra_access(&mut state.#named_field_idents, access, available_access);)*
}
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
#[inline(always)]
unsafe fn fetch<'__w>(

View File

@ -1,144 +0,0 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Pat, Path, Result};
use crate::bevy_ecs_path;
pub fn derive_states(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut base_trait_path = bevy_ecs_path();
base_trait_path
.segments
.push(format_ident!("schedule").into());
let mut trait_path = base_trait_path.clone();
trait_path.segments.push(format_ident!("States").into());
let mut state_mutation_trait_path = base_trait_path.clone();
state_mutation_trait_path
.segments
.push(format_ident!("FreelyMutableState").into());
let struct_name = &ast.ident;
quote! {
impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {}
impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause {
}
}
.into()
}
struct Source {
source_type: Path,
source_value: Pat,
}
fn parse_sources_attr(ast: &DeriveInput) -> Result<Source> {
let mut result = ast
.attrs
.iter()
.filter(|a| a.path().is_ident("source"))
.map(|meta| {
let mut source = None;
let value = meta.parse_nested_meta(|nested| {
let source_type = nested.path.clone();
let source_value = Pat::parse_multi(nested.value()?)?;
source = Some(Source {
source_type,
source_value,
});
Ok(())
});
match source {
Some(value) => Ok(value),
None => match value {
Ok(_) => Err(syn::Error::new(
ast.span(),
"Couldn't parse SubStates source",
)),
Err(e) => Err(e),
},
}
})
.collect::<Result<Vec<_>>>()?;
if result.len() > 1 {
return Err(syn::Error::new(
ast.span(),
"Only one source is allowed for SubStates",
));
}
let Some(result) = result.pop() else {
return Err(syn::Error::new(ast.span(), "SubStates require a source"));
};
Ok(result)
}
pub fn derive_substates(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let sources = parse_sources_attr(&ast).expect("Failed to parse substate sources");
let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut base_trait_path = bevy_ecs_path();
base_trait_path
.segments
.push(format_ident!("schedule").into());
let mut trait_path = base_trait_path.clone();
trait_path.segments.push(format_ident!("SubStates").into());
let mut state_set_trait_path = base_trait_path.clone();
state_set_trait_path
.segments
.push(format_ident!("StateSet").into());
let mut state_trait_path = base_trait_path.clone();
state_trait_path
.segments
.push(format_ident!("States").into());
let mut state_mutation_trait_path = base_trait_path.clone();
state_mutation_trait_path
.segments
.push(format_ident!("FreelyMutableState").into());
let struct_name = &ast.ident;
let source_state_type = sources.source_type;
let source_state_value = sources.source_value;
let result = quote! {
impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {
type SourceStates = #source_state_type;
fn should_exist(sources: #source_state_type) -> Option<Self> {
if matches!(sources, #source_state_value) {
Some(Self::default())
} else {
None
}
}
}
impl #impl_generics #state_trait_path for #struct_name #ty_generics #where_clause {
const DEPENDENCY_DEPTH : usize = <Self as #trait_path>::SourceStates::SET_DEPENDENCY_DEPTH + 1;
}
impl #impl_generics #state_mutation_trait_path for #struct_name #ty_generics #where_clause {
}
};
// panic!("Got Result\n{}", result.to_string());
result.into()
}

View File

@ -20,7 +20,10 @@ use crate::{
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE},
world::{
unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE,
ON_REPLACE,
},
};
use alloc::{boxed::Box, vec, vec::Vec};
use bevy_platform::collections::{HashMap, HashSet};
@ -630,13 +633,14 @@ impl BundleInfo {
let mut bundle_component = 0;
let after_effect = bundle.get_components(&mut |storage_type, component_ptr| {
let component_id = *self.component_ids.get_unchecked(bundle_component);
// SAFETY: bundle_component is a valid index for this bundle
let status = unsafe { bundle_component_status.get_status(bundle_component) };
match storage_type {
StorageType::Table => {
// 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 ensures that
// the target table contains the component.
let column = table.get_column_mut(component_id).debug_checked_unwrap();
let column =
// SAFETY: If component_id is in self.component_ids, BundleInfo::new ensures that
// the target table contains the component.
unsafe { table.get_column_mut(component_id).debug_checked_unwrap() };
match (status, insert_mode) {
(ComponentStatus::Added, _) => {
column.initialize(table_row, component_ptr, change_tick, caller);
@ -656,7 +660,16 @@ impl BundleInfo {
// SAFETY: If component_id is in self.component_ids, BundleInfo::new ensures that
// a sparse set exists for the component.
unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() };
sparse_set.insert(entity, component_ptr, change_tick, caller);
match (status, insert_mode) {
(ComponentStatus::Added, _) | (_, InsertMode::Replace) => {
sparse_set.insert(entity, component_ptr, change_tick, caller);
}
(ComponentStatus::Existing, InsertMode::Keep) => {
if let Some(drop_fn) = sparse_set.get_drop() {
drop_fn(component_ptr);
}
}
}
}
}
bundle_component += 1;
@ -1361,6 +1374,274 @@ impl<'w> BundleInserter<'w> {
}
}
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
pub(crate) struct BundleRemover<'w> {
world: UnsafeWorldCell<'w>,
bundle_info: ConstNonNull<BundleInfo>,
old_and_new_table: Option<(NonNull<Table>, NonNull<Table>)>,
old_archetype: NonNull<Archetype>,
new_archetype: NonNull<Archetype>,
}
impl<'w> BundleRemover<'w> {
/// Creates a new [`BundleRemover`], if such a remover would do anything.
///
/// If `require_all` is true, the [`BundleRemover`] is only created if the entire bundle is present on the archetype.
///
/// # Safety
/// Caller must ensure that `archetype_id` is valid
#[inline]
pub(crate) unsafe fn new<T: Bundle>(
world: &'w mut World,
archetype_id: ArchetypeId,
require_all: bool,
) -> Option<Self> {
// SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too.
let mut registrator =
unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) };
let bundle_id = world
.bundles
.register_info::<T>(&mut registrator, &mut world.storages);
// SAFETY: we initialized this bundle_id in `init_info`, and caller ensures archetype is valid.
unsafe { Self::new_with_id(world, archetype_id, bundle_id, require_all) }
}
/// Creates a new [`BundleRemover`], if such a remover would do anything.
///
/// If `require_all` is true, the [`BundleRemover`] is only created if the entire bundle is present on the archetype.
///
/// # Safety
/// Caller must ensure that `bundle_id` exists in `world.bundles` and `archetype_id` is valid.
#[inline]
pub(crate) unsafe fn new_with_id(
world: &'w mut World,
archetype_id: ArchetypeId,
bundle_id: BundleId,
require_all: bool,
) -> Option<Self> {
let bundle_info = world.bundles.get_unchecked(bundle_id);
// SAFETY: Caller ensures archetype and bundle ids are correct.
let new_archetype_id = unsafe {
bundle_info.remove_bundle_from_archetype(
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
archetype_id,
!require_all,
)?
};
if new_archetype_id == archetype_id {
return None;
}
let (old_archetype, new_archetype) =
world.archetypes.get_2_mut(archetype_id, new_archetype_id);
let tables = if old_archetype.table_id() == new_archetype.table_id() {
None
} else {
let (old, new) = world
.storages
.tables
.get_2_mut(old_archetype.table_id(), new_archetype.table_id());
Some((old.into(), new.into()))
};
Some(Self {
bundle_info: bundle_info.into(),
new_archetype: new_archetype.into(),
old_archetype: old_archetype.into(),
old_and_new_table: tables,
world: world.as_unsafe_world_cell(),
})
}
/// This can be passed to [`remove`](Self::remove) as the `pre_remove` function if you don't want to do anything before removing.
pub fn empty_pre_remove(
_: &mut SparseSets,
_: Option<&mut Table>,
_: &Components,
_: &[ComponentId],
) -> (bool, ()) {
(true, ())
}
/// Performs the removal.
///
/// `pre_remove` should return a bool for if the components still need to be dropped.
///
/// # Safety
/// The `location` must have the same archetype as the remover.
#[inline]
pub(crate) unsafe fn remove<T: 'static>(
&mut self,
entity: Entity,
location: EntityLocation,
caller: MaybeLocation,
pre_remove: impl FnOnce(
&mut SparseSets,
Option<&mut Table>,
&Components,
&[ComponentId],
) -> (bool, T),
) -> (EntityLocation, T) {
// Hooks
// SAFETY: all bundle components exist in World
unsafe {
// SAFETY: We only keep access to archetype/bundle data.
let mut deferred_world = self.world.into_deferred();
let bundle_components_in_archetype = || {
self.bundle_info
.as_ref()
.iter_explicit_components()
.filter(|component_id| self.old_archetype.as_ref().contains(*component_id))
};
if self.old_archetype.as_ref().has_replace_observer() {
deferred_world.trigger_observers(
ON_REPLACE,
entity,
bundle_components_in_archetype(),
caller,
);
}
deferred_world.trigger_on_replace(
self.old_archetype.as_ref(),
entity,
bundle_components_in_archetype(),
caller,
RelationshipHookMode::Run,
);
if self.old_archetype.as_ref().has_remove_observer() {
deferred_world.trigger_observers(
ON_REMOVE,
entity,
bundle_components_in_archetype(),
caller,
);
}
deferred_world.trigger_on_remove(
self.old_archetype.as_ref(),
entity,
bundle_components_in_archetype(),
caller,
);
}
// SAFETY: We still have the cell, so this is unique, it doesn't conflict with other references, and we drop it shortly.
let world = unsafe { self.world.world_mut() };
let (needs_drop, pre_remove_result) = pre_remove(
&mut world.storages.sparse_sets,
self.old_and_new_table
.as_ref()
// SAFETY: There is no conflicting access for this scope.
.map(|(old, _)| unsafe { &mut *old.as_ptr() }),
&world.components,
self.bundle_info.as_ref().explicit_components(),
);
// Handle sparse set removes
for component_id in self.bundle_info.as_ref().iter_explicit_components() {
if self.old_archetype.as_ref().contains(component_id) {
world.removed_components.send(component_id, entity);
// Make sure to drop components stored in sparse sets.
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`.
if let Some(StorageType::SparseSet) =
self.old_archetype.as_ref().get_storage_type(component_id)
{
world
.storages
.sparse_sets
.get_mut(component_id)
// Set exists because the component existed on the entity
.unwrap()
// If it was already forgotten, it would not be in the set.
.remove(entity);
}
}
}
// Handle archetype change
let remove_result = self
.old_archetype
.as_mut()
.swap_remove(location.archetype_row);
// if an entity was moved into this entity's archetype row, update its archetype row
if let Some(swapped_entity) = remove_result.swapped_entity {
let swapped_location = world.entities.get(swapped_entity).unwrap();
world.entities.set(
swapped_entity.index(),
EntityLocation {
archetype_id: swapped_location.archetype_id,
archetype_row: location.archetype_row,
table_id: swapped_location.table_id,
table_row: swapped_location.table_row,
},
);
}
// Handle table change
let new_location = if let Some((mut old_table, mut new_table)) = self.old_and_new_table {
let move_result = if needs_drop {
// SAFETY: old_table_row exists
unsafe {
old_table
.as_mut()
.move_to_and_drop_missing_unchecked(location.table_row, new_table.as_mut())
}
} else {
// SAFETY: old_table_row exists
unsafe {
old_table.as_mut().move_to_and_forget_missing_unchecked(
location.table_row,
new_table.as_mut(),
)
}
};
// SAFETY: move_result.new_row is a valid position in new_archetype's table
let new_location = unsafe {
self.new_archetype
.as_mut()
.allocate(entity, move_result.new_row)
};
// if an entity was moved into this entity's table row, update its table row
if let Some(swapped_entity) = move_result.swapped_entity {
let swapped_location = world.entities.get(swapped_entity).unwrap();
world.entities.set(
swapped_entity.index(),
EntityLocation {
archetype_id: swapped_location.archetype_id,
archetype_row: swapped_location.archetype_row,
table_id: swapped_location.table_id,
table_row: location.table_row,
},
);
world.archetypes[swapped_location.archetype_id]
.set_entity_table_row(swapped_location.archetype_row, location.table_row);
}
new_location
} else {
// The tables are the same
self.new_archetype
.as_mut()
.allocate(entity, location.table_row)
};
// SAFETY: The entity is valid and has been moved to the new location already.
unsafe {
world.entities.set(entity.index(), new_location);
}
(new_location, pre_remove_result)
}
}
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
pub(crate) struct BundleSpawner<'w> {
world: UnsafeWorldCell<'w>,
@ -1455,7 +1736,7 @@ impl<'w> BundleSpawner<'w> {
InsertMode::Replace,
caller,
);
entities.set(entity.index(), location);
entities.set_spawn_despawn(entity.index(), location, caller, self.change_tick);
(location, after_effect)
};

View File

@ -1847,8 +1847,7 @@ mod tests {
let mut new = value.map_unchanged(|ptr| {
// SAFETY: The underlying type of `ptr` matches `reflect_from_ptr`.
let value = unsafe { reflect_from_ptr.as_reflect_mut(ptr) };
value
unsafe { reflect_from_ptr.as_reflect_mut(ptr) }
});
assert!(!new.is_changed());

View File

@ -418,6 +418,21 @@ use thiserror::Error;
/// println!("{message}");
/// }
/// }
///
/// ```
/// # Setting the clone behavior
///
/// You can specify how the [`Component`] is cloned when deriving it.
///
/// Your options are the functions and variants of [`ComponentCloneBehavior`]
/// See [Handlers section of `EntityClonerBuilder`](crate::entity::EntityClonerBuilder#handlers) to understand how this affects handler priority.
/// ```
/// # use bevy_ecs::prelude::*;
///
/// #[derive(Component)]
/// #[component(clone_behavior = Ignore)]
/// struct MyComponent;
///
/// ```
///
/// # Implementing the trait for foreign types
@ -494,15 +509,6 @@ pub trait Component: Send + Sync + 'static {
/// * For a component to be immutable, this type must be [`Immutable`].
type Mutability: ComponentMutability;
/// Called when registering this component, allowing mutable access to its [`ComponentHooks`].
#[deprecated(
since = "0.16.0",
note = "Use the individual hook methods instead (e.g., `Component::on_add`, etc.)"
)]
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.update_from_component::<Self>();
}
/// Gets the `on_add` [`ComponentHook`] for this [`Component`] if one is defined.
fn on_add() -> Option<ComponentHook> {
None
@ -559,6 +565,17 @@ pub trait Component: Send + Sync + 'static {
/// ```
///
/// Fields with `#[entities]` must implement [`MapEntities`](crate::entity::MapEntities).
///
/// Bevy provides various implementations of [`MapEntities`](crate::entity::MapEntities), so that arbitrary combinations like these are supported with `#[entities]`:
///
/// ```rust
/// # use bevy_ecs::{component::Component, entity::Entity};
/// #[derive(Component)]
/// struct Inventory {
/// #[entities]
/// items: Vec<Option<Entity>>
/// }
/// ```
#[inline]
fn map_entities<E: EntityMapper>(_this: &mut Self, _mapper: &mut E) {}
}
@ -668,7 +685,7 @@ pub struct HookContext {
/// This information is stored in the [`ComponentInfo`] of the associated component.
///
/// There is two ways of configuring hooks for a component:
/// 1. Defining the [`Component::register_component_hooks`] method (see [`Component`])
/// 1. Defining the relevant hooks on the [`Component`] implementation
/// 2. Using the [`World::register_component_hooks`] method
///
/// # Example 2
@ -1784,12 +1801,7 @@ impl<'w> ComponentsRegistrator<'w> {
.debug_checked_unwrap()
};
#[expect(
deprecated,
reason = "need to use this method until it is removed to ensure user defined components register hooks correctly"
)]
// TODO: Replace with `info.hooks.update_from_component::<T>();` once `Component::register_component_hooks` is removed
T::register_component_hooks(&mut info.hooks);
info.hooks.update_from_component::<T>();
info.required_components = required_components;
}
@ -2049,7 +2061,7 @@ impl Components {
}
/// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present.
/// This will return `None` only if the id is neither regisered nor queued to be registered.
/// This will return `None` only if the id is neither registered nor queued to be registered.
///
/// Currently, the [`Cow`] will be [`Cow::Owned`] if and only if the component is queued. It will be [`Cow::Borrowed`] otherwise.
///
@ -2073,7 +2085,7 @@ impl Components {
}
/// Gets the name of the component with this [`ComponentId`] if it is present.
/// This will return `None` only if the id is neither regisered nor queued to be registered.
/// This will return `None` only if the id is neither registered nor queued to be registered.
///
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
#[inline]

View File

@ -324,16 +324,10 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::component::{StorageType, ComponentCloneBehavior, Mutable};
/// #[derive(Clone)]
/// #[derive(Clone, Component)]
/// #[component(clone_behavior = clone::<Self>())]
/// struct SomeComponent;
///
/// impl Component for SomeComponent {
/// const STORAGE_TYPE: StorageType = StorageType::Table;
/// type Mutability = Mutable;
/// fn clone_behavior() -> ComponentCloneBehavior {
/// ComponentCloneBehavior::clone::<Self>()
/// }
/// }
/// ```
///
/// # Clone Behaviors

View File

@ -1,16 +1,21 @@
pub use bevy_ecs_macros::MapEntities;
use indexmap::IndexSet;
use crate::{
entity::{hash_map::EntityHashMap, Entity},
identifier::masks::{IdentifierMask, HIGH_MASK},
world::World,
};
use alloc::{collections::VecDeque, vec::Vec};
use alloc::{
collections::{BTreeSet, VecDeque},
vec::Vec,
};
use bevy_platform::collections::HashSet;
use core::hash::BuildHasher;
use core::{hash::BuildHasher, mem};
use smallvec::SmallVec;
use super::EntityIndexSet;
/// Operation to map all contained [`Entity`] fields in a type to new values.
///
/// As entity IDs are valid only for the [`World`] they're sourced from, using [`Entity`]
@ -59,42 +64,87 @@ impl MapEntities for Entity {
}
}
impl MapEntities for Option<Entity> {
impl<T: MapEntities> MapEntities for Option<T> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
if let Some(entity) = self {
*entity = entity_mapper.get_mapped(*entity);
if let Some(entities) = self {
entities.map_entities(entity_mapper);
}
}
}
impl<S: BuildHasher + Default> MapEntities for HashSet<Entity, S> {
impl<T: MapEntities + Eq + core::hash::Hash, S: BuildHasher + Default> MapEntities
for HashSet<T, S>
{
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
*self = self.drain().map(|e| entity_mapper.get_mapped(e)).collect();
*self = self
.drain()
.map(|mut entities| {
entities.map_entities(entity_mapper);
entities
})
.collect();
}
}
impl MapEntities for Vec<Entity> {
impl<T: MapEntities + Eq + core::hash::Hash, S: BuildHasher + Default> MapEntities
for IndexSet<T, S>
{
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for entity in self.iter_mut() {
*entity = entity_mapper.get_mapped(*entity);
*self = self
.drain(..)
.map(|mut entities| {
entities.map_entities(entity_mapper);
entities
})
.collect();
}
}
impl MapEntities for EntityIndexSet {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
*self = self
.drain(..)
.map(|e| entity_mapper.get_mapped(e))
.collect();
}
}
impl<T: MapEntities + Ord> MapEntities for BTreeSet<T> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
*self = mem::take(self)
.into_iter()
.map(|mut entities| {
entities.map_entities(entity_mapper);
entities
})
.collect();
}
}
impl<T: MapEntities> MapEntities for Vec<T> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for entities in self.iter_mut() {
entities.map_entities(entity_mapper);
}
}
}
impl MapEntities for VecDeque<Entity> {
impl<T: MapEntities> MapEntities for VecDeque<T> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for entity in self.iter_mut() {
*entity = entity_mapper.get_mapped(*entity);
for entities in self.iter_mut() {
entities.map_entities(entity_mapper);
}
}
}
impl<A: smallvec::Array<Item = Entity>> MapEntities for SmallVec<A> {
impl<T: MapEntities, A: smallvec::Array<Item = T>> MapEntities for SmallVec<A> {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for entity in self.iter_mut() {
*entity = entity_mapper.get_mapped(*entity);
for entities in self.iter_mut() {
entities.map_entities(entity_mapper);
}
}
}
/// An implementor of this trait knows how to map an [`Entity`] into another [`Entity`].
///
/// Usually this is done by using an [`EntityHashMap<Entity>`] to map source entities
@ -120,7 +170,7 @@ impl<A: smallvec::Array<Item = Entity>> MapEntities for SmallVec<A> {
/// fn get_mapped(&mut self, entity: Entity) -> Entity {
/// self.map.get(&entity).copied().unwrap_or(entity)
/// }
///
///
/// fn set_mapped(&mut self, source: Entity, target: Entity) {
/// self.map.insert(source, target);
/// }
@ -177,12 +227,10 @@ impl EntityMapper for SceneEntityMapper<'_> {
// this new entity reference is specifically designed to never represent any living entity
let new = Entity::from_raw_and_generation(
self.dead_start.index(),
IdentifierMask::inc_masked_high_by(self.dead_start.generation, self.generations),
self.dead_start.row(),
self.dead_start.generation.after_versions(self.generations),
);
// Prevent generations counter from being a greater value than HIGH_MASK.
self.generations = (self.generations + 1) & HIGH_MASK;
self.generations = self.generations.wrapping_add(1);
self.map.insert(source, new);
@ -280,6 +328,7 @@ impl<'m> SceneEntityMapper<'m> {
#[cfg(test)]
mod tests {
use crate::{
entity::{Entity, EntityHashMap, EntityMapper, SceneEntityMapper},
world::World,
@ -287,14 +336,11 @@ mod tests {
#[test]
fn entity_mapper() {
const FIRST_IDX: u32 = 1;
const SECOND_IDX: u32 = 2;
let mut map = EntityHashMap::default();
let mut world = World::new();
let mut mapper = SceneEntityMapper::new(&mut map, &mut world);
let mapped_ent = Entity::from_raw(FIRST_IDX);
let mapped_ent = Entity::from_raw_u32(1).unwrap();
let dead_ref = mapper.get_mapped(mapped_ent);
assert_eq!(
@ -303,7 +349,7 @@ mod tests {
"should persist the allocated mapping from the previous line"
);
assert_eq!(
mapper.get_mapped(Entity::from_raw(SECOND_IDX)).index(),
mapper.get_mapped(Entity::from_raw_u32(2).unwrap()).index(),
dead_ref.index(),
"should re-use the same index for further dead refs"
);
@ -321,7 +367,7 @@ mod tests {
let mut world = World::new();
let dead_ref = SceneEntityMapper::world_scope(&mut map, &mut world, |_, mapper| {
mapper.get_mapped(Entity::from_raw(0))
mapper.get_mapped(Entity::from_raw_u32(0).unwrap())
});
// Next allocated entity should be a further generation on the same index

File diff suppressed because it is too large Load Diff

View File

@ -7,22 +7,17 @@ use crate::{
world::{error::EntityMutableFetchError, World},
};
use super::{default_error_handler, BevyError, ErrorContext};
use super::{BevyError, ErrorContext, ErrorHandler};
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
/// Takes a [`Command`] that potentially returns a Result and uses a given error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
pub trait HandleError<Out = ()> {
pub trait HandleError<Out = ()>: Send + 'static {
/// Takes a [`Command`] that returns a Result and uses a given error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
fn handle_error_with(self, error_handler: fn(BevyError, ErrorContext)) -> impl Command;
fn handle_error_with(self, error_handler: ErrorHandler) -> impl Command;
/// Takes a [`Command`] that returns a Result and uses the default error handler function to convert it into
/// a [`Command`] that internally handles an error if it occurs and returns `()`.
fn handle_error(self) -> impl Command
where
Self: Sized,
{
self.handle_error_with(default_error_handler())
}
fn handle_error(self) -> impl Command;
}
impl<C, T, E> HandleError<Result<T, E>> for C
@ -30,7 +25,7 @@ where
C: Command<Result<T, E>>,
E: Into<BevyError>,
{
fn handle_error_with(self, error_handler: fn(BevyError, ErrorContext)) -> impl Command {
fn handle_error_with(self, error_handler: ErrorHandler) -> impl Command {
move |world: &mut World| match self.apply(world) {
Ok(_) => {}
Err(err) => (error_handler)(
@ -41,6 +36,18 @@ where
),
}
}
fn handle_error(self) -> impl Command {
move |world: &mut World| match self.apply(world) {
Ok(_) => {}
Err(err) => world.default_error_handler()(
err.into(),
ErrorContext::Command {
name: type_name::<C>().into(),
},
),
}
}
}
impl<C> HandleError<Never> for C
@ -52,6 +59,13 @@ where
self.apply(world);
}
}
#[inline]
fn handle_error(self) -> impl Command {
move |world: &mut World| {
self.apply(world);
}
}
}
impl<C> HandleError for C
@ -63,10 +77,7 @@ where
self
}
#[inline]
fn handle_error(self) -> impl Command
where
Self: Sized,
{
fn handle_error(self) -> impl Command {
self
}
}

View File

@ -1,9 +1,8 @@
#[cfg(feature = "configurable_error_handler")]
use bevy_platform::sync::OnceLock;
use core::fmt::Display;
use crate::{component::Tick, error::BevyError};
use crate::{component::Tick, error::BevyError, prelude::Resource};
use alloc::borrow::Cow;
use derive_more::derive::{Deref, DerefMut};
/// Context for a [`BevyError`] to aid in debugging.
#[derive(Debug, PartialEq, Eq, Clone)]
@ -77,53 +76,6 @@ impl ErrorContext {
}
}
/// A global error handler. This can be set at startup, as long as it is set before
/// any uses. This should generally be configured _before_ initializing the app.
///
/// This should be set inside of your `main` function, before initializing the Bevy app.
/// The value of this error handler can be accessed using the [`default_error_handler`] function,
/// which calls [`OnceLock::get_or_init`] to get the value.
///
/// **Note:** this is only available when the `configurable_error_handler` feature of `bevy_ecs` (or `bevy`) is enabled!
///
/// # Example
///
/// ```
/// # use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, warn};
/// GLOBAL_ERROR_HANDLER.set(warn).expect("The error handler can only be set once, globally.");
/// // initialize Bevy App here
/// ```
///
/// To use this error handler in your app for custom error handling logic:
///
/// ```rust
/// use bevy_ecs::error::{default_error_handler, GLOBAL_ERROR_HANDLER, BevyError, ErrorContext, panic};
///
/// fn handle_errors(error: BevyError, ctx: ErrorContext) {
/// let error_handler = default_error_handler();
/// error_handler(error, ctx);
/// }
/// ```
///
/// # Warning
///
/// As this can *never* be overwritten, library code should never set this value.
#[cfg(feature = "configurable_error_handler")]
pub static GLOBAL_ERROR_HANDLER: OnceLock<fn(BevyError, ErrorContext)> = OnceLock::new();
/// The default error handler. This defaults to [`panic()`],
/// but if set, the [`GLOBAL_ERROR_HANDLER`] will be used instead, enabling error handler customization.
/// The `configurable_error_handler` feature must be enabled to change this from the panicking default behavior,
/// as there may be runtime overhead.
#[inline]
pub fn default_error_handler() -> fn(BevyError, ErrorContext) {
#[cfg(not(feature = "configurable_error_handler"))]
return panic;
#[cfg(feature = "configurable_error_handler")]
return *GLOBAL_ERROR_HANDLER.get_or_init(|| panic);
}
macro_rules! inner {
($call:path, $e:ident, $c:ident) => {
$call!(
@ -135,6 +87,25 @@ macro_rules! inner {
};
}
/// Defines how Bevy reacts to errors.
pub type ErrorHandler = fn(BevyError, ErrorContext);
/// Error handler to call when an error is not handled otherwise.
/// Defaults to [`panic()`].
///
/// When updated while a [`Schedule`] is running, it doesn't take effect for
/// that schedule until it's completed.
///
/// [`Schedule`]: crate::schedule::Schedule
#[derive(Resource, Deref, DerefMut, Copy, Clone)]
pub struct DefaultErrorHandler(pub ErrorHandler);
impl Default for DefaultErrorHandler {
fn default() -> Self {
Self(panic)
}
}
/// Error handler that panics with the system error.
#[track_caller]
#[inline]

View File

@ -7,8 +7,9 @@
//! All [`BevyError`]s returned by a system, observer or command are handled by an "error handler". By default, the
//! [`panic`] error handler function is used, resulting in a panic with the error message attached.
//!
//! You can change the default behavior by registering a custom error handler.
//! Modify the [`GLOBAL_ERROR_HANDLER`] value to set a custom error handler function for your entire app.
//! You can change the default behavior by registering a custom error handler:
//! Use [`DefaultErrorHandler`] to set a custom error handler function for a world,
//! or `App::set_error_handler` for a whole app.
//! In practice, this is generally feature-flagged: panicking or loudly logging errors in development,
//! and quietly logging or ignoring them in production to avoid crashing the app.
//!
@ -33,10 +34,8 @@
//! The [`ErrorContext`] allows you to access additional details relevant to providing
//! context surrounding the error such as the system's [`name`] in your error messages.
//!
//! Remember to turn on the `configurable_error_handler` feature to set a global error handler!
//!
//! ```rust, ignore
//! use bevy_ecs::error::{GLOBAL_ERROR_HANDLER, BevyError, ErrorContext};
//! use bevy_ecs::error::{BevyError, ErrorContext, DefaultErrorHandler};
//! use log::trace;
//!
//! fn my_error_handler(error: BevyError, ctx: ErrorContext) {
@ -48,10 +47,9 @@
//! }
//!
//! fn main() {
//! // This requires the "configurable_error_handler" feature to be enabled to be in scope.
//! GLOBAL_ERROR_HANDLER.set(my_error_handler).expect("The error handler can only be set once.");
//!
//! // Initialize your Bevy App here
//! let mut world = World::new();
//! world.insert_resource(DefaultErrorHandler(my_error_handler));
//! // Use your world here
//! }
//! ```
//!

View File

@ -24,8 +24,13 @@ pub use mut_iterators::{EventMutIterator, EventMutIteratorWithId};
pub use mutator::EventMutator;
pub use reader::EventReader;
pub use registry::{EventRegistry, ShouldUpdateEvents};
#[expect(
deprecated,
reason = "`EventUpdates` was renamed to `EventUpdateSystems`."
)]
pub use update::{
event_update_condition, event_update_system, signal_event_update_system, EventUpdates,
event_update_condition, event_update_system, signal_event_update_system, EventUpdateSystems,
EventUpdates,
};
pub use writer::EventWriter;

View File

@ -13,7 +13,11 @@ use super::registry::ShouldUpdateEvents;
#[doc(hidden)]
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventUpdates;
pub struct EventUpdateSystems;
/// Deprecated alias for [`EventUpdateSystems`].
#[deprecated(since = "0.17.0", note = "Renamed to `EventUpdateSystems`.")]
pub type EventUpdates = EventUpdateSystems;
/// Signals the [`event_update_system`] to run after `FixedUpdate` systems.
///

View File

@ -98,38 +98,4 @@ impl<'w, E: Event> EventWriter<'w, E> {
{
self.events.send_default()
}
/// Sends an `event`, which can later be read by [`EventReader`](super::EventReader)s.
/// This method returns the [ID](`EventId`) of the sent `event`.
///
/// See [`Events`] for details.
#[deprecated(since = "0.16.0", note = "Use `EventWriter::write` instead.")]
#[track_caller]
pub fn send(&mut self, event: E) -> EventId<E> {
self.write(event)
}
/// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
/// This is more efficient than sending each event individually.
/// This method returns the [IDs](`EventId`) of the sent `events`.
///
/// See [`Events`] for details.
#[deprecated(since = "0.16.0", note = "Use `EventWriter::write_batch` instead.")]
#[track_caller]
pub fn send_batch(&mut self, events: impl IntoIterator<Item = E>) -> SendBatchIds<E> {
self.write_batch(events)
}
/// Sends the default value of the event. Useful when the event is an empty struct.
/// This method returns the [ID](`EventId`) of the sent `event`.
///
/// See [`Events`] for details.
#[deprecated(since = "0.16.0", note = "Use `EventWriter::write_default` instead.")]
#[track_caller]
pub fn send_default(&mut self) -> EventId<E>
where
E: Default,
{
self.write_default()
}
}

View File

@ -106,13 +106,6 @@ impl ChildOf {
pub fn parent(&self) -> Entity {
self.0
}
/// The parent entity of this child entity.
#[deprecated(since = "0.16.0", note = "Use child_of.parent() instead")]
#[inline]
pub fn get(&self) -> Entity {
self.0
}
}
// TODO: We need to impl either FromWorld or Default so ChildOf can be registered as Reflect.
@ -281,6 +274,12 @@ impl<'w> EntityWorldMut<'w> {
self.add_related::<ChildOf>(children)
}
/// Removes all the children from this entity.
/// See also [`clear_related`](Self::clear_related)
pub fn clear_children(&mut self) -> &mut Self {
self.clear_related::<ChildOf>()
}
/// Insert children at specific index.
/// See also [`insert_related`](Self::insert_related).
pub fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self {
@ -338,20 +337,6 @@ impl<'w> EntityWorldMut<'w> {
});
self
}
/// Removes the [`ChildOf`] component, if it exists.
#[deprecated(since = "0.16.0", note = "Use entity_mut.remove::<ChildOf>()")]
pub fn remove_parent(&mut self) -> &mut Self {
self.remove::<ChildOf>();
self
}
/// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists.
#[deprecated(since = "0.16.0", note = "Use entity_mut.insert(ChildOf(entity))")]
pub fn set_parent(&mut self, parent: Entity) -> &mut Self {
self.insert(ChildOf(parent));
self
}
}
impl<'a> EntityCommands<'a> {
@ -369,6 +354,12 @@ impl<'a> EntityCommands<'a> {
self.add_related::<ChildOf>(children)
}
/// Removes all the children from this entity.
/// See also [`clear_related`](Self::clear_related)
pub fn clear_children(&mut self) -> &mut Self {
self.clear_related::<ChildOf>()
}
/// Insert children at specific index.
/// See also [`insert_related`](Self::insert_related).
pub fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self {
@ -422,20 +413,6 @@ impl<'a> EntityCommands<'a> {
self.with_related::<ChildOf>(bundle);
self
}
/// Removes the [`ChildOf`] component, if it exists.
#[deprecated(since = "0.16.0", note = "Use entity_commands.remove::<ChildOf>()")]
pub fn remove_parent(&mut self) -> &mut Self {
self.remove::<ChildOf>();
self
}
/// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists.
#[deprecated(since = "0.16.0", note = "Use entity_commands.insert(ChildOf(entity))")]
pub fn set_parent(&mut self, parent: Entity) -> &mut Self {
self.insert(ChildOf(parent));
self
}
}
/// An `on_insert` component hook that when run, will validate that the parent of a given entity
@ -499,7 +476,7 @@ pub fn validate_parent_has_component<C: Component>(
#[macro_export]
macro_rules! children {
[$($child:expr),*$(,)?] => {
$crate::hierarchy::Children::spawn(($($crate::spawn::Spawn($child)),*))
$crate::hierarchy::Children::spawn($crate::recursive_spawn!($($child),*))
};
}
@ -656,6 +633,43 @@ mod tests {
);
}
// regression test for https://github.com/bevyengine/bevy/pull/19134
#[test]
fn insert_children_index_bound() {
let mut world = World::new();
let child1 = world.spawn_empty().id();
let child2 = world.spawn_empty().id();
let child3 = world.spawn_empty().id();
let child4 = world.spawn_empty().id();
let mut entity_world_mut = world.spawn_empty();
let first_children = entity_world_mut.add_children(&[child1, child2]).id();
let hierarchy = get_hierarchy(&world, first_children);
assert_eq!(
hierarchy,
Node::new_with(first_children, vec![Node::new(child1), Node::new(child2)])
);
let root = world
.entity_mut(first_children)
.insert_children(usize::MAX, &[child3, child4])
.id();
let hierarchy = get_hierarchy(&world, root);
assert_eq!(
hierarchy,
Node::new_with(
root,
vec![
Node::new(child1),
Node::new(child2),
Node::new(child3),
Node::new(child4),
]
)
);
}
#[test]
fn remove_children() {
let mut world = World::new();
@ -719,6 +733,39 @@ mod tests {
assert_eq!(world.entity(id).get::<Children>().unwrap().len(), 2,);
}
#[test]
fn spawn_many_children() {
let mut world = World::new();
// 12 children should result in a flat tuple
let id = world
.spawn(children![(), (), (), (), (), (), (), (), (), (), (), ()])
.id();
assert_eq!(world.entity(id).get::<Children>().unwrap().len(), 12,);
// 13 will start nesting, but should nonetheless produce a flat hierarchy
let id = world
.spawn(children![
(),
(),
(),
(),
(),
(),
(),
(),
(),
(),
(),
(),
(),
])
.id();
assert_eq!(world.entity(id).get::<Children>().unwrap().len(), 13,);
}
#[test]
fn replace_children() {
let mut world = World::new();

View File

@ -1,29 +0,0 @@
//! Error types for [`super::Identifier`] conversions. An ID can be converted
//! to various kinds, but these can fail if they are not valid forms of those
//! kinds. The error type in this module encapsulates the various failure modes.
use core::fmt;
/// An Error type for [`super::Identifier`], mostly for providing error
/// handling for conversions of an ID to a type abstracting over the ID bits.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum IdentifierError {
/// A given ID has an invalid value for initializing to a [`crate::identifier::Identifier`].
InvalidIdentifier,
/// A given ID has an invalid configuration of bits for converting to an [`crate::entity::Entity`].
InvalidEntityId(u64),
}
impl fmt::Display for IdentifierError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidIdentifier => write!(
f,
"The given id contains a zero value high component, which is invalid"
),
Self::InvalidEntityId(_) => write!(f, "The given id is not a valid entity."),
}
}
}
impl core::error::Error for IdentifierError {}

View File

@ -1,11 +0,0 @@
/// The kinds of ID that [`super::Identifier`] can represent. Each
/// variant imposes different usages of the low/high segments
/// of the ID.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum IdKind {
/// An ID variant that is compatible with [`crate::entity::Entity`].
Entity = 0,
/// A future ID variant.
Placeholder = 0b1000_0000,
}

View File

@ -1,233 +0,0 @@
use core::num::NonZero;
use super::kinds::IdKind;
/// Mask for extracting the value portion of a 32-bit high segment. This
/// yields 31-bits of total value, as the final bit (the most significant)
/// is reserved as a flag bit. Can be negated to extract the flag bit.
pub(crate) const HIGH_MASK: u32 = 0x7FFF_FFFF;
/// Abstraction over masks needed to extract values/components of an [`super::Identifier`].
pub(crate) struct IdentifierMask;
impl IdentifierMask {
/// Returns the low component from a `u64` value
#[inline(always)]
pub(crate) const fn get_low(value: u64) -> u32 {
// This will truncate to the lowest 32 bits
value as u32
}
/// Returns the high component from a `u64` value
#[inline(always)]
pub(crate) const fn get_high(value: u64) -> u32 {
// This will discard the lowest 32 bits
(value >> u32::BITS) as u32
}
/// Pack a low and high `u32` values into a single `u64` value.
#[inline(always)]
pub(crate) const fn pack_into_u64(low: u32, high: u32) -> u64 {
((high as u64) << u32::BITS) | (low as u64)
}
/// Pack the [`IdKind`] bits into a high segment.
#[inline(always)]
pub(crate) const fn pack_kind_into_high(value: u32, kind: IdKind) -> u32 {
value | ((kind as u32) << 24)
}
/// Extract the value component from a high segment of an [`super::Identifier`].
#[inline(always)]
pub(crate) const fn extract_value_from_high(value: u32) -> u32 {
value & HIGH_MASK
}
/// Extract the ID kind component from a high segment of an [`super::Identifier`].
#[inline(always)]
pub(crate) const fn extract_kind_from_high(value: u32) -> IdKind {
// The negated HIGH_MASK will extract just the bit we need for kind.
let kind_mask = !HIGH_MASK;
let bit = value & kind_mask;
if bit == kind_mask {
IdKind::Placeholder
} else {
IdKind::Entity
}
}
/// Offsets a masked generation value by the specified amount, wrapping to 1 instead of 0.
/// Will never be greater than [`HIGH_MASK`] or less than `1`, and increments are masked to
/// never be greater than [`HIGH_MASK`].
#[inline(always)]
pub(crate) const fn inc_masked_high_by(lhs: NonZero<u32>, rhs: u32) -> NonZero<u32> {
let lo = (lhs.get() & HIGH_MASK).wrapping_add(rhs & HIGH_MASK);
// Checks high 32 bit for whether we have overflowed 31 bits.
let overflowed = lo >> 31;
// SAFETY:
// - Adding the overflow flag will offset overflows to start at 1 instead of 0
// - The sum of `0x7FFF_FFFF` + `u32::MAX` + 1 (overflow) == `0x7FFF_FFFF`
// - If the operation doesn't overflow at 31 bits, no offsetting takes place
unsafe { NonZero::<u32>::new_unchecked(lo.wrapping_add(overflowed) & HIGH_MASK) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_u64_parts() {
// Two distinct bit patterns per low/high component
let value: u64 = 0x7FFF_FFFF_0000_000C;
assert_eq!(IdentifierMask::get_low(value), 0x0000_000C);
assert_eq!(IdentifierMask::get_high(value), 0x7FFF_FFFF);
}
#[test]
fn extract_kind() {
// All bits are ones.
let high: u32 = 0xFFFF_FFFF;
assert_eq!(
IdentifierMask::extract_kind_from_high(high),
IdKind::Placeholder
);
// Second and second to last bits are ones.
let high: u32 = 0x4000_0002;
assert_eq!(IdentifierMask::extract_kind_from_high(high), IdKind::Entity);
}
#[test]
fn extract_high_value() {
// All bits are ones.
let high: u32 = 0xFFFF_FFFF;
// Excludes the most significant bit as that is a flag bit.
assert_eq!(IdentifierMask::extract_value_from_high(high), 0x7FFF_FFFF);
// Start bit and end bit are ones.
let high: u32 = 0x8000_0001;
assert_eq!(IdentifierMask::extract_value_from_high(high), 0x0000_0001);
// Classic bit pattern.
let high: u32 = 0xDEAD_BEEF;
assert_eq!(IdentifierMask::extract_value_from_high(high), 0x5EAD_BEEF);
}
#[test]
fn pack_kind_bits() {
// All bits are ones expect the most significant bit, which is zero
let high: u32 = 0x7FFF_FFFF;
assert_eq!(
IdentifierMask::pack_kind_into_high(high, IdKind::Placeholder),
0xFFFF_FFFF
);
// Arbitrary bit pattern
let high: u32 = 0x00FF_FF00;
assert_eq!(
IdentifierMask::pack_kind_into_high(high, IdKind::Entity),
// Remains unchanged as before
0x00FF_FF00
);
// Bit pattern that almost spells a word
let high: u32 = 0x40FF_EEEE;
assert_eq!(
IdentifierMask::pack_kind_into_high(high, IdKind::Placeholder),
0xC0FF_EEEE // Milk and no sugar, please.
);
}
#[test]
fn pack_into_u64() {
let high: u32 = 0x7FFF_FFFF;
let low: u32 = 0x0000_00CC;
assert_eq!(
IdentifierMask::pack_into_u64(low, high),
0x7FFF_FFFF_0000_00CC
);
}
#[test]
fn incrementing_masked_nonzero_high_is_safe() {
// Adding from lowest value with lowest to highest increment
// No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK
assert_eq!(
NonZero::<u32>::MIN,
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MIN, 0)
);
assert_eq!(
NonZero::<u32>::new(2).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MIN, 1)
);
assert_eq!(
NonZero::<u32>::new(3).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MIN, 2)
);
assert_eq!(
NonZero::<u32>::MIN,
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MIN, HIGH_MASK)
);
assert_eq!(
NonZero::<u32>::MIN,
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MIN, u32::MAX)
);
// Adding from absolute highest value with lowest to highest increment
// No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK
assert_eq!(
NonZero::<u32>::new(HIGH_MASK).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MAX, 0)
);
assert_eq!(
NonZero::<u32>::MIN,
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MAX, 1)
);
assert_eq!(
NonZero::<u32>::new(2).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MAX, 2)
);
assert_eq!(
NonZero::<u32>::new(HIGH_MASK).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MAX, HIGH_MASK)
);
assert_eq!(
NonZero::<u32>::new(HIGH_MASK).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::MAX, u32::MAX)
);
// Adding from actual highest value with lowest to highest increment
// No result should ever be greater than 0x7FFF_FFFF or HIGH_MASK
assert_eq!(
NonZero::<u32>::new(HIGH_MASK).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::new(HIGH_MASK).unwrap(), 0)
);
assert_eq!(
NonZero::<u32>::MIN,
IdentifierMask::inc_masked_high_by(NonZero::<u32>::new(HIGH_MASK).unwrap(), 1)
);
assert_eq!(
NonZero::<u32>::new(2).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::new(HIGH_MASK).unwrap(), 2)
);
assert_eq!(
NonZero::<u32>::new(HIGH_MASK).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::new(HIGH_MASK).unwrap(), HIGH_MASK)
);
assert_eq!(
NonZero::<u32>::new(HIGH_MASK).unwrap(),
IdentifierMask::inc_masked_high_by(NonZero::<u32>::new(HIGH_MASK).unwrap(), u32::MAX)
);
}
}

View File

@ -1,249 +0,0 @@
//! A module for the unified [`Identifier`] ID struct, for use as a representation
//! of multiple types of IDs in a single, packed type. Allows for describing an [`crate::entity::Entity`],
//! or other IDs that can be packed and expressed within a `u64` sized type.
//! [`Identifier`]s cannot be created directly, only able to be converted from other
//! compatible IDs.
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use self::{error::IdentifierError, kinds::IdKind, masks::IdentifierMask};
use core::{hash::Hash, num::NonZero};
pub mod error;
pub(crate) mod kinds;
pub(crate) mod masks;
/// A unified identifier for all entity and similar IDs.
///
/// Has the same size as a `u64` integer, but the layout is split between a 32-bit low
/// segment, a 31-bit high segment, and the significant bit reserved as type flags to denote
/// entity kinds.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(opaque))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Hash, PartialEq, Clone))]
// Alignment repr necessary to allow LLVM to better output
// optimized codegen for `to_bits`, `PartialEq` and `Ord`.
#[repr(C, align(8))]
pub struct Identifier {
// Do not reorder the fields here. The ordering is explicitly used by repr(C)
// to make this struct equivalent to a u64.
#[cfg(target_endian = "little")]
low: u32,
high: NonZero<u32>,
#[cfg(target_endian = "big")]
low: u32,
}
impl Identifier {
/// Construct a new [`Identifier`]. The `high` parameter is masked with the
/// `kind` so to pack the high value and bit flags into the same field.
#[inline(always)]
pub const fn new(low: u32, high: u32, kind: IdKind) -> Result<Self, IdentifierError> {
// the high bits are masked to cut off the most significant bit
// as these are used for the type flags. This means that the high
// portion is only 31 bits, but this still provides 2^31
// values/kinds/ids that can be stored in this segment.
let masked_value = IdentifierMask::extract_value_from_high(high);
let packed_high = IdentifierMask::pack_kind_into_high(masked_value, kind);
// If the packed high component ends up being zero, that means that we tried
// to initialize an Identifier into an invalid state.
if packed_high == 0 {
Err(IdentifierError::InvalidIdentifier)
} else {
// SAFETY: The high value has been checked to ensure it is never
// zero.
unsafe {
Ok(Self {
low,
high: NonZero::<u32>::new_unchecked(packed_high),
})
}
}
}
/// Returns the value of the low segment of the [`Identifier`].
#[inline(always)]
pub const fn low(self) -> u32 {
self.low
}
/// Returns the value of the high segment of the [`Identifier`]. This
/// does not apply any masking.
#[inline(always)]
pub const fn high(self) -> NonZero<u32> {
self.high
}
/// Returns the masked value of the high segment of the [`Identifier`].
/// Does not include the flag bits.
#[inline(always)]
pub const fn masked_high(self) -> u32 {
IdentifierMask::extract_value_from_high(self.high.get())
}
/// Returns the kind of [`Identifier`] from the high segment.
#[inline(always)]
pub const fn kind(self) -> IdKind {
IdentifierMask::extract_kind_from_high(self.high.get())
}
/// Convert the [`Identifier`] into a `u64`.
#[inline(always)]
pub const fn to_bits(self) -> u64 {
IdentifierMask::pack_into_u64(self.low, self.high.get())
}
/// Convert a `u64` into an [`Identifier`].
///
/// # Panics
///
/// This method will likely panic if given `u64` values that did not come from [`Identifier::to_bits`].
#[inline(always)]
pub const fn from_bits(value: u64) -> Self {
let id = Self::try_from_bits(value);
match id {
Ok(id) => id,
Err(_) => panic!("Attempted to initialize invalid bits as an id"),
}
}
/// Convert a `u64` into an [`Identifier`].
///
/// This method is the fallible counterpart to [`Identifier::from_bits`].
#[inline(always)]
pub const fn try_from_bits(value: u64) -> Result<Self, IdentifierError> {
let high = NonZero::<u32>::new(IdentifierMask::get_high(value));
match high {
Some(high) => Ok(Self {
low: IdentifierMask::get_low(value),
high,
}),
None => Err(IdentifierError::InvalidIdentifier),
}
}
}
// By not short-circuiting in comparisons, we get better codegen.
// See <https://github.com/rust-lang/rust/issues/117800>
impl PartialEq for Identifier {
#[inline]
fn eq(&self, other: &Self) -> bool {
// By using `to_bits`, the codegen can be optimized out even
// further potentially. Relies on the correct alignment/field
// order of `Entity`.
self.to_bits() == other.to_bits()
}
}
impl Eq for Identifier {}
// The derive macro codegen output is not optimal and can't be optimized as well
// by the compiler. This impl resolves the issue of non-optimal codegen by relying
// on comparing against the bit representation of `Entity` instead of comparing
// the fields. The result is then LLVM is able to optimize the codegen for Entity
// far beyond what the derive macro can.
// See <https://github.com/rust-lang/rust/issues/106107>
impl PartialOrd for Identifier {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
// Make use of our `Ord` impl to ensure optimal codegen output
Some(self.cmp(other))
}
}
// The derive macro codegen output is not optimal and can't be optimized as well
// by the compiler. This impl resolves the issue of non-optimal codegen by relying
// on comparing against the bit representation of `Entity` instead of comparing
// the fields. The result is then LLVM is able to optimize the codegen for Entity
// far beyond what the derive macro can.
// See <https://github.com/rust-lang/rust/issues/106107>
impl Ord for Identifier {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
// This will result in better codegen for ordering comparisons, plus
// avoids pitfalls with regards to macro codegen relying on property
// position when we want to compare against the bit representation.
self.to_bits().cmp(&other.to_bits())
}
}
impl Hash for Identifier {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_bits().hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn id_construction() {
let id = Identifier::new(12, 55, IdKind::Entity).unwrap();
assert_eq!(id.low(), 12);
assert_eq!(id.high().get(), 55);
assert_eq!(
IdentifierMask::extract_kind_from_high(id.high().get()),
IdKind::Entity
);
}
#[test]
fn from_bits() {
// This high value should correspond to the max high() value
// and also Entity flag.
let high = 0x7FFFFFFF;
let low = 0xC;
let bits: u64 = (high << u32::BITS) | low;
let id = Identifier::try_from_bits(bits).unwrap();
assert_eq!(id.to_bits(), 0x7FFFFFFF0000000C);
assert_eq!(id.low(), low as u32);
assert_eq!(id.high().get(), 0x7FFFFFFF);
assert_eq!(
IdentifierMask::extract_kind_from_high(id.high().get()),
IdKind::Entity
);
}
#[rustfmt::skip]
#[test]
#[expect(
clippy::nonminimal_bool,
reason = "This intentionally tests all possible comparison operators as separate functions; thus, we don't want to rewrite these comparisons to use different operators."
)]
fn id_comparison() {
assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() == Identifier::new(123, 456, IdKind::Entity).unwrap());
assert!(Identifier::new(123, 456, IdKind::Placeholder).unwrap() == Identifier::new(123, 456, IdKind::Placeholder).unwrap());
assert!(Identifier::new(123, 789, IdKind::Entity).unwrap() != Identifier::new(123, 456, IdKind::Entity).unwrap());
assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(123, 789, IdKind::Entity).unwrap());
assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(456, 123, IdKind::Entity).unwrap());
assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() != Identifier::new(123, 456, IdKind::Placeholder).unwrap());
// ordering is by flag then high then by low
assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() >= Identifier::new(123, 456, IdKind::Entity).unwrap());
assert!(Identifier::new(123, 456, IdKind::Entity).unwrap() <= Identifier::new(123, 456, IdKind::Entity).unwrap());
assert!(!(Identifier::new(123, 456, IdKind::Entity).unwrap() < Identifier::new(123, 456, IdKind::Entity).unwrap()));
assert!(!(Identifier::new(123, 456, IdKind::Entity).unwrap() > Identifier::new(123, 456, IdKind::Entity).unwrap()));
assert!(Identifier::new(9, 1, IdKind::Entity).unwrap() < Identifier::new(1, 9, IdKind::Entity).unwrap());
assert!(Identifier::new(1, 9, IdKind::Entity).unwrap() > Identifier::new(9, 1, IdKind::Entity).unwrap());
assert!(Identifier::new(9, 1, IdKind::Entity).unwrap() < Identifier::new(9, 1, IdKind::Placeholder).unwrap());
assert!(Identifier::new(1, 9, IdKind::Placeholder).unwrap() > Identifier::new(1, 9, IdKind::Entity).unwrap());
assert!(Identifier::new(1, 1, IdKind::Entity).unwrap() < Identifier::new(2, 1, IdKind::Entity).unwrap());
assert!(Identifier::new(1, 1, IdKind::Entity).unwrap() <= Identifier::new(2, 1, IdKind::Entity).unwrap());
assert!(Identifier::new(2, 2, IdKind::Entity).unwrap() > Identifier::new(1, 2, IdKind::Entity).unwrap());
assert!(Identifier::new(2, 2, IdKind::Entity).unwrap() >= Identifier::new(1, 2, IdKind::Entity).unwrap());
}
}

View File

@ -39,7 +39,6 @@ pub mod entity_disabling;
pub mod error;
pub mod event;
pub mod hierarchy;
pub mod identifier;
pub mod intern;
pub mod label;
pub mod name;
@ -64,10 +63,6 @@ pub use bevy_ptr as ptr;
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[expect(
deprecated,
reason = "`crate::schedule::apply_deferred` is considered deprecated; however, it may still be used by crates which consume `bevy_ecs`, so its removal here may cause confusion. It is intended to be removed in the Bevy 0.17 cycle."
)]
#[doc(hidden)]
pub use crate::{
bundle::Bundle,
@ -80,21 +75,21 @@ pub mod prelude {
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
name::{Name, NameOrEntity},
observer::{Observer, Trigger},
query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
related,
relationship::RelationshipTarget,
removal_detection::RemovedComponents,
resource::Resource,
schedule::{
apply_deferred, common_conditions::*, ApplyDeferred, Condition, IntoScheduleConfigs,
IntoSystemSet, Schedule, Schedules, SystemSet,
common_conditions::*, ApplyDeferred, Condition, IntoScheduleConfigs, IntoSystemSet,
Schedule, Schedules, SystemSet,
},
spawn::{Spawn, SpawnRelated},
system::{
Command, Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef,
IntoSystem, Local, NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem,
Res, ResMut, Single, System, SystemIn, SystemInput, SystemParamBuilder,
SystemParamFunction,
SystemParamFunction, When,
},
world::{
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
@ -152,7 +147,6 @@ mod tests {
use core::{
any::TypeId,
marker::PhantomData,
num::NonZero,
sync::atomic::{AtomicUsize, Ordering},
};
use std::sync::Mutex;
@ -489,10 +483,9 @@ mod tests {
results.lock().unwrap().push((e, i));
});
results.lock().unwrap().sort();
assert_eq!(
&*results.lock().unwrap(),
&[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)]
);
let mut expected = [(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)];
expected.sort();
assert_eq!(&*results.lock().unwrap(), &expected);
}
#[test]
@ -510,10 +503,9 @@ mod tests {
.par_iter(&world)
.for_each(|(e, &SparseStored(i))| results.lock().unwrap().push((e, i)));
results.lock().unwrap().sort();
assert_eq!(
&*results.lock().unwrap(),
&[(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)]
);
let mut expected = [(e1, 1), (e2, 2), (e3, 3), (e4, 4), (e5, 5)];
expected.sort();
assert_eq!(&*results.lock().unwrap(), &expected);
}
#[test]
@ -1543,8 +1535,8 @@ mod tests {
let mut world_a = World::new();
let world_b = World::new();
let mut query = world_a.query::<&A>();
let _ = query.get(&world_a, Entity::from_raw(0));
let _ = query.get(&world_b, Entity::from_raw(0));
let _ = query.get(&world_a, Entity::from_raw_u32(0).unwrap());
let _ = query.get(&world_b, Entity::from_raw_u32(0).unwrap());
}
#[test]
@ -1697,97 +1689,6 @@ mod tests {
assert_eq!(0, query_min_size![(&A, &B), Or<(Changed<A>, Changed<B>)>]);
}
#[test]
fn insert_or_spawn_batch() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = Entity::from_raw(1);
let values = vec![(e0, (B(0), C)), (e1, (B(1), C))];
#[expect(
deprecated,
reason = "This needs to be supported for now, and therefore still needs the test."
)]
world.insert_or_spawn_batch(values).unwrap();
assert_eq!(
world.get::<A>(e0),
Some(&A(0)),
"existing component was preserved"
);
assert_eq!(
world.get::<B>(e0),
Some(&B(0)),
"pre-existing entity received correct B component"
);
assert_eq!(
world.get::<B>(e1),
Some(&B(1)),
"new entity was spawned and received correct B component"
);
assert_eq!(
world.get::<C>(e0),
Some(&C),
"pre-existing entity received C component"
);
assert_eq!(
world.get::<C>(e1),
Some(&C),
"new entity was spawned and received C component"
);
}
#[test]
fn insert_or_spawn_batch_invalid() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = Entity::from_raw(1);
let e2 = world.spawn_empty().id();
let invalid_e2 =
Entity::from_raw_and_generation(e2.index(), NonZero::<u32>::new(2).unwrap());
let values = vec![(e0, (B(0), C)), (e1, (B(1), C)), (invalid_e2, (B(2), C))];
#[expect(
deprecated,
reason = "This needs to be supported for now, and therefore still needs the test."
)]
let result = world.insert_or_spawn_batch(values);
assert_eq!(
result,
Err(vec![invalid_e2]),
"e2 failed to be spawned or inserted into"
);
assert_eq!(
world.get::<A>(e0),
Some(&A(0)),
"existing component was preserved"
);
assert_eq!(
world.get::<B>(e0),
Some(&B(0)),
"pre-existing entity received correct B component"
);
assert_eq!(
world.get::<B>(e1),
Some(&B(1)),
"new entity was spawned and received correct B component"
);
assert_eq!(
world.get::<C>(e0),
Some(&C),
"pre-existing entity received C component"
);
assert_eq!(
world.get::<C>(e1),
Some(&C),
"new entity was spawned and received C component"
);
}
#[test]
fn insert_batch() {
let mut world = World::default();
@ -1876,7 +1777,7 @@ mod tests {
fn try_insert_batch() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = Entity::from_raw(1);
let e1 = Entity::from_raw_u32(1).unwrap();
let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];
@ -1900,7 +1801,7 @@ mod tests {
fn try_insert_batch_if_new() {
let mut world = World::default();
let e0 = world.spawn(A(0)).id();
let e1 = Entity::from_raw(1);
let e1 = Entity::from_raw_u32(1).unwrap();
let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))];
@ -2843,4 +2744,27 @@ mod tests {
)]
#[derive(Component)]
struct MyEntitiesTuple(#[entities] Vec<Entity>, #[entities] Entity, usize);
#[test]
fn clone_entities() {
use crate::entity::{ComponentCloneCtx, SourceComponent};
#[derive(Component)]
#[component(clone_behavior = Ignore)]
struct IgnoreClone;
#[derive(Component)]
#[component(clone_behavior = Default)]
struct DefaultClone;
#[derive(Component)]
#[component(clone_behavior = Custom(custom_clone))]
struct CustomClone;
#[derive(Component, Clone)]
#[component(clone_behavior = clone::<Self>())]
struct CloneFunction;
fn custom_clone(_source: &SourceComponent, _ctx: &mut ComponentCloneCtx) {}
}
}

View File

@ -276,7 +276,7 @@ mod tests {
let d1 = query.get(&world, e1).unwrap();
let d2 = query.get(&world, e2).unwrap();
// NameOrEntity Display for entities without a Name should be {index}v{generation}
assert_eq!(d1.to_string(), "0v1");
assert_eq!(d1.to_string(), "0v0");
// NameOrEntity Display for entities with a Name should be the Name
assert_eq!(d2.to_string(), "MyName");
}

View File

@ -3,11 +3,12 @@ use crate::{
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
},
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
observer::ObserverState,
world::World,
};
use alloc::vec::Vec;
use super::Observer;
/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to.
#[derive(Default)]
pub struct ObservedBy(pub(crate) Vec<Entity>);
@ -27,7 +28,7 @@ impl Component for ObservedBy {
let Ok(mut entity_mut) = world.get_entity_mut(e) else {
continue;
};
let Some(mut state) = entity_mut.get_mut::<ObserverState>() else {
let Some(mut state) = entity_mut.get_mut::<Observer>() else {
continue;
};
state.despawned_watched_entities += 1;
@ -77,10 +78,10 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo
.entity_mut(target)
.insert(ObservedBy(observed_by.clone()));
for observer in &observed_by {
for observer_entity in observed_by.iter().copied() {
let mut observer_state = world
.get_mut::<ObserverState>(*observer)
.expect("Source observer entity must have ObserverState");
.get_mut::<Observer>(observer_entity)
.expect("Source observer entity must have Observer");
observer_state.descriptor.entities.push(target);
let event_types = observer_state.descriptor.events.clone();
let components = observer_state.descriptor.components.clone();

View File

@ -315,13 +315,6 @@ impl ObserverDescriptor {
self
}
pub(crate) fn merge(&mut self, descriptor: &ObserverDescriptor) {
self.events.extend(descriptor.events.iter().copied());
self.components
.extend(descriptor.components.iter().copied());
self.entities.extend(descriptor.entities.iter().copied());
}
/// Returns the `events` that the observer is watching.
pub fn events(&self) -> &[ComponentId] {
&self.events
@ -349,7 +342,7 @@ pub struct ObserverTrigger {
components: SmallVec<[ComponentId; 2]>,
/// The entity the trigger targeted.
pub target: Entity,
/// The location of the source code that triggered the obserer.
/// The location of the source code that triggered the observer.
pub caller: MaybeLocation,
}
@ -562,6 +555,10 @@ impl World {
/// // ...
/// });
/// ```
///
/// # Panics
///
/// Panics if the given system is an exclusive system.
pub fn add_observer<E: Event, B: Bundle, M>(
&mut self,
system: impl IntoObserverSystem<E, B, M>,
@ -724,11 +721,10 @@ impl World {
pub(crate) fn register_observer(&mut self, observer_entity: Entity) {
// SAFETY: References do not alias.
let (observer_state, archetypes, observers) = unsafe {
let observer_state: *const ObserverState =
self.get::<ObserverState>(observer_entity).unwrap();
let observer_state: *const Observer = self.get::<Observer>(observer_entity).unwrap();
// Populate ObservedBy for each observed entity.
for watched_entity in &(*observer_state).descriptor.entities {
let mut entity_mut = self.entity_mut(*watched_entity);
for watched_entity in (*observer_state).descriptor.entities.iter().copied() {
let mut entity_mut = self.entity_mut(watched_entity);
let mut observed_by = entity_mut.entry::<ObservedBy>().or_default().into_mut();
observed_by.0.push(observer_entity);
}
@ -849,7 +845,7 @@ mod tests {
use crate::component::ComponentId;
use crate::{
change_detection::MaybeLocation,
observer::{Observer, ObserverDescriptor, ObserverState, OnReplace},
observer::{Observer, OnReplace},
prelude::*,
traversal::Traversal,
};
@ -1079,7 +1075,7 @@ mod tests {
world.add_observer(|_: Trigger<OnAdd, A>, mut res: ResMut<Order>| res.observed("add_2"));
world.spawn(A).flush();
assert_eq!(vec!["add_1", "add_2"], world.resource::<Order>().0);
assert_eq!(vec!["add_2", "add_1"], world.resource::<Order>().0);
// Our A entity plus our two observers
assert_eq!(world.entities().len(), 3);
}
@ -1364,14 +1360,14 @@ mod tests {
world.init_resource::<Order>();
let event_a = OnRemove::register_component_id(&mut world);
world.spawn(ObserverState {
// SAFETY: we registered `event_a` above and it matches the type of EventA
descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) },
runner: |mut world, _trigger, _ptr, _propagate| {
// SAFETY: we registered `event_a` above and it matches the type of EventA
let observe = unsafe {
Observer::with_dynamic_runner(|mut world, _trigger, _ptr, _propagate| {
world.resource_mut::<Order>().observed("event_a");
},
..Default::default()
});
})
.with_event(event_a)
};
world.spawn(observe);
world.commands().queue(move |world: &mut World| {
// SAFETY: we registered `event_a` above and it matches the type of EventA

View File

@ -1,9 +1,9 @@
use alloc::{boxed::Box, vec, vec::Vec};
use alloc::{boxed::Box, vec};
use core::any::Any;
use crate::{
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
error::{default_error_handler, ErrorContext},
error::{ErrorContext, ErrorHandler},
observer::{ObserverDescriptor, ObserverTrigger},
prelude::*,
query::DebugCheckedUnwrap,
@ -12,85 +12,6 @@ use crate::{
};
use bevy_ptr::PtrMut;
/// Contains [`Observer`] information. This defines how a given observer behaves. It is the
/// "source of truth" for a given observer entity's behavior.
pub struct ObserverState {
pub(crate) descriptor: ObserverDescriptor,
pub(crate) runner: ObserverRunner,
pub(crate) last_trigger_id: u32,
pub(crate) despawned_watched_entities: u32,
}
impl Default for ObserverState {
fn default() -> Self {
Self {
runner: |_, _, _, _| {},
last_trigger_id: 0,
despawned_watched_entities: 0,
descriptor: Default::default(),
}
}
}
impl ObserverState {
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
/// is triggered.
pub fn with_event(mut self, event: ComponentId) -> Self {
self.descriptor.events.push(event);
self
}
/// Observe the given event list. This will cause the [`Observer`] to run whenever an event with any of the given [`ComponentId`]s
/// is triggered.
pub fn with_events(mut self, events: impl IntoIterator<Item = ComponentId>) -> Self {
self.descriptor.events.extend(events);
self
}
/// Observe the given [`Entity`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for any [`Entity`] target in the list.
pub fn with_entities(mut self, entities: impl IntoIterator<Item = Entity>) -> Self {
self.descriptor.entities.extend(entities);
self
}
/// Observe the given [`ComponentId`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for any [`ComponentId`] target in the list.
pub fn with_components(mut self, components: impl IntoIterator<Item = ComponentId>) -> Self {
self.descriptor.components.extend(components);
self
}
}
impl Component for ObserverState {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
fn on_add() -> Option<ComponentHook> {
Some(|mut world, HookContext { entity, .. }| {
world.commands().queue(move |world: &mut World| {
world.register_observer(entity);
});
})
}
fn on_remove() -> Option<ComponentHook> {
Some(|mut world, HookContext { entity, .. }| {
let descriptor = core::mem::take(
&mut world
.entity_mut(entity)
.get_mut::<ObserverState>()
.unwrap()
.as_mut()
.descriptor,
);
world.commands().queue(move |world: &mut World| {
world.unregister_observer(entity, descriptor);
});
})
}
}
/// Type for function that is run when an observer is triggered.
///
/// Typically refers to the default runner that runs the system stored in the associated [`Observer`] component,
@ -264,27 +185,71 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities!
///
/// 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.
///
/// [`SystemParam`]: crate::system::SystemParam
pub struct Observer {
system: Box<dyn Any + Send + Sync + 'static>,
descriptor: ObserverDescriptor,
hook_on_add: ComponentHook,
error_handler: Option<fn(BevyError, ErrorContext)>,
error_handler: Option<ErrorHandler>,
system: Box<dyn Any + Send + Sync + 'static>,
pub(crate) descriptor: ObserverDescriptor,
pub(crate) last_trigger_id: u32,
pub(crate) despawned_watched_entities: u32,
pub(crate) runner: ObserverRunner,
}
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).
///
/// # Panics
///
/// Panics if the given system is an exclusive system.
pub fn new<E: Event, B: Bundle, M, I: IntoObserverSystem<E, B, M>>(system: I) -> Self {
let system = Box::new(IntoObserverSystem::into_system(system));
assert!(
!system.is_exclusive(),
concat!(
"Exclusive system `{}` may not be used as observer.\n",
"Instead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do."
),
system.name()
);
Self {
system: Box::new(IntoObserverSystem::into_system(system)),
system,
descriptor: Default::default(),
hook_on_add: hook_on_add::<E, B, I::System>,
error_handler: None,
runner: observer_system_runner::<E, B, I::System>,
despawned_watched_entities: 0,
last_trigger_id: 0,
}
}
/// Creates a new [`Observer`] with custom runner, this is mostly used for dynamic event observer
pub fn with_dynamic_runner(runner: ObserverRunner) -> Self {
Self {
system: Box::new(|| {}),
descriptor: Default::default(),
hook_on_add: |mut world, hook_context| {
let default_error_handler = world.default_error_handler();
world.commands().queue(move |world: &mut World| {
let entity = hook_context.entity;
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
if observe.descriptor.events.is_empty() {
return;
}
if observe.error_handler.is_none() {
observe.error_handler = Some(default_error_handler);
}
world.register_observer(entity);
}
});
},
error_handler: None,
runner,
despawned_watched_entities: 0,
last_trigger_id: 0,
}
}
@ -345,6 +310,21 @@ impl Component for Observer {
hook(world, context);
})
}
fn on_remove() -> Option<ComponentHook> {
Some(|mut world, HookContext { entity, .. }| {
let descriptor = core::mem::take(
&mut world
.entity_mut(entity)
.get_mut::<Self>()
.unwrap()
.as_mut()
.descriptor,
);
world.commands().queue(move |world: &mut World| {
world.unregister_observer(entity, descriptor);
});
})
}
}
fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
@ -360,12 +340,8 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
.get_entity(observer_trigger.observer)
.debug_checked_unwrap()
};
// SAFETY: Observer was triggered so must have an `ObserverState`
let mut state = unsafe {
observer_cell
.get_mut::<ObserverState>()
.debug_checked_unwrap()
};
// SAFETY: Observer was triggered so must have an `Observer`
let mut state = unsafe { observer_cell.get_mut::<Observer>().debug_checked_unwrap() };
// TODO: Move this check into the observer cache to avoid dynamic dispatch
let last_trigger = world.last_trigger_id();
@ -374,27 +350,18 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
}
state.last_trigger_id = last_trigger;
// SAFETY: Observer was triggered so must have an `Observer` component.
let error_handler = unsafe {
observer_cell
.get::<Observer>()
.debug_checked_unwrap()
.error_handler
.debug_checked_unwrap()
};
let trigger: Trigger<E, B> = Trigger::new(
// SAFETY: Caller ensures `ptr` is castable to `&mut T`
unsafe { ptr.deref_mut() },
propagate,
observer_trigger,
);
// 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();
let system = state.system.downcast_mut::<S>().debug_checked_unwrap();
&mut *system
};
@ -409,7 +376,10 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
match (*system).validate_param_unsafe(world) {
Ok(()) => {
if let Err(err) = (*system).run_unsafe(trigger, world) {
error_handler(
let handler = state
.error_handler
.unwrap_or_else(|| world.default_error_handler());
handler(
err,
ErrorContext::Observer {
name: (*system).name(),
@ -421,7 +391,10 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
}
Err(e) => {
if !e.skipped {
error_handler(
let handler = state
.error_handler
.unwrap_or_else(|| world.default_error_handler());
handler(
e.into(),
ErrorContext::Observer {
name: (*system).name(),
@ -448,52 +421,31 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
) {
world.commands().queue(move |world: &mut World| {
let event_id = E::register_component_id(world);
let mut components = Vec::new();
let mut components = vec![];
B::component_ids(&mut world.components_registrator(), &mut |id| {
components.push(id);
});
let mut descriptor = ObserverDescriptor {
events: vec![event_id],
components,
..Default::default()
};
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
observe.descriptor.events.push(event_id);
observe.descriptor.components.extend(components);
let error_handler = default_error_handler();
// Initialize System
let system: *mut dyn ObserverSystem<E, B> =
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
descriptor.merge(&observe.descriptor);
if observe.error_handler.is_none() {
observe.error_handler = Some(error_handler);
}
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()
});
let system: *mut dyn ObserverSystem<E, B> = observe.system.downcast_mut::<S>().unwrap();
// SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias
unsafe {
(*system).initialize(world);
}
world.register_observer(entity);
}
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{event::Event, observer::Trigger};
use crate::{
error::{ignore, DefaultErrorHandler},
event::Event,
observer::Trigger,
};
#[derive(Event)]
struct TriggerEvent;
@ -521,12 +473,31 @@ mod tests {
Err("I failed!".into())
}
// Using observer error handler
let mut world = World::default();
world.init_resource::<Ran>();
let observer = Observer::new(system).with_error_handler(crate::error::ignore);
world.spawn(observer);
Schedule::default().run(&mut world);
world.spawn(Observer::new(system).with_error_handler(ignore));
world.trigger(TriggerEvent);
assert!(world.resource::<Ran>().0);
// Using world error handler
let mut world = World::default();
world.init_resource::<Ran>();
world.spawn(Observer::new(system));
// Test that the correct handler is used when the observer was added
// before the default handler
world.insert_resource(DefaultErrorHandler(ignore));
world.trigger(TriggerEvent);
assert!(world.resource::<Ran>().0);
}
#[test]
#[should_panic(
expected = "Exclusive system `bevy_ecs::observer::runner::tests::exclusive_system_cannot_be_observer::system` may not be used as observer.\nInstead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do."
)]
fn exclusive_system_cannot_be_observer() {
fn system(_: Trigger<TriggerEvent>, _world: &mut World) {}
let mut world = World::default();
world.add_observer(system);
}
}

View File

@ -257,9 +257,10 @@ impl<T: SparseSetIndex> Access<T> {
/// This is for components whose values are not accessed (and thus will never cause conflicts),
/// but whose presence in an archetype may affect query results.
///
/// Currently, this is only used for [`Has<T>`].
/// Currently, this is only used for [`Has<T>`] and [`Allows<T>`].
///
/// [`Has<T>`]: crate::query::Has
/// [`Allows<T>`]: crate::query::filter::Allows
pub fn add_archetypal(&mut self, index: T) {
self.archetypal.grow_and_insert(index.sparse_set_index());
}
@ -430,75 +431,56 @@ 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,
invertible_union_with(
&mut self.component_read_and_writes,
&mut self.component_read_and_writes_inverted,
&other.component_read_and_writes,
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,
);
invertible_union_with(
&mut self.component_writes,
&mut self.component_writes_inverted,
&other.component_writes,
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.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);
self.archetypal.union_with(&other.archetypal);
}
/// Removes any access from `self` that would conflict with `other`.
/// This removes any reads and writes for any component written by `other`,
/// and removes any writes for any component read by `other`.
pub fn remove_conflicting_access(&mut self, other: &Access<T>) {
invertible_difference_with(
&mut self.component_read_and_writes,
&mut self.component_read_and_writes_inverted,
&other.component_writes,
other.component_writes_inverted,
);
invertible_difference_with(
&mut self.component_writes,
&mut self.component_writes_inverted,
&other.component_read_and_writes,
other.component_read_and_writes_inverted,
);
if other.reads_all_resources {
self.writes_all_resources = false;
self.resource_writes.clear();
}
if other.writes_all_resources {
self.reads_all_resources = false;
self.resource_read_and_writes.clear();
}
self.resource_read_and_writes
.difference_with(&other.resource_writes);
self.resource_writes
.difference_with(&other.resource_read_and_writes);
}
/// Returns `true` if the access and `other` can be active at the same time,
@ -838,6 +820,55 @@ impl<T: SparseSetIndex> Access<T> {
}
}
/// Performs an in-place union of `other` into `self`, where either set may be inverted.
///
/// Each set corresponds to a `FixedBitSet` if `inverted` is `false`,
/// or to the infinite (co-finite) complement of the `FixedBitSet` if `inverted` is `true`.
///
/// This updates the `self` set to include any elements in the `other` set.
/// Note that this may change `self_inverted` to `true` if we add an infinite
/// set to a finite one, resulting in a new infinite set.
fn invertible_union_with(
self_set: &mut FixedBitSet,
self_inverted: &mut bool,
other_set: &FixedBitSet,
other_inverted: bool,
) {
match (*self_inverted, other_inverted) {
(true, true) => self_set.intersect_with(other_set),
(true, false) => self_set.difference_with(other_set),
(false, true) => {
*self_inverted = true;
// We have to grow here because the new bits are going to get flipped to 1.
self_set.grow(other_set.len());
self_set.toggle_range(..);
self_set.intersect_with(other_set);
}
(false, false) => self_set.union_with(other_set),
}
}
/// Performs an in-place set difference of `other` from `self`, where either set may be inverted.
///
/// Each set corresponds to a `FixedBitSet` if `inverted` is `false`,
/// or to the infinite (co-finite) complement of the `FixedBitSet` if `inverted` is `true`.
///
/// This updates the `self` set to remove any elements in the `other` set.
/// Note that this may change `self_inverted` to `false` if we remove an
/// infinite set from another infinite one, resulting in a finite difference.
fn invertible_difference_with(
self_set: &mut FixedBitSet,
self_inverted: &mut bool,
other_set: &FixedBitSet,
other_inverted: bool,
) {
// We can share the implementation of `invertible_union_with` with some algebra:
// A - B = A & !B = !(!A | B)
*self_inverted = !*self_inverted;
invertible_union_with(self_set, self_inverted, other_set, other_inverted);
*self_inverted = !*self_inverted;
}
/// Error returned when attempting to iterate over items included in an [`Access`]
/// if the access excludes items rather than including them.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Error)]
@ -1289,6 +1320,14 @@ impl<T: SparseSetIndex> Clone for FilteredAccessSet<T> {
}
impl<T: SparseSetIndex> FilteredAccessSet<T> {
/// Creates an empty [`FilteredAccessSet`].
pub const fn new() -> Self {
Self {
combined_access: Access::new(),
filtered_accesses: Vec::new(),
}
}
/// Returns a reference to the unfiltered access of the entire set.
#[inline]
pub fn combined_access(&self) -> &Access<T> {
@ -1412,15 +1451,13 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
impl<T: SparseSetIndex> Default for FilteredAccessSet<T> {
fn default() -> Self {
Self {
combined_access: Default::default(),
filtered_accesses: Vec::new(),
}
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::{invertible_difference_with, invertible_union_with};
use crate::query::{
access::AccessFilters, Access, AccessConflicts, ComponentAccessKind, FilteredAccess,
FilteredAccessSet, UnboundedAccessError,
@ -1763,4 +1800,99 @@ mod tests {
}),
);
}
/// Create a `FixedBitSet` with a given number of total bits and a given list of bits to set.
/// Setting the number of bits is important in tests since the `PartialEq` impl checks that the length matches.
fn bit_set(bits: usize, iter: impl IntoIterator<Item = usize>) -> FixedBitSet {
let mut result = FixedBitSet::with_capacity(bits);
result.extend(iter);
result
}
#[test]
fn invertible_union_with_tests() {
let invertible_union = |mut self_inverted: bool, other_inverted: bool| {
// Check all four possible bit states: In both sets, the first, the second, or neither
let mut self_set = bit_set(4, [0, 1]);
let other_set = bit_set(4, [0, 2]);
invertible_union_with(
&mut self_set,
&mut self_inverted,
&other_set,
other_inverted,
);
(self_set, self_inverted)
};
// Check each combination of `inverted` flags
let (s, i) = invertible_union(false, false);
// [0, 1] | [0, 2] = [0, 1, 2]
assert_eq!((s, i), (bit_set(4, [0, 1, 2]), false));
let (s, i) = invertible_union(false, true);
// [0, 1] | [1, 3, ...] = [0, 1, 3, ...]
assert_eq!((s, i), (bit_set(4, [2]), true));
let (s, i) = invertible_union(true, false);
// [2, 3, ...] | [0, 2] = [0, 2, 3, ...]
assert_eq!((s, i), (bit_set(4, [1]), true));
let (s, i) = invertible_union(true, true);
// [2, 3, ...] | [1, 3, ...] = [1, 2, 3, ...]
assert_eq!((s, i), (bit_set(4, [0]), true));
}
#[test]
fn invertible_union_with_different_lengths() {
// When adding a large inverted set to a small normal set,
// make sure we invert the bits beyond the original length.
// Failing to call `grow` before `toggle_range` would cause bit 1 to be zero,
// which would incorrectly treat it as included in the output set.
let mut self_set = bit_set(1, [0]);
let mut self_inverted = false;
let other_set = bit_set(3, [0, 1]);
let other_inverted = true;
invertible_union_with(
&mut self_set,
&mut self_inverted,
&other_set,
other_inverted,
);
// [0] | [2, ...] = [0, 2, ...]
assert_eq!((self_set, self_inverted), (bit_set(3, [1]), true));
}
#[test]
fn invertible_difference_with_tests() {
let invertible_difference = |mut self_inverted: bool, other_inverted: bool| {
// Check all four possible bit states: In both sets, the first, the second, or neither
let mut self_set = bit_set(4, [0, 1]);
let other_set = bit_set(4, [0, 2]);
invertible_difference_with(
&mut self_set,
&mut self_inverted,
&other_set,
other_inverted,
);
(self_set, self_inverted)
};
// Check each combination of `inverted` flags
let (s, i) = invertible_difference(false, false);
// [0, 1] - [0, 2] = [1]
assert_eq!((s, i), (bit_set(4, [1]), false));
let (s, i) = invertible_difference(false, true);
// [0, 1] - [1, 3, ...] = [0]
assert_eq!((s, i), (bit_set(4, [0]), false));
let (s, i) = invertible_difference(true, false);
// [2, 3, ...] - [0, 2] = [3, ...]
assert_eq!((s, i), (bit_set(4, [0, 1, 2]), true));
let (s, i) = invertible_difference(true, true);
// [2, 3, ...] - [1, 3, ...] = [2]
assert_eq!((s, i), (bit_set(4, [2]), false));
}
}

View File

@ -248,11 +248,9 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
pub fn transmute_filtered<NewD: QueryData, NewF: QueryFilter>(
&mut self,
) -> &mut QueryBuilder<'w, NewD, NewF> {
let mut fetch_state = NewD::init_state(self.world);
let fetch_state = NewD::init_state(self.world);
let filter_state = NewF::init_state(self.world);
NewD::set_access(&mut fetch_state, &self.access);
let mut access = FilteredAccess::default();
NewD::update_component_access(&fetch_state, &mut access);
NewF::update_component_access(&filter_state, &mut access);
@ -275,7 +273,10 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
#[cfg(test)]
mod tests {
use crate::{prelude::*, world::FilteredEntityRef};
use crate::{
prelude::*,
world::{EntityMutExcept, EntityRefExcept, FilteredEntityMut, FilteredEntityRef},
};
use std::dbg;
#[derive(Component, PartialEq, Debug)]
@ -422,6 +423,89 @@ mod tests {
}
}
#[test]
fn builder_provide_access() {
let mut world = World::new();
world.spawn((A(0), B(1)));
let mut query =
QueryBuilder::<(Entity, FilteredEntityRef, FilteredEntityMut)>::new(&mut world)
.data::<&mut A>()
.data::<&B>()
.build();
// The `FilteredEntityRef` only has read access, so the `FilteredEntityMut` can have read access without conflicts
let (_entity, entity_ref_1, mut entity_ref_2) = query.single_mut(&mut world).unwrap();
assert!(entity_ref_1.get::<A>().is_some());
assert!(entity_ref_1.get::<B>().is_some());
assert!(entity_ref_2.get::<A>().is_some());
assert!(entity_ref_2.get_mut::<A>().is_none());
assert!(entity_ref_2.get::<B>().is_some());
assert!(entity_ref_2.get_mut::<B>().is_none());
let mut query =
QueryBuilder::<(Entity, FilteredEntityMut, FilteredEntityMut)>::new(&mut world)
.data::<&mut A>()
.data::<&B>()
.build();
// The first `FilteredEntityMut` has write access to A, so the second one cannot have write access
let (_entity, mut entity_ref_1, mut entity_ref_2) = query.single_mut(&mut world).unwrap();
assert!(entity_ref_1.get::<A>().is_some());
assert!(entity_ref_1.get_mut::<A>().is_some());
assert!(entity_ref_1.get::<B>().is_some());
assert!(entity_ref_1.get_mut::<B>().is_none());
assert!(entity_ref_2.get::<A>().is_none());
assert!(entity_ref_2.get_mut::<A>().is_none());
assert!(entity_ref_2.get::<B>().is_some());
assert!(entity_ref_2.get_mut::<B>().is_none());
let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B)>::new(&mut world)
.data::<&mut A>()
.data::<&mut B>()
.build();
// Any `A` access would conflict with `&mut A`, and write access to `B` would conflict with `&B`.
let (mut entity_ref, _a, _b) = query.single_mut(&mut world).unwrap();
assert!(entity_ref.get::<A>().is_none());
assert!(entity_ref.get_mut::<A>().is_none());
assert!(entity_ref.get::<B>().is_some());
assert!(entity_ref.get_mut::<B>().is_none());
let mut query = QueryBuilder::<(FilteredEntityMut, &mut A, &B)>::new(&mut world)
.data::<EntityMut>()
.build();
// Same as above, but starting from "all" access
let (mut entity_ref, _a, _b) = query.single_mut(&mut world).unwrap();
assert!(entity_ref.get::<A>().is_none());
assert!(entity_ref.get_mut::<A>().is_none());
assert!(entity_ref.get::<B>().is_some());
assert!(entity_ref.get_mut::<B>().is_none());
let mut query = QueryBuilder::<(FilteredEntityMut, EntityMutExcept<A>)>::new(&mut world)
.data::<EntityMut>()
.build();
// Removing `EntityMutExcept<A>` just leaves A
let (mut entity_ref_1, _entity_ref_2) = query.single_mut(&mut world).unwrap();
assert!(entity_ref_1.get::<A>().is_some());
assert!(entity_ref_1.get_mut::<A>().is_some());
assert!(entity_ref_1.get::<B>().is_none());
assert!(entity_ref_1.get_mut::<B>().is_none());
let mut query = QueryBuilder::<(FilteredEntityMut, EntityRefExcept<A>)>::new(&mut world)
.data::<EntityMut>()
.build();
// Removing `EntityRefExcept<A>` just leaves A, plus read access
let (mut entity_ref_1, _entity_ref_2) = query.single_mut(&mut world).unwrap();
assert!(entity_ref_1.get::<A>().is_some());
assert!(entity_ref_1.get_mut::<A>().is_some());
assert!(entity_ref_1.get::<B>().is_some());
assert!(entity_ref_1.get_mut::<B>().is_none());
}
/// Regression test for issue #14348
#[test]
fn builder_static_dense_dynamic_sparse() {

View File

@ -31,6 +31,8 @@ use variadics_please::all_tuples;
/// Gets the identifier of the queried entity.
/// - **[`EntityLocation`].**
/// Gets the location metadata of the queried entity.
/// - **[`SpawnDetails`].**
/// Gets the tick the entity was spawned at.
/// - **[`EntityRef`].**
/// Read-only access to arbitrary components on the queried entity.
/// - **[`EntityMut`].**
@ -291,6 +293,22 @@ pub unsafe trait QueryData: WorldQuery {
/// This function manually implements subtyping for the query items.
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort>;
/// Offers additional access above what we requested in `update_component_access`.
/// Implementations may add additional access that is a subset of `available_access`
/// and does not conflict with anything in `access`,
/// and must update `access` to include that access.
///
/// This is used by [`WorldQuery`] types like [`FilteredEntityRef`]
/// and [`FilteredEntityMut`] to support dynamic access.
///
/// Called when constructing a [`QueryLens`](crate::system::QueryLens) or calling [`QueryState::from_builder`](super::QueryState::from_builder)
fn provide_extra_access(
_state: &mut Self::State,
_access: &mut Access<ComponentId>,
_available_access: &Access<ComponentId>,
) {
}
/// Fetch [`Self::Item`](`QueryData::Item`) for either the given `entity` in the current [`Table`],
/// or for the given `entity` in the current [`Archetype`]. This must always be called after
/// [`WorldQuery::set_table`] with a `table_row` in the range of the current [`Table`] or after
@ -470,12 +488,80 @@ unsafe impl QueryData for EntityLocation {
/// SAFETY: access is read only
unsafe impl ReadOnlyQueryData for EntityLocation {}
/// SAFETY:
/// `fetch` accesses all components in a readonly way.
/// This is sound because `update_component_access` and `update_archetype_component_access` set read access for all components and panic when appropriate.
/// Filters are unchanged.
unsafe impl<'a> WorldQuery for EntityRef<'a> {
type Fetch<'w> = UnsafeWorldCell<'w>;
/// The `SpawnDetails` query parameter fetches the [`Tick`] the entity was spawned at.
///
/// To evaluate whether the spawn happened since the last time the system ran, the system
/// param [`SystemChangeTick`](bevy_ecs::system::SystemChangeTick) needs to be used.
///
/// If the query should filter for spawned entities instead, use the
/// [`Spawned`](bevy_ecs::query::Spawned) query filter instead.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::entity::Entity;
/// # use bevy_ecs::system::Query;
/// # use bevy_ecs::query::Spawned;
/// # use bevy_ecs::query::SpawnDetails;
///
/// fn print_spawn_details(query: Query<(Entity, SpawnDetails)>) {
/// for (entity, spawn_details) in &query {
/// if spawn_details.is_spawned() {
/// print!("new ");
/// }
/// print!(
/// "entity {:?} spawned at {:?}",
/// entity,
/// spawn_details.spawned_at()
/// );
/// match spawn_details.spawned_by().into_option() {
/// Some(location) => println!(" by {:?}", location),
/// None => println!()
/// }
/// }
/// }
///
/// # bevy_ecs::system::assert_is_system(print_spawn_details);
/// ```
#[derive(Clone, Copy, Debug)]
pub struct SpawnDetails {
spawned_by: MaybeLocation,
spawned_at: Tick,
last_run: Tick,
this_run: Tick,
}
impl SpawnDetails {
/// Returns `true` if the entity spawned since the last time this system ran.
/// Otherwise, returns `false`.
pub fn is_spawned(self) -> bool {
self.spawned_at.is_newer_than(self.last_run, self.this_run)
}
/// Returns the `Tick` this entity spawned at.
pub fn spawned_at(self) -> Tick {
self.spawned_at
}
/// Returns the source code location from which this entity has been spawned.
pub fn spawned_by(self) -> MaybeLocation {
self.spawned_by
}
}
#[doc(hidden)]
#[derive(Clone)]
pub struct SpawnDetailsFetch<'w> {
entities: &'w Entities,
last_run: Tick,
this_run: Tick,
}
// SAFETY:
// No components are accessed.
unsafe impl WorldQuery for SpawnDetails {
type Fetch<'w> = SpawnDetailsFetch<'w>;
type State = ();
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@ -485,10 +571,116 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> {
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
world
SpawnDetailsFetch {
entities: world.entities(),
last_run,
this_run,
}
}
const IS_DENSE: bool = true;
#[inline]
unsafe fn set_archetype<'w>(
_fetch: &mut Self::Fetch<'w>,
_state: &Self::State,
_archetype: &'w Archetype,
_table: &'w Table,
) {
}
#[inline]
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) {
}
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
}
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
true
}
}
// SAFETY:
// No components are accessed.
// Is its own ReadOnlyQueryData.
unsafe impl QueryData for SpawnDetails {
const IS_READ_ONLY: bool = true;
type ReadOnly = Self;
type Item<'w> = Self;
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: only living entities are queried
let (spawned_by, spawned_at) = unsafe {
fetch
.entities
.entity_get_spawned_or_despawned_unchecked(entity)
};
Self {
spawned_by,
spawned_at,
last_run: fetch.last_run,
this_run: fetch.this_run,
}
}
}
/// SAFETY: access is read only
unsafe impl ReadOnlyQueryData for SpawnDetails {}
/// The [`WorldQuery::Fetch`] type for WorldQueries that can fetch multiple components from an entity
/// ([`EntityRef`], [`EntityMut`], etc.)
#[derive(Copy, Clone)]
#[doc(hidden)]
pub struct EntityFetch<'w> {
world: UnsafeWorldCell<'w>,
last_run: Tick,
this_run: Tick,
}
/// SAFETY:
/// `fetch` accesses all components in a readonly way.
/// This is sound because `update_component_access` and `update_archetype_component_access` set read access for all components and panic when appropriate.
/// Filters are unchanged.
unsafe impl<'a> WorldQuery for EntityRef<'a> {
type Fetch<'w> = EntityFetch<'w>;
type State = ();
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
fetch
}
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
EntityFetch {
world,
last_run,
this_run,
}
}
const IS_DENSE: bool = true;
@ -540,12 +732,17 @@ unsafe impl<'a> QueryData for EntityRef<'a> {
#[inline(always)]
unsafe fn fetch<'w>(
world: &mut Self::Fetch<'w>,
fetch: &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: `fetch` must be called with an entity that exists in the world
let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() };
let cell = unsafe {
fetch
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.debug_checked_unwrap()
};
// SAFETY: Read-only access to every component has been registered.
unsafe { EntityRef::new(cell) }
}
@ -556,7 +753,7 @@ unsafe impl ReadOnlyQueryData for EntityRef<'_> {}
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
unsafe impl<'a> WorldQuery for EntityMut<'a> {
type Fetch<'w> = UnsafeWorldCell<'w>;
type Fetch<'w> = EntityFetch<'w>;
type State = ();
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@ -566,10 +763,14 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> {
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
world
EntityFetch {
world,
last_run,
this_run,
}
}
const IS_DENSE: bool = true;
@ -621,12 +822,17 @@ unsafe impl<'a> QueryData for EntityMut<'a> {
#[inline(always)]
unsafe fn fetch<'w>(
world: &mut Self::Fetch<'w>,
fetch: &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: `fetch` must be called with an entity that exists in the world
let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() };
let cell = unsafe {
fetch
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.debug_checked_unwrap()
};
// SAFETY: mutable access to every component has been registered.
unsafe { EntityMut::new(cell) }
}
@ -634,8 +840,8 @@ unsafe impl<'a> QueryData for EntityMut<'a> {
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
type Fetch<'w> = (UnsafeWorldCell<'w>, Access<ComponentId>);
type State = FilteredAccess<ComponentId>;
type Fetch<'w> = (EntityFetch<'w>, Access<ComponentId>);
type State = Access<ComponentId>;
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
fetch
@ -646,12 +852,19 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
let mut access = Access::default();
access.read_all_components();
(world, access)
(
EntityFetch {
world,
last_run,
this_run,
},
access,
)
}
#[inline]
@ -661,18 +874,12 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
_: &'w Archetype,
_table: &Table,
) {
fetch.1.clone_from(&state.access);
fetch.1.clone_from(state);
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) {
fetch.1.clone_from(&state.access);
}
#[inline]
fn set_access<'w>(state: &mut Self::State, access: &FilteredAccess<ComponentId>) {
state.clone_from(access);
state.access_mut().clear_writes();
fetch.1.clone_from(state);
}
fn update_component_access(
@ -680,18 +887,18 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
filtered_access: &mut FilteredAccess<ComponentId>,
) {
assert!(
filtered_access.access().is_compatible(&state.access),
filtered_access.access().is_compatible(state),
"FilteredEntityRef conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.",
);
filtered_access.access.extend(&state.access);
filtered_access.access.extend(state);
}
fn init_state(_world: &mut World) -> Self::State {
FilteredAccess::default()
Access::default()
}
fn get_state(_components: &Components) -> Option<Self::State> {
Some(FilteredAccess::default())
Some(Access::default())
}
fn matches_component_set(
@ -712,14 +919,38 @@ unsafe impl<'a> QueryData for FilteredEntityRef<'a> {
item
}
#[inline]
fn provide_extra_access(
state: &mut Self::State,
access: &mut Access<ComponentId>,
available_access: &Access<ComponentId>,
) {
// Claim any extra access that doesn't conflict with other subqueries
// This is used when constructing a `QueryLens` or creating a query from a `QueryBuilder`
// Start with the entire available access, since that is the most we can possibly access
state.clone_from(available_access);
// Prevent all writes, since `FilteredEntityRef` only performs read access
state.clear_writes();
// Prevent any access that would conflict with other accesses in the current query
state.remove_conflicting_access(access);
// Finally, add the resulting access to the query access
// to make sure a later `FilteredEntityMut` won't conflict with this.
access.extend(state);
}
#[inline(always)]
unsafe fn fetch<'w>(
(world, access): &mut Self::Fetch<'w>,
(fetch, access): &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: `fetch` must be called with an entity that exists in the world
let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() };
let cell = unsafe {
fetch
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.debug_checked_unwrap()
};
// SAFETY: mutable access to every component has been registered.
unsafe { FilteredEntityRef::new(cell, access.clone()) }
}
@ -730,8 +961,8 @@ unsafe impl ReadOnlyQueryData for FilteredEntityRef<'_> {}
/// SAFETY: The accesses of `Self::ReadOnly` are a subset of the accesses of `Self`
unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
type Fetch<'w> = (UnsafeWorldCell<'w>, Access<ComponentId>);
type State = FilteredAccess<ComponentId>;
type Fetch<'w> = (EntityFetch<'w>, Access<ComponentId>);
type State = Access<ComponentId>;
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
fetch
@ -742,12 +973,19 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
let mut access = Access::default();
access.write_all_components();
(world, access)
(
EntityFetch {
world,
last_run,
this_run,
},
access,
)
}
#[inline]
@ -757,17 +995,12 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
_: &'w Archetype,
_table: &Table,
) {
fetch.1.clone_from(&state.access);
fetch.1.clone_from(state);
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, _: &'w Table) {
fetch.1.clone_from(&state.access);
}
#[inline]
fn set_access<'w>(state: &mut Self::State, access: &FilteredAccess<ComponentId>) {
state.clone_from(access);
fetch.1.clone_from(state);
}
fn update_component_access(
@ -775,18 +1008,18 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
filtered_access: &mut FilteredAccess<ComponentId>,
) {
assert!(
filtered_access.access().is_compatible(&state.access),
filtered_access.access().is_compatible(state),
"FilteredEntityMut conflicts with a previous access in this query. Exclusive access cannot coincide with any other accesses.",
);
filtered_access.access.extend(&state.access);
filtered_access.access.extend(state);
}
fn init_state(_world: &mut World) -> Self::State {
FilteredAccess::default()
Access::default()
}
fn get_state(_components: &Components) -> Option<Self::State> {
Some(FilteredAccess::default())
Some(Access::default())
}
fn matches_component_set(
@ -807,14 +1040,36 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> {
item
}
#[inline]
fn provide_extra_access(
state: &mut Self::State,
access: &mut Access<ComponentId>,
available_access: &Access<ComponentId>,
) {
// Claim any extra access that doesn't conflict with other subqueries
// This is used when constructing a `QueryLens` or creating a query from a `QueryBuilder`
// Start with the entire available access, since that is the most we can possibly access
state.clone_from(available_access);
// Prevent any access that would conflict with other accesses in the current query
state.remove_conflicting_access(access);
// Finally, add the resulting access to the query access
// to make sure a later `FilteredEntityRef` or `FilteredEntityMut` won't conflict with this.
access.extend(state);
}
#[inline(always)]
unsafe fn fetch<'w>(
(world, access): &mut Self::Fetch<'w>,
(fetch, access): &mut Self::Fetch<'w>,
entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
// SAFETY: `fetch` must be called with an entity that exists in the world
let cell = unsafe { world.get_entity(entity).debug_checked_unwrap() };
let cell = unsafe {
fetch
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.debug_checked_unwrap()
};
// SAFETY: mutable access to every component has been registered.
unsafe { FilteredEntityMut::new(cell, access.clone()) }
}
@ -827,7 +1082,7 @@ unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B>
where
B: Bundle,
{
type Fetch<'w> = UnsafeWorldCell<'w>;
type Fetch<'w> = EntityFetch<'w>;
type State = SmallVec<[ComponentId; 4]>;
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@ -837,10 +1092,14 @@ where
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_: &Self::State,
_: Tick,
_: Tick,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
world
EntityFetch {
world,
last_run,
this_run,
}
}
const IS_DENSE: bool = true;
@ -907,11 +1166,14 @@ where
}
unsafe fn fetch<'w>(
world: &mut Self::Fetch<'w>,
fetch: &mut Self::Fetch<'w>,
entity: Entity,
_: TableRow,
) -> Self::Item<'w> {
let cell = world.get_entity(entity).unwrap();
let cell = fetch
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.unwrap();
EntityRefExcept::new(cell)
}
}
@ -927,7 +1189,7 @@ unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B>
where
B: Bundle,
{
type Fetch<'w> = UnsafeWorldCell<'w>;
type Fetch<'w> = EntityFetch<'w>;
type State = SmallVec<[ComponentId; 4]>;
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
@ -937,10 +1199,14 @@ where
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_: &Self::State,
_: Tick,
_: Tick,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
world
EntityFetch {
world,
last_run,
this_run,
}
}
const IS_DENSE: bool = true;
@ -1008,11 +1274,14 @@ where
}
unsafe fn fetch<'w>(
world: &mut Self::Fetch<'w>,
fetch: &mut Self::Fetch<'w>,
entity: Entity,
_: TableRow,
) -> Self::Item<'w> {
let cell = world.get_entity(entity).unwrap();
let cell = fetch
.world
.get_entity_with_ticks(entity, fetch.last_run, fetch.this_run)
.unwrap();
EntityMutExcept::new(cell)
}
}
@ -2079,6 +2348,16 @@ macro_rules! impl_tuple_query_data {
)*)
}
#[inline]
fn provide_extra_access(
state: &mut Self::State,
access: &mut Access<ComponentId>,
available_access: &Access<ComponentId>,
) {
let ($($name,)*) = state;
$($name::provide_extra_access($name, access, available_access);)*
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
@ -2493,10 +2772,11 @@ impl<C: Component, T: Copy, S: Copy> Copy for StorageSwitch<C, T, S> {}
#[cfg(test)]
mod tests {
use bevy_ecs_macros::QueryData;
use super::*;
use crate::change_detection::DetectChanges;
use crate::system::{assert_is_system, Query};
use bevy_ecs::prelude::Schedule;
use bevy_ecs_macros::QueryData;
#[derive(Component)]
pub struct A;
@ -2590,4 +2870,34 @@ mod tests {
assert_is_system(client_system);
}
// Test that EntityRef::get_ref::<T>() returns a Ref<T> value with the correct
// ticks when the EntityRef was retrieved from a Query.
// See: https://github.com/bevyengine/bevy/issues/13735
#[test]
fn test_entity_ref_query_with_ticks() {
#[derive(Component)]
pub struct C;
fn system(query: Query<EntityRef>) {
for entity_ref in &query {
if let Some(c) = entity_ref.get_ref::<C>() {
if !c.is_added() {
panic!("Expected C to be added");
}
}
}
}
let mut world = World::new();
let mut schedule = Schedule::default();
schedule.add_systems(system);
world.spawn(C);
// reset the change ticks
world.clear_trackers();
// we want EntityRef to use the change ticks of the system
schedule.run(&mut world);
}
}

View File

@ -1,7 +1,7 @@
use crate::{
archetype::Archetype,
component::{Component, ComponentId, Components, StorageType, Tick},
entity::Entity,
entity::{Entities, Entity},
query::{DebugCheckedUnwrap, FilteredAccess, StorageSwitch, WorldQuery},
storage::{ComponentSparseSet, Table, TableRow},
world::{unsafe_world_cell::UnsafeWorldCell, World},
@ -17,6 +17,8 @@ use variadics_please::all_tuples;
/// [`With`] and [`Without`] filters can be applied to check if the queried entity does or does not contain a particular component.
/// - **Change detection filters.**
/// [`Added`] and [`Changed`] filters can be applied to detect component changes to an entity.
/// - **Spawned filter.**
/// [`Spawned`] filter can be applied to check if the queried entity was spawned recently.
/// - **`QueryFilter` tuples.**
/// If every element of a tuple implements `QueryFilter`, then the tuple itself also implements the same trait.
/// This enables a single `Query` to filter over multiple conditions.
@ -555,6 +557,63 @@ all_tuples!(
S
);
/// Allows a query to contain entities with the component `T`, bypassing [`DefaultQueryFilters`].
///
/// [`DefaultQueryFilters`]: crate::entity_disabling::DefaultQueryFilters
pub struct Allows<T>(PhantomData<T>);
/// SAFETY:
/// `update_component_access` does not add any accesses.
/// This is sound because [`QueryFilter::filter_fetch`] does not access any components.
/// `update_component_access` adds an archetypal filter for `T`.
/// This is sound because it doesn't affect the query
unsafe impl<T: Component> WorldQuery for Allows<T> {
type Fetch<'w> = ();
type State = ComponentId;
fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {}
#[inline]
unsafe fn init_fetch(_: UnsafeWorldCell, _: &ComponentId, _: Tick, _: Tick) {}
// Even if the component is sparse, this implementation doesn't do anything with it
const IS_DENSE: bool = true;
#[inline]
unsafe fn set_archetype(_: &mut (), _: &ComponentId, _: &Archetype, _: &Table) {}
#[inline]
unsafe fn set_table(_: &mut (), _: &ComponentId, _: &Table) {}
#[inline]
fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) {
access.access_mut().add_archetypal(id);
}
fn init_state(world: &mut World) -> ComponentId {
world.register_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
components.component_id::<T>()
}
fn matches_component_set(_: &ComponentId, _: &impl Fn(ComponentId) -> bool) -> bool {
// Allows<T> always matches
true
}
}
// SAFETY: WorldQuery impl performs no access at all
unsafe impl<T: Component> QueryFilter for Allows<T> {
const IS_ARCHETYPAL: bool = true;
#[inline(always)]
unsafe fn filter_fetch(_: &mut Self::Fetch<'_>, _: Entity, _: TableRow) -> bool {
true
}
}
/// A filter on a component that only retains results the first time after they have been added.
///
/// A common use for this filter is one-time initialization.
@ -567,8 +626,8 @@ all_tuples!(
/// # Deferred
///
/// Note, that entity modifications issued with [`Commands`](crate::system::Commands)
/// are visible only after deferred operations are applied,
/// typically at the end of the schedule iteration.
/// are visible only after deferred operations are applied, typically after the system
/// that queued them.
///
/// # Time complexity
///
@ -792,9 +851,8 @@ unsafe impl<T: Component> QueryFilter for Added<T> {
/// # Deferred
///
/// Note, that entity modifications issued with [`Commands`](crate::system::Commands)
/// (like entity creation or entity component addition or removal)
/// are visible only after deferred operations are applied,
/// typically at the end of the schedule iteration.
/// (like entity creation or entity component addition or removal) are visible only
/// after deferred operations are applied, typically after the system that queued them.
///
/// # Time complexity
///
@ -1005,6 +1063,146 @@ unsafe impl<T: Component> QueryFilter for Changed<T> {
}
}
/// A filter that only retains results the first time after the entity has been spawned.
///
/// A common use for this filter is one-time initialization.
///
/// To retain all results without filtering but still check whether they were spawned after the
/// system last ran, use [`SpawnDetails`](crate::query::SpawnDetails) instead.
///
/// **Note** that this includes entities that spawned before the first time this Query was run.
///
/// # Deferred
///
/// Note, that entity spawns issued with [`Commands`](crate::system::Commands)
/// are visible only after deferred operations are applied, typically after the
/// system that queued them.
///
/// # Time complexity
///
/// `Spawned` is not [`ArchetypeFilter`], which practically means that if query matches million
/// entities, `Spawned` filter will iterate over all of them even if none of them were spawned.
///
/// For example, these two systems are roughly equivalent in terms of performance:
///
/// ```
/// # use bevy_ecs::entity::Entity;
/// # use bevy_ecs::system::Query;
/// # use bevy_ecs::query::Spawned;
/// # use bevy_ecs::query::SpawnDetails;
///
/// fn system1(query: Query<Entity, Spawned>) {
/// for entity in &query { /* entity spawned */ }
/// }
///
/// fn system2(query: Query<(Entity, SpawnDetails)>) {
/// for (entity, spawned) in &query {
/// if spawned.is_spawned() { /* entity spawned */ }
/// }
/// }
/// ```
///
/// # Examples
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::query::Spawned;
/// # use bevy_ecs::system::IntoSystem;
/// # use bevy_ecs::system::Query;
/// #
/// # #[derive(Component, Debug)]
/// # struct Name {};
///
/// fn print_spawning_entities(query: Query<&Name, Spawned>) {
/// for name in &query {
/// println!("Entity spawned: {:?}", name);
/// }
/// }
///
/// # bevy_ecs::system::assert_is_system(print_spawning_entities);
/// ```
pub struct Spawned;
#[doc(hidden)]
#[derive(Clone)]
pub struct SpawnedFetch<'w> {
entities: &'w Entities,
last_run: Tick,
this_run: Tick,
}
// SAFETY: WorldQuery impl accesses no components or component ticks
unsafe impl WorldQuery for Spawned {
type Fetch<'w> = SpawnedFetch<'w>;
type State = ();
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
fetch
}
#[inline]
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
_state: &(),
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
SpawnedFetch {
entities: world.entities(),
last_run,
this_run,
}
}
const IS_DENSE: bool = true;
#[inline]
unsafe fn set_archetype<'w>(
_fetch: &mut Self::Fetch<'w>,
_state: &(),
_archetype: &'w Archetype,
_table: &'w Table,
) {
}
#[inline]
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &(), _table: &'w Table) {}
#[inline]
fn update_component_access(_state: &(), _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
}
fn matches_component_set(_state: &(), _set_contains_id: &impl Fn(ComponentId) -> bool) -> bool {
true
}
}
// SAFETY: WorldQuery impl accesses no components or component ticks
unsafe impl QueryFilter for Spawned {
const IS_ARCHETYPAL: bool = false;
#[inline(always)]
unsafe fn filter_fetch(
fetch: &mut Self::Fetch<'_>,
entity: Entity,
_table_row: TableRow,
) -> bool {
// SAFETY: only living entities are queried
let spawned = unsafe {
fetch
.entities
.entity_get_spawned_or_despawned_unchecked(entity)
.1
};
spawned.is_newer_than(fetch.last_run, fetch.this_run)
}
}
/// A marker trait to indicate that the filter works at an archetype level.
///
/// This is needed to implement [`ExactSizeIterator`] for
@ -1015,7 +1213,7 @@ unsafe impl<T: Component> QueryFilter for Changed<T> {
/// [Tuples](prim@tuple) and [`Or`] filters are automatically implemented with the trait only if its containing types
/// also implement the same trait.
///
/// [`Added`] and [`Changed`] works with entities, and therefore are not archetypal. As such
/// [`Added`], [`Changed`] and [`Spawned`] work with entities, and therefore are not archetypal. As such
/// they do not implement [`ArchetypeFilter`].
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a valid `Query` filter based on archetype information",

View File

@ -287,7 +287,14 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
pub fn from_builder(builder: &mut QueryBuilder<D, F>) -> Self {
let mut fetch_state = D::init_state(builder.world_mut());
let filter_state = F::init_state(builder.world_mut());
D::set_access(&mut fetch_state, builder.access());
let mut component_access = FilteredAccess::default();
D::update_component_access(&fetch_state, &mut component_access);
D::provide_extra_access(
&mut fetch_state,
component_access.access_mut(),
builder.access().access(),
);
let mut component_access = builder.access().clone();
@ -452,8 +459,8 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// This is equivalent to `self.iter().next().is_none()`, and thus the worst case runtime will be `O(n)`
/// where `n` is the number of *potential* matches. This can be notably expensive for queries that rely
/// on non-archetypal filters such as [`Added`] or [`Changed`] which must individually check each query
/// result for a match.
/// on non-archetypal filters such as [`Added`], [`Changed`] or [`Spawned`] which must individually check
/// each query result for a match.
///
/// # Panics
///
@ -461,6 +468,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// [`Added`]: crate::query::Added
/// [`Changed`]: crate::query::Changed
/// [`Spawned`]: crate::query::Spawned
#[inline]
pub fn is_empty(&self, world: &World, last_run: Tick, this_run: Tick) -> bool {
self.validate_world(world.id());
@ -753,29 +761,27 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
let mut fetch_state = NewD::get_state(world.components()).expect("Could not create fetch_state, Please initialize all referenced components before transmuting.");
let filter_state = NewF::get_state(world.components()).expect("Could not create filter_state, Please initialize all referenced components before transmuting.");
fn to_readonly(mut access: FilteredAccess<ComponentId>) -> FilteredAccess<ComponentId> {
access.access_mut().clear_writes();
access
}
let self_access = if D::IS_READ_ONLY && self.component_access.access().has_any_write() {
let mut self_access = self.component_access.clone();
if D::IS_READ_ONLY {
// The current state was transmuted from a mutable
// `QueryData` to a read-only one.
// Ignore any write access in the current state.
&to_readonly(self.component_access.clone())
} else {
&self.component_access
};
self_access.access_mut().clear_writes();
}
NewD::set_access(&mut fetch_state, self_access);
NewD::update_component_access(&fetch_state, &mut component_access);
NewD::provide_extra_access(
&mut fetch_state,
component_access.access_mut(),
self_access.access(),
);
let mut filter_component_access = FilteredAccess::default();
NewF::update_component_access(&filter_state, &mut filter_component_access);
component_access.extend(&filter_component_access);
assert!(
component_access.is_subset(self_access),
component_access.is_subset(&self_access),
"Transmuted state for {} attempts to access terms that are not allowed by original state {}.",
core::any::type_name::<(NewD, NewF)>(), core::any::type_name::<(D, F)>()
);
@ -787,7 +793,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
is_dense: self.is_dense,
fetch_state,
filter_state,
component_access: self.component_access.clone(),
component_access: self_access,
matched_tables: self.matched_tables.clone(),
matched_archetypes: self.matched_archetypes.clone(),
#[cfg(feature = "trace")]
@ -881,8 +887,12 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
}
}
NewD::set_access(&mut new_fetch_state, &joined_component_access);
NewD::update_component_access(&new_fetch_state, &mut component_access);
NewD::provide_extra_access(
&mut new_fetch_state,
component_access.access_mut(),
joined_component_access.access(),
);
let mut new_filter_component_access = FilteredAccess::default();
NewF::update_component_access(&new_filter_state, &mut new_filter_component_access);
@ -984,7 +994,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// assert_eq!(component_values, [&A(0), &A(1), &A(2)]);
///
/// let wrong_entity = Entity::from_raw(365);
/// let wrong_entity = Entity::from_raw_u32(365).unwrap();
///
/// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity);
/// ```
@ -1022,7 +1032,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// assert_eq!(component_values, [&A(0), &A(1), &A(2)]);
///
/// let wrong_entity = Entity::from_raw(365);
/// let wrong_entity = Entity::from_raw_u32(365).unwrap();
///
/// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity);
/// ```
@ -1078,7 +1088,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// assert_eq!(component_values, [&A(5), &A(6), &A(7)]);
///
/// let wrong_entity = Entity::from_raw(57);
/// let wrong_entity = Entity::from_raw_u32(57).unwrap();
/// let invalid_entity = world.spawn_empty().id();
///
/// assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity);
@ -1124,7 +1134,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// assert_eq!(component_values, [&A(5), &A(6), &A(7)]);
///
/// let wrong_entity = Entity::from_raw(57);
/// let wrong_entity = Entity::from_raw_u32(57).unwrap();
/// let invalid_entity = world.spawn_empty().id();
///
/// assert_eq!(match query_state.get_many_unique(&mut world, UniqueEntityArray::from([wrong_entity])).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity);
@ -1452,7 +1462,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// # assert_eq!(component_values, [&A(5), &A(6), &A(7)]);
///
/// # let wrong_entity = Entity::from_raw(57);
/// # let wrong_entity = Entity::from_raw_u32(57).unwrap();
/// # let invalid_entity = world.spawn_empty().id();
///
/// # assert_eq!(match query_state.get_many(&mut world, [wrong_entity]).unwrap_err() {QueryEntityError::EntityDoesNotExist(error) => error.entity, _ => panic!()}, wrong_entity);
@ -1773,7 +1783,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// fn my_system(query: Query<&A>) -> Result {
/// let a = query.single()?;
///
///
/// // Do something with `a`
/// Ok(())
/// }
@ -1791,16 +1801,6 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
self.query(world).single_inner()
}
/// A deprecated alias for [`QueryState::single`].
#[deprecated(since = "0.16.0", note = "Please use `single` instead.")]
#[inline]
pub fn get_single<'w>(
&mut self,
world: &'w World,
) -> Result<ROQueryItem<'w, D>, QuerySingleError> {
self.single(world)
}
/// Returns a single mutable query result when there is exactly one entity matching
/// the query.
///
@ -1818,15 +1818,6 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
self.query_mut(world).single_inner()
}
/// A deprecated alias for [`QueryState::single_mut`].
#[deprecated(since = "0.16.0", note = "Please use `single` instead.")]
pub fn get_single_mut<'w>(
&mut self,
world: &'w mut World,
) -> Result<D::Item<'w>, QuerySingleError> {
self.single_mut(world)
}
/// Returns a query result when there is exactly one entity matching the query.
///
/// If the number of query results is not exactly one, a [`QuerySingleError`] is returned
@ -1894,7 +1885,7 @@ mod tests {
let world_2 = World::new();
let mut query_state = world_1.query::<Entity>();
let _panics = query_state.get(&world_2, Entity::from_raw(0));
let _panics = query_state.get(&world_2, Entity::from_raw_u32(0).unwrap());
}
#[test]
@ -2062,12 +2053,12 @@ mod tests {
fn can_transmute_filtered_entity() {
let mut world = World::new();
let entity = world.spawn((A(0), B(1))).id();
let query =
QueryState::<(Entity, &A, &B)>::new(&mut world).transmute::<FilteredEntityRef>(&world);
let query = QueryState::<(Entity, &A, &B)>::new(&mut world)
.transmute::<(Entity, FilteredEntityRef)>(&world);
let mut query = query;
// Our result is completely untyped
let entity_ref = query.single(&world).unwrap();
let (_entity, entity_ref) = query.single(&world).unwrap();
assert_eq!(entity, entity_ref.id());
assert_eq!(0, entity_ref.get::<A>().unwrap().0);
@ -2285,11 +2276,11 @@ mod tests {
let query_1 = QueryState::<&mut A>::new(&mut world);
let query_2 = QueryState::<&mut B>::new(&mut world);
let mut new_query: QueryState<FilteredEntityMut> = query_1.join(&world, &query_2);
let mut new_query: QueryState<(Entity, FilteredEntityMut)> = query_1.join(&world, &query_2);
let mut entity = new_query.single_mut(&mut world).unwrap();
assert!(entity.get_mut::<A>().is_some());
assert!(entity.get_mut::<B>().is_some());
let (_entity, mut entity_mut) = new_query.single_mut(&mut world).unwrap();
assert!(entity_mut.get_mut::<A>().is_some());
assert!(entity_mut.get_mut::<B>().is_some());
}
#[test]
@ -2315,6 +2306,10 @@ mod tests {
let mut query = QueryState::<Has<C>>::new(&mut world);
assert_eq!(3, query.iter(&world).count());
// Allows should bypass the filter entirely
let mut query = QueryState::<(), Allows<C>>::new(&mut world);
assert_eq!(3, query.iter(&world).count());
// Other filters should still be respected
let mut query = QueryState::<Has<C>, Without<B>>::new(&mut world);
assert_eq!(1, query.iter(&world).count());

View File

@ -13,11 +13,11 @@ use variadics_please::all_tuples;
/// # Safety
///
/// Implementor must ensure that
/// [`update_component_access`], [`matches_component_set`], [`QueryData::fetch`], [`QueryFilter::filter_fetch`] and [`init_fetch`]
/// [`update_component_access`], [`QueryData::provide_extra_access`], [`matches_component_set`], [`QueryData::fetch`], [`QueryFilter::filter_fetch`] and [`init_fetch`]
/// obey the following:
///
/// - For each component mutably accessed by [`QueryData::fetch`], [`update_component_access`] should add write access unless read or write access has already been added, in which case it should panic.
/// - For each component readonly accessed by [`QueryData::fetch`] or [`QueryFilter::filter_fetch`], [`update_component_access`] should add read access unless write access has already been added, in which case it should panic.
/// - For each component mutably accessed by [`QueryData::fetch`], [`update_component_access`] or [`QueryData::provide_extra_access`] should add write access unless read or write access has already been added, in which case it should panic.
/// - For each component readonly accessed by [`QueryData::fetch`] or [`QueryFilter::filter_fetch`], [`update_component_access`] or [`QueryData::provide_extra_access`] should add read access unless write access has already been added, in which case it should panic.
/// - If `fetch` mutably accesses the same component twice, [`update_component_access`] should panic.
/// - [`update_component_access`] may not add a `Without` filter for a component unless [`matches_component_set`] always returns `false` when the component set contains that component.
/// - [`update_component_access`] may not add a `With` filter for a component unless [`matches_component_set`] always returns `false` when the component set doesn't contain that component.
@ -27,9 +27,11 @@ use variadics_please::all_tuples;
/// - Each filter in that disjunction must be a conjunction of the corresponding element's filter with the previous `access`
/// - For each resource readonly accessed by [`init_fetch`], [`update_component_access`] should add read access.
/// - Mutable resource access is not allowed.
/// - Any access added during [`QueryData::provide_extra_access`] must be a subset of `available_access`, and must not conflict with any access in `access`.
///
/// When implementing [`update_component_access`], note that `add_read` and `add_write` both also add a `With` filter, whereas `extend_access` does not change the filters.
///
/// [`QueryData::provide_extra_access`]: crate::query::QueryData::provide_extra_access
/// [`QueryData::fetch`]: crate::query::QueryData::fetch
/// [`QueryFilter::filter_fetch`]: crate::query::QueryFilter::filter_fetch
/// [`init_fetch`]: Self::init_fetch
@ -101,12 +103,6 @@ pub unsafe trait WorldQuery {
/// - `state` must be the [`State`](Self::State) that `fetch` was initialized with.
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table);
/// Sets available accesses for implementors with dynamic access such as [`FilteredEntityRef`](crate::world::FilteredEntityRef)
/// or [`FilteredEntityMut`](crate::world::FilteredEntityMut).
///
/// Called when constructing a [`QueryLens`](crate::system::QueryLens) or calling [`QueryState::from_builder`](super::QueryState::from_builder)
fn set_access(_state: &mut Self::State, _access: &FilteredAccess<ComponentId>) {}
/// Adds any component accesses used by this [`WorldQuery`] to `access`.
///
/// Used to check which queries are disjoint and can run in parallel

View File

@ -47,6 +47,11 @@ impl<'w> EntityWorldMut<'w> {
self
}
/// Removes the relation `R` between this entity and all its related entities.
pub fn clear_related<R: Relationship>(&mut self) -> &mut Self {
self.remove::<R::RelationshipTarget>()
}
/// Relates the given entities to this entity with the relation `R`, starting at this particular index.
///
/// If the `related` has duplicates, a related entity will take the index of its last occurrence in `related`.
@ -81,7 +86,7 @@ impl<'w> EntityWorldMut<'w> {
let id = self.id();
self.world_scope(|world| {
for (offset, related) in related.iter().enumerate() {
let index = index + offset;
let index = index.saturating_add(offset);
if world
.get::<R>(*related)
.is_some_and(|relationship| relationship.get() == id)
@ -376,6 +381,13 @@ impl<'a> EntityCommands<'a> {
})
}
/// Removes the relation `R` between this entity and all its related entities.
pub fn clear_related<R: Relationship>(&mut self) -> &mut Self {
self.queue(|mut entity: EntityWorldMut| {
entity.clear_related::<R>();
})
}
/// Relates the given entities to this entity with the relation `R`, starting at this particular index.
///
/// If the `related` has duplicates, a related entity will take the index of its last occurrence in `related`.
@ -519,6 +531,16 @@ impl<'w, R: Relationship> RelatedSpawner<'w, R> {
pub fn target_entity(&self) -> Entity {
self.target
}
/// Returns a reference to the underlying [`World`].
pub fn world(&self) -> &World {
self.world
}
/// Returns a mutable reference to the underlying [`World`].
pub fn world_mut(&mut self) -> &mut World {
self.world
}
}
/// Uses commands to spawn related "source" entities with the given [`Relationship`], targeting
@ -613,4 +635,19 @@ mod tests {
assert!(!world.entity(entity).contains::<TestComponent>());
}
}
#[test]
fn remove_all_related() {
let mut world = World::new();
let a = world.spawn_empty().id();
let b = world.spawn(ChildOf(a)).id();
let c = world.spawn(ChildOf(a)).id();
world.entity_mut(a).clear_related::<ChildOf>();
assert_eq!(world.entity(a).get::<Children>(), None);
assert_eq!(world.entity(b).get::<ChildOf>(), None);
assert_eq!(world.entity(c).get::<ChildOf>(), None);
}
}

View File

@ -1,5 +1,12 @@
use crate::entity::{hash_set::EntityHashSet, Entity};
use alloc::collections::{btree_set, BTreeSet};
use core::{
hash::BuildHasher,
ops::{Deref, DerefMut},
};
use crate::entity::{Entity, EntityHashSet, EntityIndexSet};
use alloc::vec::Vec;
use indexmap::IndexSet;
use smallvec::SmallVec;
/// The internal [`Entity`] collection used by a [`RelationshipTarget`](crate::relationship::RelationshipTarget) component.
@ -213,15 +220,14 @@ impl OrderedRelationshipSourceCollection for Vec<Entity> {
fn place_most_recent(&mut self, index: usize) {
if let Some(entity) = self.pop() {
let index = index.min(self.len().saturating_sub(1));
let index = index.min(self.len());
self.insert(index, entity);
}
}
fn place(&mut self, entity: Entity, index: usize) {
if let Some(current) = <[Entity]>::iter(self).position(|e| *e == entity) {
// The len is at least 1, so the subtraction is safe.
let index = index.min(self.len().saturating_sub(1));
let index = index.min(self.len());
Vec::remove(self, current);
self.insert(index, entity);
};
@ -445,6 +451,138 @@ impl<const N: usize> OrderedRelationshipSourceCollection for SmallVec<[Entity; N
}
}
impl<S: BuildHasher + Default> RelationshipSourceCollection for IndexSet<Entity, S> {
type SourceIter<'a>
= core::iter::Copied<indexmap::set::Iter<'a, Entity>>
where
S: 'a;
fn new() -> Self {
IndexSet::default()
}
fn reserve(&mut self, additional: usize) {
self.reserve(additional);
}
fn with_capacity(capacity: usize) -> Self {
IndexSet::with_capacity_and_hasher(capacity, S::default())
}
fn add(&mut self, entity: Entity) -> bool {
self.insert(entity)
}
fn remove(&mut self, entity: Entity) -> bool {
self.shift_remove(&entity)
}
fn iter(&self) -> Self::SourceIter<'_> {
self.iter().copied()
}
fn len(&self) -> usize {
self.len()
}
fn clear(&mut self) {
self.clear();
}
fn shrink_to_fit(&mut self) {
self.shrink_to_fit();
}
fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) {
self.extend(entities);
}
}
impl RelationshipSourceCollection for EntityIndexSet {
type SourceIter<'a> = core::iter::Copied<crate::entity::index_set::Iter<'a>>;
fn new() -> Self {
EntityIndexSet::new()
}
fn reserve(&mut self, additional: usize) {
self.deref_mut().reserve(additional);
}
fn with_capacity(capacity: usize) -> Self {
EntityIndexSet::with_capacity(capacity)
}
fn add(&mut self, entity: Entity) -> bool {
self.insert(entity)
}
fn remove(&mut self, entity: Entity) -> bool {
self.deref_mut().shift_remove(&entity)
}
fn iter(&self) -> Self::SourceIter<'_> {
self.iter().copied()
}
fn len(&self) -> usize {
self.deref().len()
}
fn clear(&mut self) {
self.deref_mut().clear();
}
fn shrink_to_fit(&mut self) {
self.deref_mut().shrink_to_fit();
}
fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) {
self.extend(entities);
}
}
impl RelationshipSourceCollection for BTreeSet<Entity> {
type SourceIter<'a> = core::iter::Copied<btree_set::Iter<'a, Entity>>;
fn new() -> Self {
BTreeSet::new()
}
fn with_capacity(_: usize) -> Self {
// BTreeSet doesn't have a capacity
Self::new()
}
fn reserve(&mut self, _: usize) {
// BTreeSet doesn't have a capacity
}
fn add(&mut self, entity: Entity) -> bool {
self.insert(entity)
}
fn remove(&mut self, entity: Entity) -> bool {
self.remove(&entity)
}
fn iter(&self) -> Self::SourceIter<'_> {
self.iter().copied()
}
fn len(&self) -> usize {
self.len()
}
fn clear(&mut self) {
self.clear();
}
fn shrink_to_fit(&mut self) {
// BTreeSet doesn't have a capacity
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -547,6 +685,40 @@ mod tests {
assert_eq!(a, world.get::<Below>(c).unwrap().0);
}
#[test]
fn entity_index_map() {
#[derive(Component)]
#[relationship(relationship_target = RelTarget)]
struct Rel(Entity);
#[derive(Component)]
#[relationship_target(relationship = Rel, linked_spawn)]
struct RelTarget(EntityHashSet);
let mut world = World::new();
let a = world.spawn_empty().id();
let b = world.spawn_empty().id();
let c = world.spawn_empty().id();
let d = world.spawn_empty().id();
world.entity_mut(a).add_related::<Rel>(&[b, c, d]);
let rel_target = world.get::<RelTarget>(a).unwrap();
let collection = rel_target.collection();
// Insertions should maintain ordering
assert!(collection.iter().eq(&[d, c, b]));
world.entity_mut(c).despawn();
let rel_target = world.get::<RelTarget>(a).unwrap();
let collection = rel_target.collection();
// Removals should maintain ordering
assert!(collection.iter().eq(&[d, b]));
}
#[test]
#[should_panic]
fn one_to_one_relationship_shared_target() {
@ -557,7 +729,6 @@ mod tests {
#[derive(Component)]
#[relationship_target(relationship = Above)]
struct Below(Entity);
let mut world = World::new();
let a = world.spawn_empty().id();
let b = world.spawn_empty().id();

View File

@ -6,6 +6,7 @@ mod single_threaded;
use alloc::{borrow::Cow, vec, vec::Vec};
use core::any::TypeId;
#[expect(deprecated, reason = "We still need to support this.")]
pub use self::{simple::SimpleExecutor, single_threaded::SingleThreadedExecutor};
#[cfg(feature = "std")]
@ -18,7 +19,7 @@ use crate::{
component::{ComponentId, Tick},
error::{BevyError, ErrorContext, Result},
prelude::{IntoSystemSet, SystemSet},
query::Access,
query::{Access, FilteredAccessSet},
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
@ -53,6 +54,10 @@ pub enum ExecutorKind {
SingleThreaded,
/// Like [`SingleThreaded`](ExecutorKind::SingleThreaded) but calls [`apply_deferred`](crate::system::System::apply_deferred)
/// immediately after running each system.
#[deprecated(
since = "0.17.0",
note = "Use SingleThreaded instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
)]
Simple,
/// Runs the schedule using a thread pool. Non-conflicting systems can run in parallel.
#[cfg(feature = "std")]
@ -118,17 +123,6 @@ impl SystemSchedule {
}
}
/// See [`ApplyDeferred`].
#[deprecated(
since = "0.16.0",
note = "Use `ApplyDeferred` instead. This was previously a function but is now a marker struct System."
)]
#[expect(
non_upper_case_globals,
reason = "This item is deprecated; as such, its previous name needs to stay."
)]
pub const apply_deferred: ApplyDeferred = ApplyDeferred;
/// A special [`System`] that instructs the executor to call
/// [`System::apply_deferred`] on the systems that have run but not applied
/// their [`Deferred`] system parameters (like [`Commands`]) or other system buffers.
@ -174,6 +168,10 @@ impl System for ApplyDeferred {
const { &Access::new() }
}
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
const { &FilteredAccessSet::new() }
}
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
// This system accesses no archetype components.
const { &Access::new() }
@ -267,38 +265,54 @@ impl IntoSystemSet<()> for ApplyDeferred {
mod __rust_begin_short_backtrace {
use core::hint::black_box;
#[cfg(feature = "std")]
use crate::world::unsafe_world_cell::UnsafeWorldCell;
use crate::{
error::Result,
system::{ReadOnlySystem, ScheduleSystem},
world::{unsafe_world_cell::UnsafeWorldCell, World},
world::World,
};
/// # Safety
/// See `System::run_unsafe`.
// This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.
#[cfg(feature = "std")]
#[inline(never)]
pub(super) unsafe fn run_unsafe(system: &mut ScheduleSystem, world: UnsafeWorldCell) -> Result {
let result = system.run_unsafe((), world);
// Call `black_box` to prevent this frame from being tail-call optimized away
black_box(());
result
}
/// # Safety
/// See `ReadOnlySystem::run_unsafe`.
#[cfg_attr(
not(feature = "std"),
expect(dead_code, reason = "currently only used with the std feature")
)]
// This is only used by `MultiThreadedExecutor`, and would be dead code without `std`.
#[cfg(feature = "std")]
#[inline(never)]
pub(super) unsafe fn readonly_run_unsafe<O: 'static>(
system: &mut dyn ReadOnlySystem<In = (), Out = O>,
world: UnsafeWorldCell,
) -> O {
// Call `black_box` to prevent this frame from being tail-call optimized away
black_box(system.run_unsafe((), world))
}
#[inline(never)]
pub(super) fn run(system: &mut ScheduleSystem, world: &mut World) -> Result {
let result = system.run((), world);
// Call `black_box` to prevent this frame from being tail-call optimized away
black_box(());
result
}
#[inline(never)]
pub(super) fn run_without_applying_deferred(
system: &mut ScheduleSystem,
world: &mut World,
) -> Result {
let result = system.run_without_applying_deferred((), world);
// Call `black_box` to prevent this frame from being tail-call optimized away
black_box(());
result
}
@ -308,6 +322,7 @@ mod __rust_begin_short_backtrace {
system: &mut dyn ReadOnlySystem<In = (), Out = O>,
world: &mut World,
) -> O {
// Call `black_box` to prevent this frame from being tail-call optimized away
black_box(system.run((), world))
}
}
@ -325,6 +340,7 @@ mod tests {
struct TestComponent;
const EXECUTORS: [ExecutorKind; 3] = [
#[expect(deprecated, reason = "We still need to test this.")]
ExecutorKind::Simple,
ExecutorKind::SingleThreaded,
ExecutorKind::MultiThreaded,
@ -385,6 +401,7 @@ mod tests {
let mut world = World::new();
let mut schedule = Schedule::default();
#[expect(deprecated, reason = "We still need to test this.")]
schedule.set_executor_kind(ExecutorKind::Simple);
schedule.add_systems(look_for_missing_resource);
schedule.run(&mut world);

View File

@ -1,7 +1,7 @@
use alloc::{boxed::Box, vec::Vec};
use bevy_platform::sync::Arc;
use bevy_tasks::{ComputeTaskPool, Scope, TaskPool, ThreadExecutor};
use bevy_utils::{default, syncunsafecell::SyncUnsafeCell};
use bevy_utils::syncunsafecell::SyncUnsafeCell;
use concurrent_queue::ConcurrentQueue;
use core::{any::Any, panic::AssertUnwindSafe};
use fixedbitset::FixedBitSet;
@ -13,10 +13,8 @@ use std::sync::{Mutex, MutexGuard};
use tracing::{info_span, Span};
use crate::{
archetype::ArchetypeComponentId,
error::{default_error_handler, BevyError, ErrorContext, Result},
error::{ErrorContext, ErrorHandler, Result},
prelude::Resource,
query::Access,
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
system::ScheduleSystem,
world::{unsafe_world_cell::UnsafeWorldCell, World},
@ -62,8 +60,13 @@ impl<'env, 'sys> Environment<'env, 'sys> {
/// Per-system data used by the [`MultiThreadedExecutor`].
// Copied here because it can't be read from the system when it's running.
struct SystemTaskMetadata {
/// The [`ArchetypeComponentId`] access of the system.
archetype_component_access: Access<ArchetypeComponentId>,
/// The set of systems whose `component_access_set()` conflicts with this one.
conflicting_systems: FixedBitSet,
/// The set of systems whose `component_access_set()` conflicts with this system's conditions.
/// Note that this is separate from `conflicting_systems` to handle the case where
/// a system is skipped by an earlier system set condition or system stepping,
/// and needs access to run its conditions but not for itself.
condition_conflicting_systems: FixedBitSet,
/// Indices of the systems that directly depend on the system.
dependents: Vec<usize>,
/// Is `true` if the system does not access `!Send` data.
@ -97,8 +100,8 @@ pub struct MultiThreadedExecutor {
pub struct ExecutorState {
/// Metadata for scheduling and running system tasks.
system_task_metadata: Vec<SystemTaskMetadata>,
/// Union of the accesses of all currently running systems.
active_access: Access<ArchetypeComponentId>,
/// The set of systems whose `component_access_set()` conflicts with this system set's conditions.
set_condition_conflicting_systems: Vec<FixedBitSet>,
/// Returns `true` if a system with non-`Send` access is running.
local_thread_running: bool,
/// Returns `true` if an exclusive system is running.
@ -131,7 +134,7 @@ pub struct ExecutorState {
struct Context<'scope, 'env, 'sys> {
environment: &'env Environment<'env, 'sys>,
scope: &'scope Scope<'scope, 'env, ()>,
error_handler: fn(BevyError, ErrorContext),
error_handler: ErrorHandler,
}
impl Default for MultiThreadedExecutor {
@ -164,7 +167,8 @@ impl SystemExecutor for MultiThreadedExecutor {
state.system_task_metadata = Vec::with_capacity(sys_count);
for index in 0..sys_count {
state.system_task_metadata.push(SystemTaskMetadata {
archetype_component_access: default(),
conflicting_systems: FixedBitSet::with_capacity(sys_count),
condition_conflicting_systems: FixedBitSet::with_capacity(sys_count),
dependents: schedule.system_dependents[index].clone(),
is_send: schedule.systems[index].is_send(),
is_exclusive: schedule.systems[index].is_exclusive(),
@ -174,6 +178,60 @@ impl SystemExecutor for MultiThreadedExecutor {
}
}
{
#[cfg(feature = "trace")]
let _span = info_span!("calculate conflicting systems").entered();
for index1 in 0..sys_count {
let system1 = &schedule.systems[index1];
for index2 in 0..index1 {
let system2 = &schedule.systems[index2];
if !system2
.component_access_set()
.is_compatible(system1.component_access_set())
{
state.system_task_metadata[index1]
.conflicting_systems
.insert(index2);
state.system_task_metadata[index2]
.conflicting_systems
.insert(index1);
}
}
for index2 in 0..sys_count {
let system2 = &schedule.systems[index2];
if schedule.system_conditions[index1].iter().any(|condition| {
!system2
.component_access_set()
.is_compatible(condition.component_access_set())
}) {
state.system_task_metadata[index1]
.condition_conflicting_systems
.insert(index2);
}
}
}
state.set_condition_conflicting_systems.clear();
state.set_condition_conflicting_systems.reserve(set_count);
for set_idx in 0..set_count {
let mut conflicting_systems = FixedBitSet::with_capacity(sys_count);
for sys_index in 0..sys_count {
let system = &schedule.systems[sys_index];
if schedule.set_conditions[set_idx].iter().any(|condition| {
!system
.component_access_set()
.is_compatible(condition.component_access_set())
}) {
conflicting_systems.insert(sys_index);
}
}
state
.set_condition_conflicting_systems
.push(conflicting_systems);
}
}
state.num_dependencies_remaining = Vec::with_capacity(sys_count);
}
@ -182,7 +240,7 @@ impl SystemExecutor for MultiThreadedExecutor {
schedule: &mut SystemSchedule,
world: &mut World,
_skip_systems: Option<&FixedBitSet>,
error_handler: fn(BevyError, ErrorContext),
error_handler: ErrorHandler,
) {
let state = self.state.get_mut().unwrap();
// reset counts
@ -257,7 +315,6 @@ impl SystemExecutor for MultiThreadedExecutor {
debug_assert!(state.ready_systems.is_clear());
debug_assert!(state.running_systems.is_clear());
state.active_access.clear();
state.evaluated_sets.clear();
state.skipped_systems.clear();
state.completed_systems.clear();
@ -345,9 +402,9 @@ impl ExecutorState {
fn new() -> Self {
Self {
system_task_metadata: Vec::new(),
set_condition_conflicting_systems: Vec::new(),
num_running_systems: 0,
num_dependencies_remaining: Vec::new(),
active_access: default(),
local_thread_running: false,
exclusive_running: false,
evaluated_sets: FixedBitSet::new(),
@ -368,8 +425,6 @@ impl ExecutorState {
self.finish_system_and_handle_dependents(result);
}
self.rebuild_active_access();
// SAFETY:
// - `finish_system_and_handle_dependents` has updated the currently running systems.
// - `rebuild_active_access` locks access for all currently running systems.
@ -429,6 +484,7 @@ impl ExecutorState {
system,
conditions,
context.environment.world_cell,
context.error_handler,
)
} {
self.skip_system_and_signal_dependents(system_index);
@ -488,37 +544,30 @@ impl ExecutorState {
{
for condition in &mut conditions.set_conditions[set_idx] {
condition.update_archetype_component_access(world);
if !condition
.archetype_component_access()
.is_compatible(&self.active_access)
{
return false;
}
}
if !self.set_condition_conflicting_systems[set_idx].is_disjoint(&self.running_systems) {
return false;
}
}
for condition in &mut conditions.system_conditions[system_index] {
condition.update_archetype_component_access(world);
if !condition
.archetype_component_access()
.is_compatible(&self.active_access)
{
return false;
}
}
if !system_meta
.condition_conflicting_systems
.is_disjoint(&self.running_systems)
{
return false;
}
if !self.skipped_systems.contains(system_index) {
system.update_archetype_component_access(world);
if !system
.archetype_component_access()
.is_compatible(&self.active_access)
if !system_meta
.conflicting_systems
.is_disjoint(&self.running_systems)
{
return false;
}
self.system_task_metadata[system_index]
.archetype_component_access
.clone_from(system.archetype_component_access());
}
true
@ -536,9 +585,9 @@ impl ExecutorState {
system: &mut ScheduleSystem,
conditions: &mut Conditions,
world: UnsafeWorldCell,
error_handler: ErrorHandler,
) -> bool {
let mut should_run = !self.skipped_systems.contains(system_index);
let error_handler = default_error_handler();
for set_idx in conditions.sets_with_conditions_of_systems[system_index].ones() {
if self.evaluated_sets.contains(set_idx) {
@ -551,7 +600,11 @@ impl ExecutorState {
// required by the conditions.
// - `update_archetype_component_access` has been called for each run condition.
let set_conditions_met = unsafe {
evaluate_and_fold_conditions(&mut conditions.set_conditions[set_idx], world)
evaluate_and_fold_conditions(
&mut conditions.set_conditions[set_idx],
world,
error_handler,
)
};
if !set_conditions_met {
@ -569,7 +622,11 @@ impl ExecutorState {
// required by the conditions.
// - `update_archetype_component_access` has been called for each run condition.
let system_conditions_met = unsafe {
evaluate_and_fold_conditions(&mut conditions.system_conditions[system_index], world)
evaluate_and_fold_conditions(
&mut conditions.system_conditions[system_index],
world,
error_handler,
)
};
if !system_conditions_met {
@ -648,9 +705,6 @@ impl ExecutorState {
context.system_completed(system_index, res, system);
};
self.active_access
.extend(&system_meta.archetype_component_access);
if system_meta.is_send {
context.scope.spawn(task);
} else {
@ -741,15 +795,6 @@ impl ExecutorState {
}
}
}
fn rebuild_active_access(&mut self) {
self.active_access.clear();
for index in self.running_systems.ones() {
let system_meta = &self.system_task_metadata[index];
self.active_access
.extend(&system_meta.archetype_component_access);
}
}
}
fn apply_deferred(
@ -786,9 +831,8 @@ fn apply_deferred(
unsafe fn evaluate_and_fold_conditions(
conditions: &mut [BoxedCondition],
world: UnsafeWorldCell,
error_handler: ErrorHandler,
) -> bool {
let error_handler = default_error_handler();
#[expect(
clippy::unnecessary_fold,
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."

View File

@ -1,3 +1,5 @@
#![expect(deprecated, reason = "Everything here is deprecated")]
use core::panic::AssertUnwindSafe;
use fixedbitset::FixedBitSet;
@ -8,7 +10,7 @@ use tracing::info_span;
use std::eprintln;
use crate::{
error::{default_error_handler, BevyError, ErrorContext},
error::{ErrorContext, ErrorHandler},
schedule::{
executor::is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule,
},
@ -20,6 +22,10 @@ use super::__rust_begin_short_backtrace;
/// A variant of [`SingleThreadedExecutor`](crate::schedule::SingleThreadedExecutor) that calls
/// [`apply_deferred`](crate::system::System::apply_deferred) immediately after running each system.
#[derive(Default)]
#[deprecated(
since = "0.17.0",
note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
)]
pub struct SimpleExecutor {
/// Systems sets whose conditions have been evaluated.
evaluated_sets: FixedBitSet,
@ -44,7 +50,7 @@ impl SystemExecutor for SimpleExecutor {
schedule: &mut SystemSchedule,
world: &mut World,
_skip_systems: Option<&FixedBitSet>,
error_handler: fn(BevyError, ErrorContext),
error_handler: ErrorHandler,
) {
// If stepping is enabled, make sure we skip those systems that should
// not be run.
@ -67,8 +73,11 @@ impl SystemExecutor for SimpleExecutor {
}
// evaluate system set's conditions
let set_conditions_met =
evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world);
let set_conditions_met = evaluate_and_fold_conditions(
&mut schedule.set_conditions[set_idx],
world,
error_handler,
);
if !set_conditions_met {
self.completed_systems
@ -80,8 +89,11 @@ impl SystemExecutor for SimpleExecutor {
}
// evaluate system's conditions
let system_conditions_met =
evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world);
let system_conditions_met = evaluate_and_fold_conditions(
&mut schedule.system_conditions[system_index],
world,
error_handler,
);
should_run &= system_conditions_met;
@ -165,10 +177,15 @@ impl SimpleExecutor {
}
}
}
fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool {
let error_handler = default_error_handler();
#[deprecated(
since = "0.17.0",
note = "Use SingleThreadedExecutor instead. See https://github.com/bevyengine/bevy/issues/18453 for motivation."
)]
fn evaluate_and_fold_conditions(
conditions: &mut [BoxedCondition],
world: &mut World,
error_handler: ErrorHandler,
) -> bool {
#[expect(
clippy::unnecessary_fold,
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."

View File

@ -8,7 +8,7 @@ use tracing::info_span;
use std::eprintln;
use crate::{
error::{default_error_handler, BevyError, ErrorContext},
error::{ErrorContext, ErrorHandler},
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
world::World,
};
@ -50,7 +50,7 @@ impl SystemExecutor for SingleThreadedExecutor {
schedule: &mut SystemSchedule,
world: &mut World,
_skip_systems: Option<&FixedBitSet>,
error_handler: fn(BevyError, ErrorContext),
error_handler: ErrorHandler,
) {
// If stepping is enabled, make sure we skip those systems that should
// not be run.
@ -73,8 +73,11 @@ impl SystemExecutor for SingleThreadedExecutor {
}
// evaluate system set's conditions
let set_conditions_met =
evaluate_and_fold_conditions(&mut schedule.set_conditions[set_idx], world);
let set_conditions_met = evaluate_and_fold_conditions(
&mut schedule.set_conditions[set_idx],
world,
error_handler,
);
if !set_conditions_met {
self.completed_systems
@ -86,8 +89,11 @@ impl SystemExecutor for SingleThreadedExecutor {
}
// evaluate system's conditions
let system_conditions_met =
evaluate_and_fold_conditions(&mut schedule.system_conditions[system_index], world);
let system_conditions_met = evaluate_and_fold_conditions(
&mut schedule.system_conditions[system_index],
world,
error_handler,
);
should_run &= system_conditions_met;
@ -128,33 +134,16 @@ impl SystemExecutor for SingleThreadedExecutor {
}
let f = AssertUnwindSafe(|| {
if system.is_exclusive() {
if let Err(err) = __rust_begin_short_backtrace::run(system, world) {
error_handler(
err,
ErrorContext::System {
name: system.name(),
last_run: system.get_last_run(),
},
);
}
} else {
// Use run_unsafe to avoid immediately applying deferred buffers
let world = world.as_unsafe_world_cell();
system.update_archetype_component_access(world);
// SAFETY: We have exclusive, single-threaded access to the world and
// update_archetype_component_access is being called immediately before this.
unsafe {
if let Err(err) = __rust_begin_short_backtrace::run_unsafe(system, world) {
error_handler(
err,
ErrorContext::System {
name: system.name(),
last_run: system.get_last_run(),
},
);
}
};
if let Err(err) =
__rust_begin_short_backtrace::run_without_applying_deferred(system, world)
{
error_handler(
err,
ErrorContext::System {
name: system.name(),
last_run: system.get_last_run(),
},
);
}
});
@ -210,9 +199,11 @@ impl SingleThreadedExecutor {
}
}
fn evaluate_and_fold_conditions(conditions: &mut [BoxedCondition], world: &mut World) -> bool {
let error_handler: fn(BevyError, ErrorContext) = default_error_handler();
fn evaluate_and_fold_conditions(
conditions: &mut [BoxedCondition],
world: &mut World,
error_handler: ErrorHandler,
) -> bool {
#[expect(
clippy::unnecessary_fold,
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."

Some files were not shown because too many files have changed in this diff Show More