diff --git a/benches/benches/bevy_ecs/bundles/insert_many.rs b/benches/benches/bevy_ecs/bundles/insert_many.rs new file mode 100644 index 0000000000000..fbc0409f4d057 --- /dev/null +++ b/benches/benches/bevy_ecs/bundles/insert_many.rs @@ -0,0 +1,68 @@ +use benches::bench; +use bevy_ecs::{component::Component, world::World}; +use criterion::Criterion; + +const ENTITY_COUNT: usize = 2_000; + +#[derive(Component)] +struct C(usize); + +#[derive(Component)] +struct W(T); + +pub fn insert_many(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group(bench!("insert_many")); + + group.bench_function("all", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world + .spawn_empty() + .insert(C::<0>(1)) + .insert(C::<1>(1)) + .insert(C::<2>(1)) + .insert(C::<3>(1)) + .insert(C::<4>(1)) + .insert(C::<5>(1)) + .insert(C::<6>(1)) + .insert(C::<7>(1)) + .insert(C::<8>(1)) + .insert(C::<9>(1)) + .insert(C::<10>(1)) + .insert(C::<11>(1)) + .insert(C::<12>(1)) + .insert(C::<13>(1)) + .insert(C::<14>(1)); + } + }); + }); + + group.bench_function("only_last", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world + .spawn(( + C::<0>(1), + C::<1>(1), + C::<2>(1), + C::<3>(1), + C::<4>(1), + C::<5>(1), + C::<6>(1), + C::<7>(1), + C::<8>(1), + C::<9>(1), + C::<10>(1), + C::<11>(1), + C::<12>(1), + C::<13>(1), + )) + .insert(C::<14>(1)); + } + }); + }); + + group.finish(); +} diff --git a/benches/benches/bevy_ecs/bundles/mod.rs b/benches/benches/bevy_ecs/bundles/mod.rs new file mode 100644 index 0000000000000..1fb7421f626c9 --- /dev/null +++ b/benches/benches/bevy_ecs/bundles/mod.rs @@ -0,0 +1,63 @@ +use bevy_ecs::{ + bundle::{BoundedBundleKey, Bundle, ComponentsFromBundle}, + component::{ComponentId, ComponentsRegistrator, RequiredComponents, StorageType}, + ptr::OwningPtr, +}; +use criterion::criterion_group; + +mod insert_many; +mod spawn_many; +mod spawn_many_zst; +mod spawn_one_zst; + +criterion_group!( + benches, + spawn_one_zst::spawn_one_zst, + spawn_many_zst::spawn_many_zst, + spawn_many::spawn_many, + insert_many::insert_many, +); + +struct MakeDynamic(B); + +// SAFETY: +// - setting is_static and is_bounded to false is always safe and makes cache_key irrelevant +// - everything else is delegated to B, which is a valid Bundle +unsafe impl Bundle for MakeDynamic { + fn is_static() -> bool { + false + } + + fn is_bounded() -> bool { + false + } + + fn cache_key(&self) -> BoundedBundleKey { + BoundedBundleKey::empty() + } + + fn component_ids( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId), + ) { + self.0.component_ids(components, ids); + } + + fn register_required_components( + &self, + components: &mut ComponentsRegistrator, + required_components: &mut RequiredComponents, + ) { + self.0 + .register_required_components(components, required_components); + } +} + +impl ComponentsFromBundle for MakeDynamic { + type Effect = ::Effect; + + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { + self.0.get_components(func) + } +} diff --git a/benches/benches/bevy_ecs/bundles/spawn_many.rs b/benches/benches/bevy_ecs/bundles/spawn_many.rs new file mode 100644 index 0000000000000..b1f2c8098637f --- /dev/null +++ b/benches/benches/bevy_ecs/bundles/spawn_many.rs @@ -0,0 +1,306 @@ +use core::hint::black_box; + +use benches::bench; +use bevy_ecs::{bundle::Bundle, component::Component, world::World}; +use criterion::Criterion; + +use super::MakeDynamic; + +const ENTITY_COUNT: usize = 2_000; + +#[derive(Component)] +struct C(usize); + +#[derive(Component)] +struct W(T); + +pub fn spawn_many(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group(bench!("spawn_many")); + + group.bench_function("static", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(( + C::<0>(1), + C::<1>(1), + C::<2>(1), + C::<3>(1), + C::<4>(1), + C::<5>(1), + C::<6>(1), + C::<7>(1), + C::<8>(1), + C::<9>(1), + C::<10>(1), + C::<11>(1), + C::<12>(1), + C::<13>(1), + C::<14>(1), + ))); + } + }); + }); + + group.bench_function("option_some_many", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(Some(( + C::<0>(1), + C::<1>(1), + C::<2>(1), + C::<3>(1), + C::<4>(1), + C::<5>(1), + C::<6>(1), + C::<7>(1), + C::<8>(1), + C::<9>(1), + C::<10>(1), + C::<11>(1), + C::<12>(1), + C::<13>(1), + C::<14>(1), + )))); + } + }); + }); + + group.bench_function("option_one_some", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(( + C::<0>(1), + C::<1>(1), + C::<2>(1), + C::<3>(1), + C::<4>(1), + C::<5>(1), + C::<6>(1), + C::<7>(1), + C::<8>(1), + C::<9>(1), + C::<10>(1), + C::<11>(1), + C::<12>(1), + C::<13>(1), + Some(C::<14>(1)), + ))); + } + }); + }); + + group.bench_function("option_many_some", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(( + Some(C::<0>(1)), + Some(C::<1>(1)), + Some(C::<2>(1)), + Some(C::<3>(1)), + Some(C::<4>(1)), + Some(C::<5>(1)), + Some(C::<6>(1)), + Some(C::<7>(1)), + Some(C::<8>(1)), + Some(C::<9>(1)), + Some(C::<10>(1)), + Some(C::<11>(1)), + Some(C::<12>(1)), + Some(C::<13>(1)), + Some(C::<14>(1)), + ))); + } + }); + }); + + group.bench_function("option_none_many", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box( + None::<( + C<0>, + C<1>, + C<2>, + C<3>, + C<4>, + C<5>, + C<6>, + C<7>, + C<8>, + C<9>, + C<10>, + C<11>, + C<12>, + C<13>, + C<14>, + )>, + )); + } + }); + }); + + group.bench_function("option_one_none", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(( + C::<0>(1), + C::<1>(1), + C::<2>(1), + C::<3>(1), + C::<4>(1), + C::<5>(1), + C::<6>(1), + C::<7>(1), + C::<8>(1), + C::<9>(1), + C::<10>(1), + C::<11>(1), + C::<12>(1), + C::<13>(1), + None::>, + ))); + } + }); + }); + + group.bench_function("option_many_none_and_static", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(( + (C::<0>(1), None::>>), + (C::<1>(1), None::>>), + (C::<2>(1), None::>>), + (C::<3>(1), None::>>), + (C::<4>(1), None::>>), + (C::<5>(1), None::>>), + (C::<6>(1), None::>>), + (C::<7>(1), None::>>), + (C::<8>(1), None::>>), + (C::<9>(1), None::>>), + (C::<10>(1), None::>>), + (C::<11>(1), None::>>), + (C::<12>(1), None::>>), + (C::<13>(1), None::>>), + (C::<14>(1), None::>>), + ))); + } + }); + }); + + group.bench_function("many_box_dyn_bundle", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // Note: C is a ZST so Box::new is not actually allocating + // this is mostly measuring the overhead of dynamic bundles + // and dynamic dispatch. + world.spawn(black_box(( + Box::new(C::<0>(1)) as Box, + Box::new(C::<1>(1)) as Box, + Box::new(C::<2>(1)) as Box, + Box::new(C::<3>(1)) as Box, + Box::new(C::<4>(1)) as Box, + Box::new(C::<5>(1)) as Box, + Box::new(C::<6>(1)) as Box, + Box::new(C::<7>(1)) as Box, + Box::new(C::<8>(1)) as Box, + Box::new(C::<9>(1)) as Box, + Box::new(C::<10>(1)) as Box, + Box::new(C::<11>(1)) as Box, + Box::new(C::<12>(1)) as Box, + Box::new(C::<13>(1)) as Box, + Box::new(C::<14>(1)) as Box, + ))); + } + }); + }); + + group.bench_function("box_dyn_bundle_many", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // Note: This will also count the cost of allocating a box + world.spawn(black_box(Box::new(( + C::<0>(1), + C::<1>(1), + C::<2>(1), + C::<3>(1), + C::<4>(1), + C::<5>(1), + C::<6>(1), + C::<7>(1), + C::<8>(1), + C::<9>(1), + C::<10>(1), + C::<11>(1), + C::<12>(1), + C::<13>(1), + C::<14>(1), + )) as Box)); + } + }); + }); + + group.bench_function("dynamic_bundle_many", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // This has no other overhead than opting out of the static and bounded caching + // for the whole bundle + world.spawn(MakeDynamic(( + C::<0>(1), + C::<1>(1), + C::<2>(1), + C::<3>(1), + C::<4>(1), + C::<5>(1), + C::<6>(1), + C::<7>(1), + C::<8>(1), + C::<9>(1), + C::<10>(1), + C::<11>(1), + C::<12>(1), + C::<13>(1), + C::<14>(1), + ))); + } + }); + }); + + group.bench_function("many_dynamic_bundle", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // This has no other overhead than opting out of the static and bounded caching + // for each sub-bundle + world.spawn(black_box(( + MakeDynamic(C::<0>(1)), + MakeDynamic(C::<1>(1)), + MakeDynamic(C::<2>(1)), + MakeDynamic(C::<3>(1)), + MakeDynamic(C::<4>(1)), + MakeDynamic(C::<5>(1)), + MakeDynamic(C::<6>(1)), + MakeDynamic(C::<7>(1)), + MakeDynamic(C::<8>(1)), + MakeDynamic(C::<9>(1)), + MakeDynamic(C::<10>(1)), + MakeDynamic(C::<11>(1)), + MakeDynamic(C::<12>(1)), + MakeDynamic(C::<13>(1)), + MakeDynamic(C::<14>(1)), + ))); + } + }); + }); + + group.finish(); +} diff --git a/benches/benches/bevy_ecs/bundles/spawn_many_zst.rs b/benches/benches/bevy_ecs/bundles/spawn_many_zst.rs new file mode 100644 index 0000000000000..6013897279bd8 --- /dev/null +++ b/benches/benches/bevy_ecs/bundles/spawn_many_zst.rs @@ -0,0 +1,206 @@ +use core::hint::black_box; + +use benches::bench; +use bevy_ecs::{bundle::Bundle, component::Component, world::World}; +use criterion::Criterion; + +use super::MakeDynamic; + +const ENTITY_COUNT: usize = 2_000; + +#[derive(Component)] +struct C; + +#[derive(Component)] +struct W(T); + +pub fn spawn_many_zst(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group(bench!("spawn_many_zst")); + + group.bench_function("static", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(( + C::<0>, C::<1>, C::<2>, C::<3>, C::<4>, C::<5>, C::<6>, C::<7>, C::<8>, C::<9>, + C::<10>, C::<11>, C::<12>, C::<13>, C::<14>, + ))); + } + }); + }); + + group.bench_function("option_some_many", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(Some(( + C::<0>, C::<1>, C::<2>, C::<3>, C::<4>, C::<5>, C::<6>, C::<7>, C::<8>, C::<9>, + C::<10>, C::<11>, C::<12>, C::<13>, C::<14>, + )))); + } + }); + }); + + group.bench_function("option_many_some", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(( + Some(C::<0>), + Some(C::<1>), + Some(C::<2>), + Some(C::<3>), + Some(C::<4>), + Some(C::<5>), + Some(C::<6>), + Some(C::<7>), + Some(C::<8>), + Some(C::<9>), + Some(C::<10>), + Some(C::<11>), + Some(C::<12>), + Some(C::<13>), + Some(C::<14>), + ))); + } + }); + }); + + group.bench_function("option_none_many", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box( + None::<( + C<0>, + C<1>, + C<2>, + C<3>, + C<4>, + C<5>, + C<6>, + C<7>, + C<8>, + C<9>, + C<10>, + C<11>, + C<12>, + C<13>, + C<14>, + )>, + )); + } + }); + }); + + group.bench_function("option_many_none_and_static", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(( + (C::<0>, None::>>), + (C::<1>, None::>>), + (C::<2>, None::>>), + (C::<3>, None::>>), + (C::<4>, None::>>), + (C::<5>, None::>>), + (C::<6>, None::>>), + (C::<7>, None::>>), + (C::<8>, None::>>), + (C::<9>, None::>>), + (C::<10>, None::>>), + (C::<11>, None::>>), + (C::<12>, None::>>), + (C::<13>, None::>>), + (C::<14>, None::>>), + ))); + } + }); + }); + + group.bench_function("many_box_dyn_bundle", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // Note: C is a ZST so Box::new is not actually allocating + // this is mostly measuring the overhead of dynamic bundles + // and dynamic dispatch. + world.spawn(black_box(( + Box::new(C::<0>) as Box, + Box::new(C::<1>) as Box, + Box::new(C::<2>) as Box, + Box::new(C::<3>) as Box, + Box::new(C::<4>) as Box, + Box::new(C::<5>) as Box, + Box::new(C::<6>) as Box, + Box::new(C::<7>) as Box, + Box::new(C::<8>) as Box, + Box::new(C::<9>) as Box, + Box::new(C::<10>) as Box, + Box::new(C::<11>) as Box, + Box::new(C::<12>) as Box, + Box::new(C::<13>) as Box, + Box::new(C::<14>) as Box, + ))); + } + }); + }); + + group.bench_function("box_dyn_bundle_many", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // Note: C is a ZST so Box::new is not actually allocating + // this is mostly measuring the overhead of dynamic bundles + // and dynamic dispatch. + world.spawn(black_box(Box::new(( + C::<0>, C::<1>, C::<2>, C::<3>, C::<4>, C::<5>, C::<6>, C::<7>, C::<8>, C::<9>, + C::<10>, C::<11>, C::<12>, C::<13>, C::<14>, + )) as Box)); + } + }); + }); + + group.bench_function("dynamic_bundle_many", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // This has no other overhead than opting out of the static and bounded caching + // for the whole bundle + world.spawn(MakeDynamic(( + C::<0>, C::<1>, C::<2>, C::<3>, C::<4>, C::<5>, C::<6>, C::<7>, C::<8>, C::<9>, + C::<10>, C::<11>, C::<12>, C::<13>, C::<14>, + ))); + } + }); + }); + + group.bench_function("many_dynamic_bundle", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // This has no other overhead than opting out of the static and bounded caching + // for each sub-bundle + world.spawn(black_box(( + MakeDynamic(C::<0>), + MakeDynamic(C::<1>), + MakeDynamic(C::<2>), + MakeDynamic(C::<3>), + MakeDynamic(C::<4>), + MakeDynamic(C::<5>), + MakeDynamic(C::<6>), + MakeDynamic(C::<7>), + MakeDynamic(C::<8>), + MakeDynamic(C::<9>), + MakeDynamic(C::<10>), + MakeDynamic(C::<11>), + MakeDynamic(C::<12>), + MakeDynamic(C::<13>), + MakeDynamic(C::<14>), + ))); + } + }); + }); + + group.finish(); +} diff --git a/benches/benches/bevy_ecs/bundles/spawn_one_zst.rs b/benches/benches/bevy_ecs/bundles/spawn_one_zst.rs new file mode 100644 index 0000000000000..790413cdfe0b8 --- /dev/null +++ b/benches/benches/bevy_ecs/bundles/spawn_one_zst.rs @@ -0,0 +1,79 @@ +use core::hint::black_box; + +use benches::bench; +use bevy_ecs::{bundle::Bundle, component::Component, world::World}; +use criterion::Criterion; + +use super::MakeDynamic; + +const ENTITY_COUNT: usize = 10_000; + +#[derive(Component)] +struct A; + +#[derive(Component)] +struct B; + +pub fn spawn_one_zst(criterion: &mut Criterion) { + let mut group = criterion.benchmark_group(bench!("spawn_one_zst")); + + group.bench_function("static", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(A)); + } + }); + }); + + group.bench_function("option_some", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(Some(A))); + } + }); + }); + + group.bench_function("option_none", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box(None::)); + } + }); + }); + + group.bench_function("option_none_and_static", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + world.spawn(black_box((A, None::))); + } + }); + }); + + group.bench_function("box_dyn_bundle", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // Note: A is a ZST so Box::new is not actually allocating + // this is mostly measuring the overhead of dynamic bundles + // and dynamic dispatch. + world.spawn(black_box(Box::new(A) as Box)); + } + }); + }); + + group.bench_function("dynamic_bundle", |bencher| { + bencher.iter(|| { + let mut world = World::new(); + for _ in 0..ENTITY_COUNT { + // This has no other overhead than opting out of the static and bounded caching + world.spawn(black_box(MakeDynamic(A))); + } + }); + }); + + group.finish(); +} diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 0eaae27ce4b00..cebf2a81acc7e 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -1,7 +1,7 @@ use core::hint::black_box; use benches::bench; -use bevy_ecs::bundle::Bundle; +use bevy_ecs::bundle::{Bundle, StaticBundle}; use bevy_ecs::component::ComponentCloneBehavior; use bevy_ecs::entity::EntityCloner; use bevy_ecs::hierarchy::ChildOf; @@ -53,7 +53,7 @@ type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10); /// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to /// use the [`Reflect`] trait instead of [`Clone`]. -fn reflection_cloner( +fn reflection_cloner( world: &mut World, linked_cloning: bool, ) -> EntityCloner { @@ -69,7 +69,10 @@ fn reflection_cloner( // Recursively register all components in the bundle, then save the component IDs to a list. // This uses `contributed_components()`, meaning both explicit and required component IDs in // this bundle are saved. - let component_ids: Vec<_> = world.register_bundle::().contributed_components().into(); + let component_ids: Vec<_> = world + .register_static_bundle::() + .contributed_components() + .into(); let mut builder = EntityCloner::build(world); @@ -92,7 +95,7 @@ fn reflection_cloner( /// components (which is usually [`ComponentCloneHandler::clone_handler()`]). If `clone_via_reflect` /// is true, it will overwrite the handler for all components in the bundle to be /// [`ComponentCloneHandler::reflect_handler()`]. -fn bench_clone( +fn bench_clone( b: &mut Bencher, clone_via_reflect: bool, ) { @@ -124,7 +127,7 @@ fn bench_clone( /// For example, setting `height` to 5 and `children` to 1 creates a single chain of entities with /// no siblings. Alternatively, setting `height` to 1 and `children` to 5 will spawn 5 direct /// children of the root entity. -fn bench_clone_hierarchy( +fn bench_clone_hierarchy( b: &mut Bencher, height: usize, children: usize, diff --git a/benches/benches/bevy_ecs/main.rs b/benches/benches/bevy_ecs/main.rs index 4a025ab829369..59b4c1fd7326a 100644 --- a/benches/benches/bevy_ecs/main.rs +++ b/benches/benches/bevy_ecs/main.rs @@ -5,6 +5,7 @@ use criterion::criterion_main; +mod bundles; mod change_detection; mod components; mod empty_archetypes; @@ -18,6 +19,7 @@ mod scheduling; mod world; criterion_main!( + bundles::benches, change_detection::benches, components::benches, empty_archetypes::benches, diff --git a/benches/benches/bevy_ecs/world/world_get.rs b/benches/benches/bevy_ecs/world/world_get.rs index e6e2a0bb903ef..23f1dd9b9271c 100644 --- a/benches/benches/bevy_ecs/world/world_get.rs +++ b/benches/benches/bevy_ecs/world/world_get.rs @@ -2,7 +2,7 @@ use core::hint::black_box; use nonmax::NonMaxU32; use bevy_ecs::{ - bundle::{Bundle, NoBundleEffect}, + bundle::{Bundle, NoBundleEffect, StaticBundle}, component::Component, entity::{Entity, EntityRow}, system::{Query, SystemState}, @@ -37,7 +37,9 @@ fn setup(entity_count: u32) -> World { black_box(world) } -fn setup_wide + Default>(entity_count: u32) -> World { +fn setup_wide + StaticBundle + Default>( + entity_count: u32, +) -> World { let mut world = World::default(); world.spawn_batch((0..entity_count).map(|_| T::default())); black_box(world) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index af5183b159f6b..0702248f78ab1 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -9,6 +9,7 @@ use alloc::{ }; pub use bevy_derive::AppLabel; use bevy_ecs::{ + bundle::StaticBundle, component::RequiredComponentsError, error::{DefaultErrorHandler, ErrorHandler}, event::{event_update_system, EventCursor}, @@ -1340,7 +1341,7 @@ impl App { /// } /// }); /// ``` - pub fn add_observer( + pub fn add_observer( &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 8090cff7de1a7..cd074b4962c86 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -16,7 +16,7 @@ use crate::{ use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest}; use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; use syn::{ parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, ConstParam, Data, DataStruct, DeriveInput, GenericParam, Index, TypeParam, @@ -28,18 +28,21 @@ enum BundleFieldKind { } const BUNDLE_ATTRIBUTE_NAME: &str = "bundle"; +const BUNDLE_ATTRIBUTE_DYNAMIC: &str = "dynamic"; const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore"; const BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS: &str = "ignore_from_components"; #[derive(Debug)] struct BundleAttributes { impl_from_components: bool, + dynamic: bool, } impl Default for BundleAttributes { fn default() -> Self { Self { impl_from_components: true, + dynamic: false, } } } @@ -51,7 +54,6 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let ecs_path = bevy_ecs_path(); let mut errors = vec![]; - let mut attributes = BundleAttributes::default(); for attr in &ast.attrs { @@ -61,8 +63,12 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { attributes.impl_from_components = false; return Ok(()); } + if meta.path.is_ident(BUNDLE_ATTRIBUTE_DYNAMIC) { + attributes.dynamic = true; + return Ok(()); + } - Err(meta.error(format!("Invalid bundle container attribute. Allowed attributes: `{BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS}`"))) + Err(meta.error(format!("Invalid bundle container attribute. Allowed attributes: `{BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS}`, `{BUNDLE_ATTRIBUTE_DYNAMIC}`"))) }); if let Err(error) = parsing { @@ -79,6 +85,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let mut field_kind = Vec::with_capacity(named_fields.len()); for field in named_fields { + let mut kind = BundleFieldKind::Component; + for attr in field .attrs .iter() @@ -86,7 +94,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { { if let Err(error) = attr.parse_nested_meta(|meta| { if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) { - field_kind.push(BundleFieldKind::Ignore); + kind = BundleFieldKind::Ignore; Ok(()) } else { Err(meta.error(format!( @@ -98,7 +106,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { } } - field_kind.push(BundleFieldKind::Component); + field_kind.push(kind); } let field = named_fields @@ -111,53 +119,26 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { .map(|field| &field.ty) .collect::>(); - let mut field_component_ids = Vec::new(); - let mut field_get_component_ids = Vec::new(); - let mut field_get_components = Vec::new(); - let mut field_from_components = Vec::new(); - let mut field_required_components = Vec::new(); + let mut active_field_types = Vec::new(); + let mut active_field_tokens = Vec::new(); + let mut inactive_field_tokens = Vec::new(); for (((i, field_type), field_kind), field) in field_type .iter() .enumerate() .zip(field_kind.iter()) .zip(field.iter()) { + let field_tokens = match field { + Some(field) => field.to_token_stream(), + None => Index::from(i).to_token_stream(), + }; match field_kind { BundleFieldKind::Component => { - field_component_ids.push(quote! { - <#field_type as #ecs_path::bundle::Bundle>::component_ids(components, &mut *ids); - }); - field_required_components.push(quote! { - <#field_type as #ecs_path::bundle::Bundle>::register_required_components(components, required_components); - }); - field_get_component_ids.push(quote! { - <#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids); - }); - match field { - Some(field) => { - field_get_components.push(quote! { - self.#field.get_components(&mut *func); - }); - field_from_components.push(quote! { - #field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), - }); - } - None => { - let index = Index::from(i); - field_get_components.push(quote! { - self.#index.get_components(&mut *func); - }); - field_from_components.push(quote! { - #index: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), - }); - } - } + active_field_types.push(field_type); + active_field_tokens.push(field_tokens); } - BundleFieldKind::Ignore => { - field_from_components.push(quote! { - #field: ::core::default::Default::default(), - }); + inactive_field_tokens.push(field_tokens); } } } @@ -165,60 +146,100 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let struct_name = &ast.ident; - let from_components = attributes.impl_from_components.then(|| quote! { - // SAFETY: - // - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order - #[allow(deprecated)] - unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause { - #[allow(unused_variables, non_snake_case)] - unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self - where - __F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_> - { - Self{ - #(#field_from_components)* - } - } - } - }); - - let attribute_errors = &errors; - - TokenStream::from(quote! { - #(#attribute_errors)* - + let static_bundle_impl = (!attributes.dynamic).then(|| quote! { // SAFETY: - // - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order - // - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass - // the correct `StorageType` into the callback. + // - all the active fields must implement `StaticBundle` for the function bodies to compile, and hence + // this bundle also represents a static set of components; + // - `component_ids` and `get_component_ids` delegate to the underlying implementation in the same order + // and hence are coherent; #[allow(deprecated)] - unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause { + unsafe impl #impl_generics #ecs_path::bundle::StaticBundle for #struct_name #ty_generics #where_clause { fn component_ids( components: &mut #ecs_path::component::ComponentsRegistrator, ids: &mut impl FnMut(#ecs_path::component::ComponentId) ){ - #(#field_component_ids)* + #(<#active_field_types as #ecs_path::bundle::StaticBundle>::component_ids(components, &mut *ids);)* } fn get_component_ids( components: &#ecs_path::component::Components, ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>) ){ - #(#field_get_component_ids)* + #(<#active_field_types as #ecs_path::bundle::StaticBundle>::get_component_ids(components, &mut *ids);)* } fn register_required_components( components: &mut #ecs_path::component::ComponentsRegistrator, required_components: &mut #ecs_path::component::RequiredComponents ){ - #(#field_required_components)* + #(<#active_field_types as #ecs_path::bundle::StaticBundle>::register_required_components(components, &mut *required_components);)* } } + }); - #from_components + let bundle_impl = quote! { + // SAFETY: + // - if all sub-bundles are static then this bundle is static too; + // - if all sub-bundles are bounded by some sets then this bundle is bounded by their union; + // - `cache_key` ORs together the keys of the sub-bundles in order to compute a combined key; since + // no bits is overlapped and the original keys were associated to unique combinations, this new key + // is also associated with an unique combination; + // - `component_ids` calls `ids` for each component type in the bundle, in the exact order that + // `get_components` is called. + unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause { + fn is_static() -> bool { + true #(&& <#active_field_types as #ecs_path::bundle::Bundle>::is_static())* + } + fn is_bounded() -> bool { + true #(&& <#active_field_types as #ecs_path::bundle::Bundle>::is_bounded())* + } + fn cache_key(&self) -> #ecs_path::bundle::BoundedBundleKey { + #ecs_path::bundle::BoundedBundleKey::empty() + #( + .merge(<#active_field_types as #ecs_path::bundle::Bundle>::cache_key(&self.#active_field_tokens)) + )* + } + + fn component_ids( + &self, + components: &mut #ecs_path::component::ComponentsRegistrator, + ids: &mut impl FnMut(#ecs_path::component::ComponentId), + ) { + #(<#active_field_types as #ecs_path::bundle::Bundle>::component_ids(&self.#active_field_tokens, components, ids);)* + } + + fn register_required_components( + &self, + components: &mut #ecs_path::component::ComponentsRegistrator, + required_components: &mut #ecs_path::component::RequiredComponents, + ) { + #(<#active_field_types as #ecs_path::bundle::Bundle>::register_required_components(&self.#active_field_tokens, components, required_components);)* + } + } + }; + + let bundle_from_components_impl = (attributes.impl_from_components && !attributes.dynamic).then(|| quote! { + // SAFETY: + // - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order #[allow(deprecated)] - impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause { + unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause { + #[allow(unused_variables, non_snake_case)] + unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self + where + __F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_> + { + Self{ + #(#active_field_tokens: <#active_field_types as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),)* + #(#inactive_field_tokens: ::core::default::Default::default(),)* + } + } + } + }); + + let dynamic_bundle_impl = quote! { + #[allow(deprecated)] + impl #impl_generics #ecs_path::bundle::ComponentsFromBundle for #struct_name #ty_generics #where_clause { type Effect = (); #[allow(unused_variables)] #[inline] @@ -226,9 +247,17 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { self, func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>) ) { - #(#field_get_components)* + #(<#active_field_types as #ecs_path::bundle::ComponentsFromBundle>::get_components(self.#active_field_tokens, &mut *func);)* } } + }; + + TokenStream::from(quote! { + #(#errors)* + #static_bundle_impl + #bundle_impl + #bundle_from_components_impl + #dynamic_bundle_impl }) } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index a264f0b14bab9..00f9d7ea7da2c 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -81,11 +81,12 @@ use bevy_utils::TypeIdMap; use core::{any::TypeId, ptr::NonNull}; use variadics_please::all_tuples; -/// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity. +/// The `Bundle` trait enables insertion of [`Component`]s to an entity. +/// For the removal of [`Component`]s from an entity see the [`StaticBundle`]`trait`. /// /// Implementers of the `Bundle` trait are called 'bundles'. /// -/// Each bundle represents a static set of [`Component`] types. +/// Each bundle represents a possibly dynamic set of [`Component`] types. /// Currently, bundles can only contain one of each [`Component`], and will /// panic once initialized if this is not met. /// @@ -115,15 +116,6 @@ use variadics_please::all_tuples; /// contains the components of a bundle. /// Queries should instead only select the components they logically operate on. /// -/// ## Removal -/// -/// Bundles are also used when removing components from an entity. -/// -/// Removing a bundle from an entity will remove any of its components attached -/// to the entity from the entity. -/// That is, if the entity does not have all the components of the bundle, those -/// which are present will be removed. -/// /// # Implementers /// /// Every type which implements [`Component`] also implements `Bundle`, since @@ -146,7 +138,23 @@ use variadics_please::all_tuples; /// implement `Bundle`. /// As explained above, this includes any [`Component`] type, and other derived bundles. /// +/// [`Option`]s containing bundles also implement `Bundle`, and will insert the contained value if they +/// are `Some`, otherwise they will insert nothing. +/// +/// Fully ynamic bundles (`Box`) are also supported, and will insert the components held +/// by the underlying concrete bundle type. +/// +/// # Deriving `Bundle` +/// +/// The `Bundle`, [`StaticBundle`] and [`BundleFromComponents`] traits can be automatically implemented +/// for structs holding other bundles by using the [`derive@Bundle`] derive macro. +/// +/// In case some of your contained bundles are dynamic, like `Option` bundles and `Box`, +/// you can use the `#[bundle(dynamic)]` attribute to opt out of deriving [`StaticBundle`] and +/// [`BundleFromComponents`]. +/// /// If you want to add `PhantomData` to your `Bundle` you have to mark it with `#[bundle(ignore)]`. +/// /// ``` /// # use std::marker::PhantomData; /// use bevy_ecs::{component::Component, bundle::Bundle}; @@ -163,6 +171,18 @@ use variadics_please::all_tuples; /// y: YPosition, /// } /// +/// #[derive(Bundle)] +/// #[bundle(dynamic)] +/// struct DynamicBundle { +/// // A bundle needs to use #[bundle(dynamic)] +/// name: Option, +/// // or if it contains fully dynamic bundles +/// extra: Box, +/// +/// x: XPosition, +/// y: YPosition, +/// } +/// /// // You have to implement `Default` for ignored field types in bundle structs. /// #[derive(Default)] /// struct Other(f32); @@ -191,45 +211,119 @@ use variadics_please::all_tuples; /// /// [`Query`]: crate::system::Query // Some safety points: +// - [`Bundle::is_static`] must be pure and return `true` only if the set of components contained +// in this bundle type is fixed and always the same for every instance; +// - [`Bundle::is_bounded`] must be pure and return `true` only if the set of components contained +// in this bundle type's instances is a subset of a statically known set of components; +// - if this bundle is bounded then [`Bundle::cache_key`] must be pure and return an unique key for +// each combination of components contained in this bundle's instance; // - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the -// bundle, in the _exact_ order that [`DynamicBundle::get_components`] is called. -// - [`Bundle::from_components`] must call `func` exactly once for each [`ComponentId`] returned by +// bundle, in the _exact_ order that [`ComponentsFromBundle::get_components`] is called. +// - [`ComponentsFromBundle::get_components`] must call `func` exactly once for each [`ComponentId`] returned by // [`Bundle::component_ids`]. #[diagnostic::on_unimplemented( message = "`{Self}` is not a `Bundle`", label = "invalid `Bundle`", note = "consider annotating `{Self}` with `#[derive(Component)]` or `#[derive(Bundle)]`" )] -pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { - /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s +pub unsafe trait Bundle: BundleDyn + ComponentsFromBundle + Send + Sync + 'static { + /// Whether this is a static bundle or not. In case this is a static bundle the associated + /// [`BundleId`] and [`BundleInfo`] are known to be unique and can be cached by this type's [`TypeId`]. + /// + /// This is a hack to work around the lack of specialization. + #[doc(hidden)] + fn is_static() -> bool + where + Self: Sized; + + /// Whether the set of components this bundle includes is bounded or not. In case it is bounded we + /// can produce an additional cache key based on which components from the bounded set are included in + /// `self` or not, and use that to reuse an existing [`BundleId`] and [`BundleInfo`] associated with the + /// dynamic component set of `self`. + /// + /// This is a hack to work around the lack of specialization. + #[doc(hidden)] + fn is_bounded() -> bool + where + Self: Sized; + + /// Computes the cache key associated with `self` and its size. The key is limited to 64 bits, and if the + /// size exceeds that it should be ignored. + #[doc(hidden)] + fn cache_key(&self) -> BoundedBundleKey; + + /// Gets `self`'s component ids, in the order of this bundle's [`Component`]s + #[doc(hidden)] + fn component_ids( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId), + ) where + Self: Sized; + + /// Registers components that are required by the components in this [`Bundle`]. + #[doc(hidden)] + fn register_required_components( + &self, + _components: &mut ComponentsRegistrator, + _required_components: &mut RequiredComponents, + ); +} + +/// Each bundle represents a static and fixed set of [`Component`] types. +/// See the [`Bundle`] trait for a possibly dynamic set of [`Component`] types. +/// +/// Implementers of the `Bundle` trait are called 'static bundles'. +/// +/// ## Removal +/// +/// Static bundles are used when removing components from an entity. +/// +/// Removing a bundle from an entity will remove any of its components attached +/// to the entity from the entity. +/// That is, if the entity does not have all the components of the bundle, those +/// which are present will be removed. +/// +/// # Safety +/// +/// Manual implementations of this trait are unsupported. +/// That is, there is no safe way to implement this trait, and you must not do so. +/// If you want a type to implement [`StaticBundle`], you must use [`derive@Bundle`](derive@Bundle). +// Some safety points: +// - [`StaticBundle::component_ids`] and [`StaticBundle::get_component_ids`] must match the behavior of [`Bundle::component_ids`] +#[diagnostic::on_unimplemented( + message = "`{Self}` is not a `StaticBundle`", + label = "invalid `StaticBundle`", + note = "consider annotating `{Self}` with `#[derive(Component)]` or `#[derive(Bundle)]`" +)] +pub unsafe trait StaticBundle: Send + Sync + 'static { + /// Gets this [`StaticBundle`]'s component ids, in the order of this bundle's [`Component`]s #[doc(hidden)] fn component_ids(components: &mut ComponentsRegistrator, ids: &mut impl FnMut(ComponentId)); - /// Gets this [`Bundle`]'s component ids. This will be [`None`] if the component has not been registered. + /// Gets this [`StaticBundle`]'s component ids. This will be [`None`] if the component has not been registered. + #[doc(hidden)] fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)); - /// Registers components that are required by the components in this [`Bundle`]. + /// Registers components that are required by the components in this [`StaticBundle`]. + #[doc(hidden)] fn register_required_components( _components: &mut ComponentsRegistrator, _required_components: &mut RequiredComponents, ); } -/// Creates a [`Bundle`] by taking it from internal storage. +/// Creates a bundle by taking it from the internal storage. /// /// # Safety /// /// Manual implementations of this trait are unsupported. /// That is, there is no safe way to implement this trait, and you must not do so. /// If you want a type to implement [`Bundle`], you must use [`derive@Bundle`](derive@Bundle). -/// -/// [`Query`]: crate::system::Query // Some safety points: -// - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the -// bundle, in the _exact_ order that [`DynamicBundle::get_components`] is called. // - [`Bundle::from_components`] must call `func` exactly once for each [`ComponentId`] returned by // [`Bundle::component_ids`]. -pub unsafe trait BundleFromComponents { +pub unsafe trait BundleFromComponents: StaticBundle { /// Calls `func`, which should return data for each component in the bundle, in the order of /// this bundle's [`Component`]s /// @@ -245,9 +339,11 @@ pub unsafe trait BundleFromComponents { } /// The parts from [`Bundle`] that don't require statically knowing the components of the bundle. -pub trait DynamicBundle { +pub trait ComponentsFromBundle { /// An operation on the entity that happens _after_ inserting this bundle. - type Effect: BundleEffect; + type Effect: BundleEffect + where + Self: Sized; // SAFETY: // The `StorageType` argument passed into [`Bundle::get_components`] must be correct for the // component being fetched. @@ -255,7 +351,88 @@ pub trait DynamicBundle { /// Calls `func` on each value, in the order of this bundle's [`Component`]s. This passes /// ownership of the component values to `func`. #[doc(hidden)] - fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect; + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect + where + Self: Sized; +} + +/// The dyn-compatible version of [`Bundle`]. This is used to support `Box`. +#[doc(hidden)] +pub trait BundleDyn { + /// This is the dyn-compatible version of [`Bundle::component_ids`]. + #[doc(hidden)] + fn component_ids_dyn( + &self, + components: &mut ComponentsRegistrator, + ids: &mut dyn FnMut(ComponentId), + ); + + /// This is the dyn-compatible version of [`ComponentsFromBundle::get_components`]. + #[doc(hidden)] + fn get_components_dyn( + self: Box, + func: &mut dyn FnMut(StorageType, OwningPtr<'_>), + ) -> Box; +} + +impl BundleDyn for B { + fn component_ids_dyn( + &self, + components: &mut ComponentsRegistrator, + ids: &mut dyn FnMut(ComponentId), + ) { + self.component_ids(components, &mut |id| ids(id)); + } + + fn get_components_dyn( + self: Box, + func: &mut dyn FnMut(StorageType, OwningPtr<'_>), + ) -> Box { + Box::new(self.get_components(&mut |storage_type, ptr| func(storage_type, ptr))) + } +} + +// Safety: +// - `is_static` and `is_bounded` are false because `Box` could represent any bundle at runtime; +// - `cache_key` is irrelevant because `is_bounded` is false; +// - `component_ids` and `get_components` are implemented coherently because `component_ids_dyn` and +// `get_components_dyn` are implemented in terms of the underlying bundle's `component_ids` and `get_components`. +unsafe impl Bundle for Box { + fn is_static() -> bool { + false + } + + fn is_bounded() -> bool { + false + } + + fn cache_key(&self) -> BoundedBundleKey { + BoundedBundleKey::empty() + } + + fn component_ids( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId), + ) { + (**self).component_ids_dyn(components, ids); + } + + fn register_required_components( + &self, + components: &mut ComponentsRegistrator, + required_components: &mut RequiredComponents, + ) { + (**self).register_required_components(components, required_components); + } +} + +impl ComponentsFromBundle for Box { + type Effect = Box; + + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { + self.get_components_dyn(func) + } } /// An operation on an [`Entity`] that occurs _after_ inserting the [`Bundle`] that defined this bundle effect. @@ -265,20 +442,43 @@ pub trait DynamicBundle { /// 2. Relevant Hooks are run for the insert, then Observers /// 3. The [`BundleEffect`] is run. /// -/// See [`DynamicBundle::Effect`]. -pub trait BundleEffect { +/// See [`ComponentsFromBundle::Effect`]. +pub trait BundleEffect: BundleEffectDyn { /// Applies this effect to the given `entity`. fn apply(self, entity: &mut EntityWorldMut); } +/// The dyn-compatible version of [`BundleEffect`]. This is used to support `Box`. +#[doc(hidden)] +pub trait BundleEffectDyn { + #[doc(hidden)] + fn apply_dyn(self: Box, entity: &mut EntityWorldMut); +} + +impl BundleEffectDyn for E { + fn apply_dyn(self: Box, entity: &mut EntityWorldMut) { + self.apply(entity); + } +} + +impl BundleEffect for Box { + fn apply(self, entity: &mut EntityWorldMut) { + self.apply_dyn(entity); + } +} + // SAFETY: -// - `Bundle::component_ids` calls `ids` for C's component id (and nothing else) -// - `Bundle::get_components` is called exactly once for C and passes the component's storage type based on its associated constant. -unsafe impl Bundle for C { +// - `C` always represents the set of components containing just `C` +// - `component_ids` and `get_component_ids` both call `ids` just once for C's component id (and nothing else). +unsafe impl StaticBundle for C { fn component_ids(components: &mut ComponentsRegistrator, ids: &mut impl FnMut(ComponentId)) { ids(components.register_component::()); } + fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)) { + ids(components.get_id(TypeId::of::())); + } + fn register_required_components( components: &mut ComponentsRegistrator, required_components: &mut RequiredComponents, @@ -292,9 +492,39 @@ unsafe impl Bundle for C { &mut Vec::new(), ); } +} - fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)) { - ids(components.get_id(TypeId::of::())); +// SAFETY: +// - C is a static bundle, hence both `is_static` and `is_bounded` are true; +// - `cache_key` doesn't matter because `is_static` is true; +// - `component_ids` calls `ids` for C's component id (and nothing else) +// - `get_components` is called exactly once for C and passes the component's storage type based on its associated constant. +unsafe impl Bundle for C { + fn is_static() -> bool { + true + } + fn is_bounded() -> bool { + true + } + + fn cache_key(&self) -> BoundedBundleKey { + BoundedBundleKey::empty() + } + + fn component_ids( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId), + ) { + ::component_ids(components, ids); + } + + fn register_required_components( + &self, + components: &mut ComponentsRegistrator, + required_components: &mut RequiredComponents, + ) { + ::register_required_components(components, required_components); } } @@ -313,7 +543,7 @@ unsafe impl BundleFromComponents for C { } } -impl DynamicBundle for C { +impl ComponentsFromBundle for C { type Effect = (); #[inline] fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { @@ -334,25 +564,92 @@ macro_rules! tuple_impl { )] $(#[$meta])* // SAFETY: - // - `Bundle::component_ids` calls `ids` for each component type in the - // bundle, in the exact order that `DynamicBundle::get_components` is called. - // - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`. - // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct - // `StorageType` into the callback. - unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { + // - all the sub-bundles are static, and hence their combination is static too; + // - `component_ids` and `get_component_ids` both delegate to the sub-bundle's methods + // exactly once per sub-bundle, hence they are coherent. + unsafe impl<$($name: StaticBundle),*> StaticBundle for ($($name,)*) { fn component_ids(components: &mut ComponentsRegistrator, ids: &mut impl FnMut(ComponentId)){ - $(<$name as Bundle>::component_ids(components, ids);)* + $(<$name as StaticBundle>::component_ids(components, ids);)* } fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)){ - $(<$name as Bundle>::get_component_ids(components, ids);)* + $(<$name as StaticBundle>::get_component_ids(components, ids);)* } fn register_required_components( components: &mut ComponentsRegistrator, required_components: &mut RequiredComponents, ) { - $(<$name as Bundle>::register_required_components(components, required_components);)* + $(<$name as StaticBundle>::register_required_components(components, required_components);)* + } + } + + #[expect( + clippy::allow_attributes, + reason = "This is a tuple-related macro; as such, the lints below may not always apply." + )] + #[allow( + unused_mut, + unused_variables, + reason = "Zero-length tuples won't use any of the parameters." + )] + $(#[$meta])* + // SAFETY: + // - if all sub-bundles are static then this bundle is static too; + // - if all sub-bundles are bounded by some sets then this bundle is bounded by their union; + // - `cache_key` ORs together the keys of the sub-bundles in order to compute a combined key; since + // no bits is overlapped and the original keys were associated to unique combinations, this new key + // is also associated with an unique combination; + // - `component_ids` calls `ids` for each component type in the bundle, in the exact order that + // `get_components` is called. + unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) { + fn is_static() -> bool { + true $(&& <$name as Bundle>::is_static())* + } + fn is_bounded() -> bool { + true $(&& <$name as Bundle>::is_bounded())* + } + + fn cache_key(&self) -> BoundedBundleKey { + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + let ($($name,)*) = self; + + let mut key = 0; + let mut size = 0; + + BoundedBundleKey::empty() + $( .merge($name.cache_key()) )* + } + + fn component_ids( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId), + ) { + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + let ($($name,)*) = self; + + $($name.component_ids(components, ids);)* + } + + fn register_required_components( + &self, + components: &mut ComponentsRegistrator, + required_components: &mut RequiredComponents, + ) { + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + let ($($name,)*) = self; + + $($name.register_required_components(components, required_components);)* } } @@ -368,7 +665,7 @@ macro_rules! tuple_impl { $(#[$meta])* // SAFETY: // - `Bundle::component_ids` calls `ids` for each component type in the - // bundle, in the exact order that `DynamicBundle::get_components` is called. + // bundle, in the exact order that `ComponentsFromBundle::get_components` is called. // - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`. // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct // `StorageType` into the callback. @@ -401,7 +698,7 @@ macro_rules! tuple_impl { reason = "Zero-length tuples won't use any of the parameters." )] $(#[$meta])* - impl<$($name: Bundle),*> DynamicBundle for ($($name,)*) { + impl<$($name: Bundle),*> ComponentsFromBundle for ($($name,)*) { type Effect = ($($name::Effect,)*); #[allow( clippy::unused_unit, @@ -431,7 +728,7 @@ all_tuples!( ); /// A trait implemented for [`BundleEffect`] implementations that do nothing. This is used as a type constraint for -/// [`Bundle`] APIs that do not / cannot run [`DynamicBundle::Effect`], such as "batch spawn" APIs. +/// [`Bundle`] APIs that do not / cannot run [`ComponentsFromBundle::Effect`], such as "batch spawn" APIs. pub trait NoBundleEffect {} macro_rules! after_effect_impl { @@ -461,6 +758,70 @@ macro_rules! after_effect_impl { all_tuples!(after_effect_impl, 0, 15, P); +#[doc(hidden)] +#[derive(Clone, Copy)] +pub struct BoundedBundleKey { + key: u64, + len_bits: u32, +} + +impl BoundedBundleKey { + /// Creates an empty `BoundedBundleKey`. This should be used for static bundles as + /// there's only one subset of components it can represent. This can also be used + /// as a dummy key in dynamic bundles, since it is not used for them. + #[inline] + pub const fn empty() -> Self { + Self { + key: 0, + len_bits: 0, + } + } + + /// Returns whether this bundle key is valid. Currently this is the case if + /// it fits in 64 bits. + #[inline] + pub const fn is_valid(&self) -> bool { + self.len_bits <= 64 + } + + /// Merge two `BoundedBundleKey`s by appending `other` to the right of `self` + /// and producing a new `BoundedBundleKey` representing both of them. + /// + /// Note that for there can be multiple combinations of `self` and `other` that will + /// produce the same output, so care should be taken to avoid them. + /// A way this can be achieved is by ensuring that there exists a way to compute back + /// the length of the key given the key itself and the type it is for. + #[inline] + pub const fn merge(self, other: Self) -> Self { + let len_bits = self.len_bits + other.len_bits; + let key = self.key.wrapping_shl(other.len_bits) | other.key; + let merged = Self { key, len_bits }; + if merged.is_valid() { + merged + } else { + // Normalize invalid keys so that `len_bits` never overflows + Self { + key: 0, + len_bits: 65, + } + } + } + + /// Creates a new `BoundedBundleKey` from a literal key and its bits length. + /// It's suggested to run this in a `const` block to run the asserts at compile time. + /// + /// # Panics + /// + /// This panics if the given key requires more than `len_bits` to be represented + /// or if `len_bits` is more than 64. + #[inline] + pub const fn literal(key: u64, len_bits: u32) -> Self { + assert!(len_bits <= 64); + assert!(u64::BITS - key.leading_zeros() <= len_bits); + Self { key, len_bits } + } +} + /// For a specific [`World`], this stores a unique value identifying a type of a registered [`Bundle`]. /// /// [`World`]: crate::world::World @@ -524,7 +885,7 @@ impl BundleInfo { /// Every ID in `component_ids` must be valid within the World that owns the `BundleInfo` /// and must be in the same order as the source bundle type writes its components in. unsafe fn new( - bundle_type_name: &'static str, + bundle_type_name: &str, storages: &mut Storages, components: &Components, mut component_ids: Vec, @@ -664,7 +1025,7 @@ impl BundleInfo { /// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the /// `entity`, `bundle` must match this [`BundleInfo`]'s type #[inline] - unsafe fn write_components<'a, T: DynamicBundle, S: BundleComponentStatus>( + unsafe fn write_components<'a, T: ComponentsFromBundle, S: BundleComponentStatus>( &self, table: &mut Table, sparse_sets: &mut SparseSets, @@ -1058,6 +1419,7 @@ pub(crate) enum ArchetypeMoveType { impl<'w> BundleInserter<'w> { #[inline] pub(crate) fn new( + bundle: &T, world: &'w mut World, archetype_id: ArchetypeId, change_tick: Tick, @@ -1067,7 +1429,7 @@ impl<'w> BundleInserter<'w> { unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) }; let bundle_id = world .bundles - .register_info::(&mut registrator, &mut world.storages); + .register_info(bundle, &mut registrator, &mut world.storages); // SAFETY: We just ensured this bundle exists unsafe { Self::new_with_id(world, archetype_id, bundle_id, change_tick) } } @@ -1169,7 +1531,7 @@ impl<'w> BundleInserter<'w> { /// `entity` must currently exist in the source archetype for this inserter. `location` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type #[inline] - pub(crate) unsafe fn insert( + pub(crate) unsafe fn insert( &mut self, entity: Entity, location: EntityLocation, @@ -1450,7 +1812,7 @@ impl<'w> BundleRemover<'w> { /// # Safety /// Caller must ensure that `archetype_id` is valid #[inline] - pub(crate) unsafe fn new( + pub(crate) unsafe fn new( world: &'w mut World, archetype_id: ArchetypeId, require_all: bool, @@ -1460,7 +1822,7 @@ impl<'w> BundleRemover<'w> { unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) }; let bundle_id = world .bundles - .register_info::(&mut registrator, &mut world.storages); + .register_static_info::(&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) } } @@ -1722,13 +2084,25 @@ pub(crate) struct BundleSpawner<'w> { impl<'w> BundleSpawner<'w> { #[inline] - pub fn new(world: &'w mut World, change_tick: Tick) -> Self { + pub fn new(bundle: &T, world: &'w mut World, change_tick: Tick) -> 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(bundle, &mut registrator, &mut world.storages); + // SAFETY: we initialized this bundle_id in `init_info` + unsafe { Self::new_with_id(world, bundle_id, change_tick) } + } + + #[inline] + pub fn new_static(world: &'w mut World, change_tick: Tick) -> 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::(&mut registrator, &mut world.storages); + .register_static_info::(&mut registrator, &mut world.storages); // SAFETY: we initialized this bundle_id in `init_info` unsafe { Self::new_with_id(world, bundle_id, change_tick) } } @@ -1782,7 +2156,7 @@ impl<'w> BundleSpawner<'w> { /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type #[inline] #[track_caller] - pub unsafe fn spawn_non_existent( + pub unsafe fn spawn_non_existent( &mut self, entity: Entity, bundle: T, @@ -1892,13 +2266,18 @@ impl<'w> BundleSpawner<'w> { #[derive(Default)] pub struct Bundles { bundle_infos: Vec, - /// Cache static [`BundleId`] - bundle_ids: TypeIdMap, - /// Cache bundles, which contains both explicit and required components of [`Bundle`] - contributed_bundle_ids: TypeIdMap, - /// Cache dynamic [`BundleId`] with multiple components + + /// Cache [`BundleId`]s for static bundles + static_bundle_ids: TypeIdMap, + /// Cache [`BundleId`]s for bounded but non-static bundles + bounded_bundle_ids: HashMap<(TypeId, u64), BundleId>, + /// Cache [`BundleId`]s for dynamic bundles dynamic_bundle_ids: HashMap, BundleId>, dynamic_bundle_storages: HashMap>, + + /// Cache bundles, which contains both explicit and required components of [`Bundle`] + contributed_bundle_ids: TypeIdMap, + /// Cache optimized dynamic [`BundleId`] with single component dynamic_component_bundle_ids: HashMap, dynamic_component_storages: HashMap, @@ -1932,20 +2311,20 @@ impl Bundles { /// or if `type_id` does not correspond to a type of bundle. #[inline] pub fn get_id(&self, type_id: TypeId) -> Option { - self.bundle_ids.get(&type_id).cloned() + self.static_bundle_ids.get(&type_id).cloned() } /// Registers a new [`BundleInfo`] for a statically known type. /// /// Also registers all the components in the bundle. - pub(crate) fn register_info( + pub(crate) fn register_static_info( &mut self, components: &mut ComponentsRegistrator, storages: &mut Storages, ) -> BundleId { let bundle_infos = &mut self.bundle_infos; - *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { - let mut component_ids= Vec::new(); + *self.static_bundle_ids.entry(TypeId::of::()).or_insert_with(|| { + let mut component_ids = Vec::new(); T::component_ids(components, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); let bundle_info = @@ -1959,10 +2338,74 @@ impl Bundles { }) } + pub(crate) fn register_info( + &mut self, + bundle: &T, + components: &mut ComponentsRegistrator, + storages: &mut Storages, + ) -> BundleId { + let bundle_infos = &mut self.bundle_infos; + + // Fastest case, we have a static bundle + if T::is_static() { + return *self + .static_bundle_ids + .entry(TypeId::of::()) + .or_insert_with(|| { + Self::register_new_bundle(bundle, components, storages, bundle_infos) + }); + } + + // Optimized case for bounded bundles, e.g. those with conditional components whose + // key fits in 64 bits. + if T::is_bounded() { + let cache_key = bundle.cache_key(); + if cache_key.is_valid() { + return *self + .bounded_bundle_ids + .entry((TypeId::of::(), cache_key.key)) + .or_insert_with(|| { + Self::register_new_bundle(bundle, components, storages, bundle_infos) + }); + } + } + + // Fallback to registering a dynamic bundle + + let mut component_ids = Vec::new(); + bundle.component_ids(components, &mut |id| component_ids.push(id)); + + self.init_dynamic_info( + core::any::type_name::(), + storages, + components, + &component_ids, + ) + } + + fn register_new_bundle( + bundle: &T, + components: &mut ComponentsRegistrator, + storages: &mut Storages, + bundle_infos: &mut Vec, + ) -> BundleId { + let mut component_ids = Vec::new(); + bundle.component_ids(components, &mut |id| component_ids.push(id)); + let id = BundleId(bundle_infos.len()); + let bundle_info = + // SAFETY: T::component_ids ensures: + // - its info was created + // - appropriate storage for it has been initialized. + // - it was created in the same order as the components in T + unsafe { BundleInfo::new(core::any::type_name::(), storages, components, component_ids, id) }; + bundle_infos.push(bundle_info); + id + } + /// Registers a new [`BundleInfo`], which contains both explicit and required components for a statically known type. /// /// Also registers all the components in the bundle. - pub(crate) fn register_contributed_bundle_info( + pub(crate) fn register_static_contributed_bundle_info( &mut self, components: &mut ComponentsRegistrator, storages: &mut Storages, @@ -1970,7 +2413,7 @@ impl Bundles { if let Some(id) = self.contributed_bundle_ids.get(&TypeId::of::()).cloned() { id } else { - let explicit_bundle_id = self.register_info::(components, storages); + let explicit_bundle_id = self.register_static_info::(components, storages); // SAFETY: reading from `explicit_bundle_id` and creating new bundle in same time. Its valid because bundle hashmap allow this let id = unsafe { let (ptr, len) = { @@ -1982,7 +2425,12 @@ impl Bundles { }; // SAFETY: this is sound because the contributed_components Vec for explicit_bundle_id will not be accessed mutably as // part of init_dynamic_info. No mutable references will be created and the allocation will remain valid. - self.init_dynamic_info(storages, components, core::slice::from_raw_parts(ptr, len)) + self.init_dynamic_info( + "", + storages, + components, + core::slice::from_raw_parts(ptr, len), + ) }; self.contributed_bundle_ids.insert(TypeId::of::(), id); id @@ -2020,6 +2468,7 @@ impl Bundles { /// provided [`Components`]. pub(crate) fn init_dynamic_info( &mut self, + name: &str, storages: &mut Storages, components: &Components, component_ids: &[ComponentId], @@ -2033,6 +2482,7 @@ impl Bundles { .from_key(component_ids) .or_insert_with(|| { let (id, storages) = initialize_dynamic_bundle( + name, bundle_infos, storages, components, @@ -2065,6 +2515,7 @@ impl Bundles { .entry(component_id) .or_insert_with(|| { let (id, storage_type) = initialize_dynamic_bundle( + "", bundle_infos, storages, components, @@ -2080,6 +2531,7 @@ impl Bundles { /// Asserts that all components are part of [`Components`] /// and initializes a [`BundleInfo`]. fn initialize_dynamic_bundle( + bundle_name: &str, bundle_infos: &mut Vec, storages: &mut Storages, components: &Components, @@ -2097,7 +2549,7 @@ fn initialize_dynamic_bundle( let id = BundleId(bundle_infos.len()); let bundle_info = // SAFETY: `component_ids` are valid as they were just checked - unsafe { BundleInfo::new("", storages, components, component_ids, id) }; + unsafe { BundleInfo::new(bundle_name, storages, components, component_ids, id) }; bundle_infos.push(bundle_info); (id, storage_types) @@ -2118,12 +2570,137 @@ fn sorted_remove(source: &mut Vec, remove: &[T]) { }); } +// SAFETY: +// - `is_static` is false because `Option` could either include all the components in `T` +// or none of them +// - `is_bounded` is the same as the underlying `T`, since `Option` adds the possibility +// of having none of the components in the bounding set, but doesn't change the bound. +// - `cache_key` returns either the key of the underlying bundle with a 0 appended to the right +// or a 1, and hence are different for every combination. +// - `component_ids` either calls `ids` for all components in the underlying bundle or none at all; +// if it calls it it means `self` is `Some` and hence `get_components` will also call `func` for +// all of them in the same order. +unsafe impl Bundle for Option { + fn is_static() -> bool { + false + } + fn is_bounded() -> bool { + T::is_bounded() + } + + fn cache_key(&self) -> BoundedBundleKey { + if let Some(this) = self { + this.cache_key() + .merge(const { BoundedBundleKey::literal(1, 1) }) + } else { + const { BoundedBundleKey::literal(0, 1) } + } + } + + fn component_ids( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId), + ) { + if let Some(this) = self { + this.component_ids(components, ids); + } + } + + fn register_required_components( + &self, + components: &mut ComponentsRegistrator, + required_components: &mut RequiredComponents, + ) { + if let Some(this) = self { + this.register_required_components(components, required_components); + } + } +} + +impl ComponentsFromBundle for Option { + type Effect = Option; + + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { + self.map(|this| this.get_components(func)) + } +} + +impl BundleEffect for Option { + fn apply(self, entity: &mut EntityWorldMut) { + if let Some(this) = self { + this.apply(entity); + } + } +} + +impl NoBundleEffect for Option {} + +// SAFETY: +// - `is_static` and `is_bounded` ares false because a `Vec>` could contain +// any bundle at runtime; +// - `cache_key` is irrelevant because this bundle is not bounded; +// - `component_ids` calls `component_ids` for all bundles in self, and `get_components` calls +// `get_components` for the bundle in the same order. Each bundle individually guarantees it +// will call `component_ids` and `get_components` in a matching way. +unsafe impl Bundle for Vec> { + fn is_static() -> bool { + false + } + + fn is_bounded() -> bool { + false + } + + fn cache_key(&self) -> BoundedBundleKey { + BoundedBundleKey::empty() + } + + fn component_ids( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId), + ) { + for bundle in self { + bundle.component_ids(components, ids); + } + } + + fn register_required_components( + &self, + components: &mut ComponentsRegistrator, + required_components: &mut RequiredComponents, + ) { + for bundle in self { + bundle.register_required_components(components, required_components); + } + } +} + +impl ComponentsFromBundle for Vec> { + type Effect = Vec>; + + fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { + self.into_iter() + .map(|bundle| bundle.get_components(func)) + .collect() + } +} + +impl BundleEffect for Vec { + fn apply(self, entity: &mut EntityWorldMut) { + for effect in self { + effect.apply(entity); + } + } +} + #[cfg(test)] mod tests { use crate::{ archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld, }; - use alloc::vec; + use alloc::{boxed::Box, vec}; #[derive(Component)] struct A; @@ -2398,4 +2975,111 @@ mod tests { assert_eq!(world.resource::().0, 3); } + + #[derive(Bundle)] + #[bundle(dynamic)] + struct WithOptionalField { + a: A, + b: Option, + v: Option, + } + + #[test] + fn optional_bundle() { + let mut w = World::new(); + + let e = w.spawn(None::); + assert!(!e.contains::()); + + let e = w.spawn(Some(A)); + assert!(e.contains::()); + + let e = w.spawn((None::, Some(V("Some")))); + assert!(!e.contains::()); + assert!(e.contains::()); + assert!(e.get::() == Some(&V("Some"))); + + let e = w.spawn((Some(B), Some(V("Some2")))); + assert!(e.contains::()); + assert!(e.contains::()); + assert!(e.get::() == Some(&V("Some2"))); + + let e = w.spawn((Some(B), None::)); + assert!(e.contains::()); + assert!(!e.contains::()); + + let e = w.spawn(WithOptionalField { + a: A, + b: None, + v: Some(V("V")), + }); + assert!(e.contains::()); + assert!(!e.contains::()); + assert!(e.get::() == Some(&V("V"))); + } + + #[derive(Bundle)] + #[bundle(dynamic)] + struct WithBoxDynBundleField { + a: A, + b: Box, + c: Box, + } + + #[test] + fn dyn_bundle() { + let mut w = World::new(); + + let e = w.spawn(Box::new(A) as Box); + assert!(e.contains::()); + + let e = w.spawn(Box::new((A, B)) as Box); + assert!(e.contains::()); + assert!(e.contains::()); + + let e = w.spawn(( + Box::new(A) as Box, + Box::new(B) as Box, + )); + assert!(e.contains::()); + assert!(e.contains::()); + + let e = w.spawn(WithBoxDynBundleField { + a: A, + b: Box::new(B), + c: Box::new((C, D)), + }); + assert!(e.contains::()); + assert!(e.contains::()); + assert!(e.contains::()); + assert!(e.contains::()); + } + + #[test] + fn vec_dyn_bundle() { + let mut w = World::new(); + + let e = w.spawn(vec![Box::new(A) as Box]); + assert!(e.contains::()); + + let e = w.spawn(vec![Box::new(A) as Box, Box::new(B)]); + assert!(e.contains::()); + assert!(e.contains::()); + } + + #[test] + fn mixed_dynamic_bundles() { + let mut w = World::new(); + + let e = w.spawn(Some(vec![ + Box::new(A) as Box, + Box::new(None::), + Box::new(vec![Box::new(C) as Box, Box::new(Some(D))]), + ])); + + assert!(e.contains::()); + assert!(!e.contains::()); + assert!(e.contains::()); + assert!(e.contains::()); + } } diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index b124055d16ac6..9e12a32306996 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -6,7 +6,7 @@ use core::any::TypeId; use crate::{ archetype::Archetype, - bundle::Bundle, + bundle::StaticBundle, component::{Component, ComponentCloneBehavior, ComponentCloneFn, ComponentId, ComponentInfo}, entity::{hash_map::EntityHashMap, Entities, Entity, EntityMapper}, query::DebugCheckedUnwrap, @@ -679,8 +679,8 @@ impl<'w> EntityClonerBuilder<'w> { /// /// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call /// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods. - pub fn allow(&mut self) -> &mut Self { - let bundle = self.world.register_bundle::(); + pub fn allow(&mut self) -> &mut Self { + let bundle = self.world.register_static_bundle::(); let ids = bundle.explicit_components().to_owned(); for id in ids { self.filter_allow(id); @@ -720,8 +720,8 @@ impl<'w> EntityClonerBuilder<'w> { } /// Disallows all components of the bundle from being cloned. - pub fn deny(&mut self) -> &mut Self { - let bundle = self.world.register_bundle::(); + pub fn deny(&mut self) -> &mut Self { + let bundle = self.world.register_static_bundle::(); let ids = bundle.explicit_components().to_owned(); for id in ids { self.filter_deny(id); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index e5f0e908e56c5..559a11ccf76d9 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -145,7 +145,7 @@ pub struct HotPatched; #[cfg(test)] mod tests { use crate::{ - bundle::Bundle, + bundle::{Bundle, StaticBundle}, change_detection::Ref, component::{Component, ComponentId, RequiredComponents, RequiredComponentsError}, entity::{Entity, EntityMapper}, @@ -242,9 +242,12 @@ mod tests { y: SparseStored, } let mut ids = Vec::new(); - ::component_ids(&mut world.components_registrator(), &mut |id| { - ids.push(id); - }); + ::component_ids( + &mut world.components_registrator(), + &mut |id| { + ids.push(id); + }, + ); assert_eq!( ids, @@ -292,9 +295,12 @@ mod tests { } let mut ids = Vec::new(); - ::component_ids(&mut world.components_registrator(), &mut |id| { - ids.push(id); - }); + ::component_ids( + &mut world.components_registrator(), + &mut |id| { + ids.push(id); + }, + ); assert_eq!( ids, @@ -344,7 +350,7 @@ mod tests { } let mut ids = Vec::new(); - ::component_ids( + ::component_ids( &mut world.components_registrator(), &mut |id| { ids.push(id); @@ -2733,6 +2739,13 @@ mod tests { field1: ComponentB, } + #[expect( + dead_code, + reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed." + )] + #[derive(Bundle)] + struct IgnoredFields(#[bundle(ignore)] i32, #[bundle(ignore)] i32); + #[derive(Component)] struct MyEntities { #[entities] diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index d34b686b95664..da50ee9967586 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -138,6 +138,7 @@ use variadics_please::all_tuples; use crate::{ archetype::ArchetypeFlags, + bundle::StaticBundle, change_detection::MaybeLocation, component::ComponentId, entity::EntityHashMap, @@ -169,7 +170,7 @@ use smallvec::SmallVec; /// Providing multiple components in this bundle will cause this event to be triggered by any /// matching component in the bundle, /// [rather than requiring all of them to be present](https://github.com/bevyengine/bevy/issues/15325). -pub struct On<'w, E, B: Bundle = ()> { +pub struct On<'w, E, B: StaticBundle = ()> { event: &'w mut E, propagate: &'w mut bool, trigger: ObserverTrigger, @@ -180,7 +181,7 @@ pub struct On<'w, E, B: Bundle = ()> { #[deprecated(since = "0.17.0", note = "Renamed to `On`.")] pub type Trigger<'w, E, B = ()> = On<'w, E, B>; -impl<'w, E, B: Bundle> On<'w, E, B> { +impl<'w, E, B: StaticBundle> On<'w, E, B> { /// Creates a new instance of [`On`] for the given event and observer information. pub fn new(event: &'w mut E, propagate: &'w mut bool, trigger: ObserverTrigger) -> Self { Self { @@ -250,7 +251,7 @@ impl<'w, E, B: Bundle> On<'w, E, B> { } } -impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> { +impl<'w, E: EntityEvent, B: StaticBundle> On<'w, E, B> { /// Returns the [`Entity`] that was targeted by the `event` that triggered this observer. /// /// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`]. @@ -282,7 +283,7 @@ impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> { } } -impl<'w, E: Debug, B: Bundle> Debug for On<'w, E, B> { +impl<'w, E: Debug, B: StaticBundle> Debug for On<'w, E, B> { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("On") .field("event", &self.event) @@ -293,7 +294,7 @@ impl<'w, E: Debug, B: Bundle> Debug for On<'w, E, B> { } } -impl<'w, E, B: Bundle> Deref for On<'w, E, B> { +impl<'w, E, B: StaticBundle> Deref for On<'w, E, B> { type Target = E; fn deref(&self) -> &Self::Target { @@ -301,7 +302,7 @@ impl<'w, E, B: Bundle> Deref for On<'w, E, B> { } } -impl<'w, E, B: Bundle> DerefMut for On<'w, E, B> { +impl<'w, E, B: StaticBundle> DerefMut for On<'w, E, B> { fn deref_mut(&mut self) -> &mut Self::Target { self.event } @@ -704,7 +705,7 @@ impl World { /// # Panics /// /// Panics if the given system is an exclusive system. - pub fn add_observer( + pub fn add_observer( &mut self, system: impl IntoObserverSystem, ) -> EntityWorldMut { diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 4fd9f23556363..277dbbc2b48b0 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -2,6 +2,7 @@ use alloc::{boxed::Box, vec}; use core::any::Any; use crate::{ + bundle::StaticBundle, component::{ComponentId, Mutable, StorageType}, error::{ErrorContext, ErrorHandler}, lifecycle::{ComponentHook, HookContext}, @@ -208,7 +209,7 @@ impl Observer { /// # Panics /// /// Panics if the given system is an exclusive system. - pub fn new>(system: I) -> Self { + pub fn new>(system: I) -> Self { let system = Box::new(IntoObserverSystem::into_system(system)); assert!( !system.is_exclusive(), @@ -330,7 +331,7 @@ impl Component for Observer { } } -fn observer_system_runner>( +fn observer_system_runner>( mut world: DeferredWorld, observer_trigger: ObserverTrigger, ptr: PtrMut, @@ -421,7 +422,7 @@ fn observer_system_runner>( /// The type parameters of this function _must_ match those used to create the [`Observer`]. /// As such, it is recommended to only use this function within the [`Observer::new`] method to /// ensure type parameters match. -fn hook_on_add>( +fn hook_on_add>( mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext, ) { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 55fa42c41eaac..4a1b2977ba1ac 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,6 +1,6 @@ use crate::{ archetype::{Archetype, Archetypes}, - bundle::Bundle, + bundle::StaticBundle, change_detection::{MaybeLocation, Ticks, TicksMut}, component::{Component, ComponentId, Components, Mutable, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, @@ -1082,7 +1082,7 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> { /// are rejected. unsafe impl<'a, B> WorldQuery for EntityRefExcept<'a, B> where - B: Bundle, + B: StaticBundle, { type Fetch<'w> = EntityFetch<'w>; type State = SmallVec<[ComponentId; 4]>; @@ -1157,7 +1157,7 @@ where /// SAFETY: `Self` is the same as `Self::ReadOnly`. unsafe impl<'a, B> QueryData for EntityRefExcept<'a, B> where - B: Bundle, + B: StaticBundle, { const IS_READ_ONLY: bool = true; type ReadOnly = Self; @@ -1182,14 +1182,14 @@ where /// SAFETY: `EntityRefExcept` enforces read-only access to its contained /// components. -unsafe impl<'a, B> ReadOnlyQueryData for EntityRefExcept<'a, B> where B: Bundle {} +unsafe impl<'a, B> ReadOnlyQueryData for EntityRefExcept<'a, B> where B: StaticBundle {} /// SAFETY: `EntityMutExcept` guards access to all components in the bundle `B` /// and populates `Access` values so that queries that conflict with this access /// are rejected. unsafe impl<'a, B> WorldQuery for EntityMutExcept<'a, B> where - B: Bundle, + B: StaticBundle, { type Fetch<'w> = EntityFetch<'w>; type State = SmallVec<[ComponentId; 4]>; @@ -1265,7 +1265,7 @@ where /// `EntityMutExcept` provides. unsafe impl<'a, B> QueryData for EntityMutExcept<'a, B> where - B: Bundle, + B: StaticBundle, { const IS_READ_ONLY: bool = false; type ReadOnly = EntityRefExcept<'a, B>; diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index baf0d7269715d..acbd96dfb795a 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -1,7 +1,7 @@ use super::{QueryData, QueryFilter, ReadOnlyQueryData}; use crate::{ archetype::{Archetype, ArchetypeEntity, Archetypes}, - bundle::Bundle, + bundle::StaticBundle, component::Tick, entity::{ContainsEntity, Entities, Entity, EntityEquivalent, EntitySet, EntitySetIterator}, query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, StorageId}, @@ -939,13 +939,13 @@ unsafe impl<'w, 's, F: QueryFilter> EntitySetIterator } // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator +unsafe impl<'w, 's, F: QueryFilter, B: StaticBundle> EntitySetIterator for QueryIter<'w, 's, EntityRefExcept<'_, B>, F> { } // SAFETY: [`QueryIter`] is guaranteed to return every matching entity once and only once. -unsafe impl<'w, 's, F: QueryFilter, B: Bundle> EntitySetIterator +unsafe impl<'w, 's, F: QueryFilter, B: StaticBundle> EntitySetIterator for QueryIter<'w, 's, EntityMutExcept<'_, B>, F> { } diff --git a/crates/bevy_ecs/src/reflect/bundle.rs b/crates/bevy_ecs/src/reflect/bundle.rs index ee02aff86e7fe..bd1e80e456a01 100644 --- a/crates/bevy_ecs/src/reflect/bundle.rs +++ b/crates/bevy_ecs/src/reflect/bundle.rs @@ -8,7 +8,7 @@ use alloc::boxed::Box; use core::any::{Any, TypeId}; use crate::{ - bundle::BundleFromComponents, + bundle::{BundleFromComponents, StaticBundle}, entity::EntityMapper, prelude::Bundle, relationship::RelationshipHookMode, @@ -32,6 +32,8 @@ pub struct ReflectBundle(ReflectBundleFns); /// The also [`super::component::ReflectComponentFns`]. #[derive(Clone)] pub struct ReflectBundleFns { + /// Function pointer implementing [`ReflectBundle::get_boxed`]. + pub get_boxed: fn(Box) -> Result, Box>, /// Function pointer implementing [`ReflectBundle::insert`]. pub insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry), /// Function pointer implementing [`ReflectBundle::apply`]. @@ -56,12 +58,22 @@ impl ReflectBundleFns { /// /// This is useful if you want to start with the default implementation before overriding some /// of the functions to create a custom implementation. - pub fn new() -> Self { + pub fn new() -> Self { >::from_type().0 } } impl ReflectBundle { + /// Downcast a `Box` type to `Box`. + /// + /// If the type cannot be downcast, this will return `Err(Box)`. + pub fn get_boxed( + &self, + reflect_value: Box, + ) -> Result, Box> { + (self.0.get_boxed)(reflect_value) + } + /// Insert a reflected [`Bundle`] into the entity like [`insert()`](EntityWorldMut::insert). pub fn insert( &self, @@ -147,9 +159,14 @@ impl ReflectBundle { } } -impl FromType for ReflectBundle { +impl FromType + for ReflectBundle +{ fn from_type() -> Self { ReflectBundle(ReflectBundleFns { + get_boxed: |reflect_value| { + ::downcast::(reflect_value).map(|value| value as Box) + }, insert: |entity, reflected_bundle, registry| { let bundle = entity.world_scope(|world| { from_reflect_with_fallback::(reflected_bundle, world, registry) diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 1983b6b37c11b..7b6619c319fd1 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -1,5 +1,5 @@ use crate::{ - bundle::Bundle, + bundle::{Bundle, StaticBundle}, entity::{hash_set::EntityHashSet, Entity}, prelude::Children, relationship::{ @@ -339,7 +339,7 @@ impl<'w> EntityWorldMut<'w> { /// /// This method should only be called on relationships that form a tree-like structure. /// Any cycles will cause this method to loop infinitely. - pub fn remove_recursive(&mut self) -> &mut Self { + pub fn remove_recursive(&mut self) -> &mut Self { self.remove::(); if let Some(relationship_target) = self.get::() { let related_vec: Vec = relationship_target.iter().collect(); @@ -500,7 +500,7 @@ impl<'a> EntityCommands<'a> { /// /// This method should only be called on relationships that form a tree-like structure. /// Any cycles will cause this method to loop infinitely. - pub fn remove_recursive(&mut self) -> &mut Self { + pub fn remove_recursive(&mut self) -> &mut Self { self.queue(move |mut entity: EntityWorldMut| { entity.remove_recursive::(); }) diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index d5014f22409fd..e986e300c3d91 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -2,7 +2,9 @@ //! for the best entry points into these APIs and examples of how to use them. use crate::{ - bundle::{Bundle, BundleEffect, DynamicBundle, NoBundleEffect}, + bundle::{ + BoundedBundleKey, Bundle, BundleEffect, ComponentsFromBundle, NoBundleEffect, StaticBundle, + }, entity::Entity, relationship::{RelatedSpawner, Relationship, RelationshipTarget}, world::{EntityWorldMut, World}, @@ -46,7 +48,9 @@ pub trait SpawnableList { fn size_hint(&self) -> usize; } -impl> SpawnableList for Vec { +impl + StaticBundle> SpawnableList + for Vec +{ fn spawn(self, world: &mut World, entity: Entity) { let mapped_bundles = self.into_iter().map(|b| (R::from(entity), b)); world.spawn_batch(mapped_bundles); @@ -182,35 +186,69 @@ impl> BundleEffect for SpawnRelatedBundle + Send + Sync + 'static> Bundle +// SAFETY: This internally relies on the RelationshipTarget's StaticBundle implementation, which is sound. +unsafe impl + Send + Sync + 'static> StaticBundle for SpawnRelatedBundle { fn component_ids( components: &mut crate::component::ComponentsRegistrator, ids: &mut impl FnMut(crate::component::ComponentId), ) { - ::component_ids(components, ids); + ::component_ids(components, ids); } fn get_component_ids( components: &crate::component::Components, ids: &mut impl FnMut(Option), ) { - ::get_component_ids(components, ids); + ::get_component_ids(components, ids); } fn register_required_components( components: &mut crate::component::ComponentsRegistrator, required_components: &mut crate::component::RequiredComponents, ) { - ::register_required_components( + ::register_required_components( components, required_components, ); } } -impl> DynamicBundle for SpawnRelatedBundle { + +// SAFETY: This internally relies on the RelationshipTarget's Bundle implementation, which is sound. +unsafe impl + Send + Sync + 'static> Bundle + for SpawnRelatedBundle +{ + // We are inserting `R::RelationshipTarget`, which is a `Component` and thus + // always a `StaticBundle`. + fn is_static() -> bool { + ::is_static() + } + fn is_bounded() -> bool { + ::is_bounded() + } + + fn cache_key(&self) -> BoundedBundleKey { + BoundedBundleKey::empty() + } + + fn component_ids( + &self, + components: &mut crate::component::ComponentsRegistrator, + ids: &mut impl FnMut(crate::component::ComponentId), + ) { + ::component_ids(components, ids); + } + + fn register_required_components( + &self, + components: &mut crate::component::ComponentsRegistrator, + required_components: &mut crate::component::RequiredComponents, + ) { + ::register_required_components(components, required_components); + } +} +impl> ComponentsFromBundle for SpawnRelatedBundle { type Effect = Self; fn get_components( @@ -239,7 +277,7 @@ impl BundleEffect for SpawnOneRelated { } } -impl DynamicBundle for SpawnOneRelated { +impl ComponentsFromBundle for SpawnOneRelated { type Effect = Self; fn get_components( @@ -251,34 +289,66 @@ impl DynamicBundle for SpawnOneRelated { } } -// SAFETY: This internally relies on the RelationshipTarget's Bundle implementation, which is sound. -unsafe impl Bundle for SpawnOneRelated { +// SAFETY: This internally relies on the RelationshipTarget's StaticBundle implementation, which is sound. +unsafe impl StaticBundle for SpawnOneRelated { fn component_ids( components: &mut crate::component::ComponentsRegistrator, ids: &mut impl FnMut(crate::component::ComponentId), ) { - ::component_ids(components, ids); + ::component_ids(components, ids); } fn get_component_ids( components: &crate::component::Components, ids: &mut impl FnMut(Option), ) { - ::get_component_ids(components, ids); + ::get_component_ids(components, ids); } fn register_required_components( components: &mut crate::component::ComponentsRegistrator, required_components: &mut crate::component::RequiredComponents, ) { - ::register_required_components( + ::register_required_components( components, required_components, ); } } -/// [`RelationshipTarget`] methods that create a [`Bundle`] with a [`DynamicBundle::Effect`] that: +// SAFETY: This internally relies on the RelationshipTarget's Bundle implementation, which is sound. +unsafe impl Bundle for SpawnOneRelated { + // We are inserting `R::RelationshipTarget`, which is a `Component` and thus + // always a `StaticBundle`. + fn is_static() -> bool { + ::is_static() + } + fn is_bounded() -> bool { + ::is_bounded() + } + + fn cache_key(&self) -> BoundedBundleKey { + BoundedBundleKey::empty() + } + + fn component_ids( + &self, + components: &mut crate::component::ComponentsRegistrator, + ids: &mut impl FnMut(crate::component::ComponentId), + ) { + ::component_ids(components, ids); + } + + fn register_required_components( + &self, + components: &mut crate::component::ComponentsRegistrator, + required_components: &mut crate::component::RequiredComponents, + ) { + ::register_required_components(components, required_components); + } +} + +/// [`RelationshipTarget`] methods that create a [`Bundle`] with a [`ComponentsFromBundle::Effect`] that: /// /// 1. Contains the [`RelationshipTarget`] component, pre-allocated with the necessary space for spawned entities. /// 2. Spawns an entity (or a list of entities) that relate to the entity the [`Bundle`] is added to via the [`RelationshipTarget::Relationship`]. diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 84a2fdf4e9979..98dcea01f4b0e 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -5,7 +5,7 @@ //! [`Commands`](crate::system::Commands). use crate::{ - bundle::{Bundle, InsertMode, NoBundleEffect}, + bundle::{Bundle, InsertMode, NoBundleEffect, StaticBundle}, change_detection::MaybeLocation, entity::Entity, error::Result, @@ -70,7 +70,7 @@ where pub fn spawn_batch(bundles_iter: I) -> impl Command where I: IntoIterator + Send + Sync + 'static, - I::Item: Bundle, + I::Item: Bundle + StaticBundle, { let caller = MaybeLocation::caller(); move |world: &mut World| { @@ -88,7 +88,7 @@ where pub fn insert_batch(batch: I, insert_mode: InsertMode) -> impl Command where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle + StaticBundle, { let caller = MaybeLocation::caller(); move |world: &mut World| -> Result { diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index 87bd2d858b27c..f9eb516f0c32a 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; use log::info; use crate::{ - bundle::{Bundle, InsertMode}, + bundle::{Bundle, InsertMode, StaticBundle}, change_detection::MaybeLocation, component::{Component, ComponentId, ComponentInfo}, entity::{Entity, EntityClonerBuilder}, @@ -154,7 +154,7 @@ pub fn insert_from_world(mode: InsertMode) -> impl Ent /// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity. #[track_caller] -pub fn remove() -> impl EntityCommand { +pub fn remove() -> impl EntityCommand { let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { entity.remove_with_caller::(caller); @@ -164,7 +164,7 @@ pub fn remove() -> impl EntityCommand { /// An [`EntityCommand`] that removes the components in a [`Bundle`] from an entity, /// as well as the required components for each component removed. #[track_caller] -pub fn remove_with_requires() -> impl EntityCommand { +pub fn remove_with_requires() -> impl EntityCommand { let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { entity.remove_with_requires_with_caller::(caller); @@ -190,9 +190,9 @@ pub fn clear() -> impl EntityCommand { } /// An [`EntityCommand`] that removes all components from an entity, -/// except for those in the given [`Bundle`]. +/// except for those in the given [`StaticBundle`]. #[track_caller] -pub fn retain() -> impl EntityCommand { +pub fn retain() -> impl EntityCommand { let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { entity.retain_with_caller::(caller); @@ -218,7 +218,7 @@ pub fn despawn() -> impl EntityCommand { /// An [`EntityCommand`] that creates an [`Observer`](crate::observer::Observer) /// listening for events of type `E` targeting an entity #[track_caller] -pub fn observe( +pub fn observe( observer: impl IntoObserverSystem, ) -> impl EntityCommand { let caller = MaybeLocation::caller(); @@ -254,7 +254,7 @@ pub fn clone_with( /// An [`EntityCommand`] that clones the specified components of an entity /// and inserts them into another entity. -pub fn clone_components(target: Entity) -> impl EntityCommand { +pub fn clone_components(target: Entity) -> impl EntityCommand { move |mut entity: EntityWorldMut| { entity.clone_components::(target); } @@ -262,7 +262,7 @@ pub fn clone_components(target: Entity) -> impl EntityCommand { /// An [`EntityCommand`] that clones the specified components of an entity /// and inserts them into another entity, then removes them from the original entity. -pub fn move_components(target: Entity) -> impl EntityCommand { +pub fn move_components(target: Entity) -> impl EntityCommand { move |mut entity: EntityWorldMut| { entity.move_components::(target); } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index d36588d377ed7..792d2339bbc82 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -15,7 +15,7 @@ use core::marker::PhantomData; use crate::{ self as bevy_ecs, - bundle::{Bundle, InsertMode, NoBundleEffect}, + bundle::{Bundle, InsertMode, NoBundleEffect, StaticBundle}, change_detection::{MaybeLocation, Mut}, component::{Component, ComponentId, Mutable}, entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError}, @@ -533,7 +533,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn spawn_batch(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - I::Item: Bundle, + I::Item: Bundle + StaticBundle, { self.queue(command::spawn_batch(batch)); } @@ -681,7 +681,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn insert_batch(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle + StaticBundle, { self.queue(command::insert_batch(batch, InsertMode::Replace)); } @@ -712,7 +712,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn insert_batch_if_new(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle + StaticBundle, { self.queue(command::insert_batch(batch, InsertMode::Keep)); } @@ -742,7 +742,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn try_insert_batch(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle + StaticBundle, { self.queue(command::insert_batch(batch, InsertMode::Replace).handle_error_with(warn)); } @@ -773,7 +773,7 @@ impl<'w, 's> Commands<'w, 's> { pub fn try_insert_batch_if_new(&mut self, batch: I) where I: IntoIterator + Send + Sync + 'static, - B: Bundle, + B: Bundle + StaticBundle, { self.queue(command::insert_batch(batch, InsertMode::Keep).handle_error_with(warn)); } @@ -1112,7 +1112,7 @@ impl<'w, 's> Commands<'w, 's> { /// Panics if the given system is an exclusive system. /// /// [`On`]: crate::observer::On - pub fn add_observer( + pub fn add_observer( &mut self, observer: impl IntoObserverSystem, ) -> EntityCommands { @@ -1620,7 +1620,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` #[track_caller] - pub fn remove(&mut self) -> &mut Self { + pub fn remove(&mut self) -> &mut Self { self.queue_handled(entity_command::remove::(), warn) } @@ -1656,7 +1656,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` #[track_caller] - pub fn remove_if(&mut self, condition: impl FnOnce() -> bool) -> &mut Self { + pub fn remove_if(&mut self, condition: impl FnOnce() -> bool) -> &mut Self { if condition() { self.remove::() } else { @@ -1673,7 +1673,10 @@ impl<'a> EntityCommands<'a> { /// If the entity does not exist when this command is executed, /// the resulting error will be ignored. #[track_caller] - pub fn try_remove_if(&mut self, condition: impl FnOnce() -> bool) -> &mut Self { + pub fn try_remove_if( + &mut self, + condition: impl FnOnce() -> bool, + ) -> &mut Self { if condition() { self.try_remove::() } else { @@ -1721,7 +1724,7 @@ impl<'a> EntityCommands<'a> { /// } /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` - pub fn try_remove(&mut self) -> &mut Self { + pub fn try_remove(&mut self) -> &mut Self { self.queue_handled(entity_command::remove::(), ignore) } @@ -1753,7 +1756,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(remove_with_requires_system); /// ``` #[track_caller] - pub fn remove_with_requires(&mut self) -> &mut Self { + pub fn remove_with_requires(&mut self) -> &mut Self { self.queue(entity_command::remove_with_requires::()) } @@ -1938,7 +1941,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` #[track_caller] - pub fn retain(&mut self) -> &mut Self { + pub fn retain(&mut self) -> &mut Self { self.queue(entity_command::retain::()) } @@ -1966,7 +1969,7 @@ impl<'a> EntityCommands<'a> { } /// Creates an [`Observer`] listening for events of type `E` targeting this entity. - pub fn observe( + pub fn observe( &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { @@ -2107,7 +2110,7 @@ impl<'a> EntityCommands<'a> { /// # Panics /// /// The command will panic when applied if the target entity does not exist. - pub fn clone_components(&mut self, target: Entity) -> &mut Self { + pub fn clone_components(&mut self, target: Entity) -> &mut Self { self.queue(entity_command::clone_components::(target)) } @@ -2120,7 +2123,7 @@ impl<'a> EntityCommands<'a> { /// # Panics /// /// The command will panic when applied if the target entity does not exist. - pub fn move_components(&mut self, target: Entity) -> &mut Self { + pub fn move_components(&mut self, target: Entity) -> &mut Self { self.queue(entity_command::move_components::(target)) } } diff --git a/crates/bevy_ecs/src/system/input.rs b/crates/bevy_ecs/src/system/input.rs index cb75016ee93b4..336b1ac6bc213 100644 --- a/crates/bevy_ecs/src/system/input.rs +++ b/crates/bevy_ecs/src/system/input.rs @@ -2,7 +2,7 @@ use core::ops::{Deref, DerefMut}; use variadics_please::all_tuples; -use crate::{bundle::Bundle, prelude::On, system::System}; +use crate::{bundle::StaticBundle, prelude::On, system::System}; /// Trait for types that can be used as input to [`System`]s. /// @@ -222,7 +222,7 @@ impl<'i, T: ?Sized> DerefMut for InMut<'i, T> { /// Used for [`ObserverSystem`]s. /// /// [`ObserverSystem`]: crate::system::ObserverSystem -impl SystemInput for On<'_, E, B> { +impl SystemInput for On<'_, E, B> { type Param<'i> = On<'i, E, B>; type Inner<'i> = On<'i, E, B>; diff --git a/crates/bevy_ecs/src/system/observer_system.rs b/crates/bevy_ecs/src/system/observer_system.rs index e99a86b64c30e..4ceffd4305552 100644 --- a/crates/bevy_ecs/src/system/observer_system.rs +++ b/crates/bevy_ecs/src/system/observer_system.rs @@ -2,10 +2,11 @@ use alloc::{borrow::Cow, vec::Vec}; use core::marker::PhantomData; use crate::{ + bundle::StaticBundle, component::{CheckChangeTicks, ComponentId, Tick}, error::Result, never::Never, - prelude::{Bundle, On}, + prelude::On, query::FilteredAccessSet, schedule::{Fallible, Infallible}, system::{input::SystemIn, System}, @@ -15,12 +16,12 @@ use crate::{ use super::{IntoSystem, SystemParamValidationError}; /// Implemented for [`System`]s that have [`On`] as the first argument. -pub trait ObserverSystem: +pub trait ObserverSystem: System, Out = Out> + Send + 'static { } -impl ObserverSystem for T where +impl ObserverSystem for T where T: System, Out = Out> + Send + 'static { } @@ -37,7 +38,7 @@ impl ObserverSystem for T where label = "the trait `IntoObserverSystem` is not implemented", note = "for function `ObserverSystem`s, ensure the first argument is `On` and any subsequent ones are `SystemParam`" )] -pub trait IntoObserverSystem: Send + 'static { +pub trait IntoObserverSystem: Send + 'static { /// The type of [`System`] that this instance converts into. type System: ObserverSystem; @@ -50,7 +51,7 @@ where S: IntoSystem, Out, M> + Send + 'static, S::System: ObserverSystem, E: 'static, - B: Bundle, + B: StaticBundle, { type System = S::System; @@ -64,7 +65,7 @@ where S: IntoSystem, (), M> + Send + 'static, S::System: ObserverSystem, E: Send + Sync + 'static, - B: Bundle, + B: StaticBundle, { type System = InfallibleObserverWrapper; @@ -76,7 +77,7 @@ impl IntoObserverSystem for S where S: IntoSystem, Never, M> + Send + 'static, E: Send + Sync + 'static, - B: Bundle, + B: StaticBundle, { type System = InfallibleObserverWrapper; @@ -105,7 +106,7 @@ impl System for InfallibleObserverWrapper where S: ObserverSystem, E: Send + Sync + 'static, - B: Bundle, + B: StaticBundle, Out: Send + Sync + 'static, { type In = On<'static, E, B>; diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index ab470efcc740c..7eea259d41108 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1,8 +1,8 @@ use crate::{ archetype::Archetype, bundle::{ - Bundle, BundleEffect, BundleFromComponents, BundleInserter, BundleRemover, DynamicBundle, - InsertMode, + Bundle, BundleEffect, BundleFromComponents, BundleInserter, BundleRemover, + ComponentsFromBundle, InsertMode, StaticBundle, }, change_detection::{MaybeLocation, MutUntyped}, component::{ @@ -1843,7 +1843,7 @@ impl<'w> EntityWorldMut<'w> { let location = self.location(); let change_tick = self.world.change_tick(); let mut bundle_inserter = - BundleInserter::new::(self.world, location.archetype_id, change_tick); + BundleInserter::new::(&bundle, self.world, location.archetype_id, change_tick); // SAFETY: location matches current entity. `T` matches `bundle_info` let (location, after_effect) = unsafe { bundle_inserter.insert( @@ -1966,6 +1966,7 @@ impl<'w> EntityWorldMut<'w> { let location = self.location(); let change_tick = self.world.change_tick(); let bundle_id = self.world.bundles.init_dynamic_info( + "", &mut self.world.storages, &self.world.components, component_ids, @@ -2001,7 +2002,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[must_use] #[track_caller] - pub fn take(&mut self) -> Option { + pub fn take(&mut self) -> Option { let location = self.location(); let entity = self.entity; @@ -2057,12 +2058,15 @@ impl<'w> EntityWorldMut<'w> { /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] - pub fn remove(&mut self) -> &mut Self { + pub fn remove(&mut self) -> &mut Self { self.remove_with_caller::(MaybeLocation::caller()) } #[inline] - pub(crate) fn remove_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { + pub(crate) fn remove_with_caller( + &mut self, + caller: MaybeLocation, + ) -> &mut Self { let location = self.location(); let Some(mut remover) = @@ -2094,11 +2098,11 @@ impl<'w> EntityWorldMut<'w> { /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] - pub fn remove_with_requires(&mut self) -> &mut Self { + pub fn remove_with_requires(&mut self) -> &mut Self { self.remove_with_requires_with_caller::(MaybeLocation::caller()) } - pub(crate) fn remove_with_requires_with_caller( + pub(crate) fn remove_with_requires_with_caller( &mut self, caller: MaybeLocation, ) -> &mut Self { @@ -2109,7 +2113,8 @@ impl<'w> EntityWorldMut<'w> { let mut registrator = unsafe { ComponentsRegistrator::new(&mut self.world.components, &mut self.world.component_ids) }; - let bundle_id = bundles.register_contributed_bundle_info::(&mut registrator, storages); + let bundle_id = + bundles.register_static_contributed_bundle_info::(&mut registrator, storages); // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. let Some(mut remover) = (unsafe { @@ -2142,12 +2147,15 @@ impl<'w> EntityWorldMut<'w> { /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[track_caller] - pub fn retain(&mut self) -> &mut Self { + pub fn retain(&mut self) -> &mut Self { self.retain_with_caller::(MaybeLocation::caller()) } #[inline] - pub(crate) fn retain_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { + pub(crate) fn retain_with_caller( + &mut self, + caller: MaybeLocation, + ) -> &mut Self { let old_location = self.location(); let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; @@ -2159,7 +2167,7 @@ impl<'w> EntityWorldMut<'w> { let retained_bundle = self .world .bundles - .register_info::(&mut registrator, storages); + .register_static_info::(&mut registrator, storages); // SAFETY: `retained_bundle` exists as we just initialized it. let retained_bundle_info = unsafe { self.world.bundles.get_unchecked(retained_bundle) }; let old_archetype = &mut archetypes[old_location.archetype_id]; @@ -2169,10 +2177,12 @@ impl<'w> EntityWorldMut<'w> { .components() .filter(|c| !retained_bundle_info.contributed_components().contains(c)) .collect::>(); - let remove_bundle = - self.world - .bundles - .init_dynamic_info(&mut self.world.storages, ®istrator, to_remove); + let remove_bundle = self.world.bundles.init_dynamic_info( + "", + &mut self.world.storages, + ®istrator, + to_remove, + ); // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. let Some(mut remover) = (unsafe { @@ -2262,6 +2272,7 @@ impl<'w> EntityWorldMut<'w> { let components = &mut self.world.components; let bundle_id = self.world.bundles.init_dynamic_info( + "", &mut self.world.storages, components, component_ids, @@ -2307,6 +2318,7 @@ impl<'w> EntityWorldMut<'w> { let components = &mut self.world.components; let bundle_id = self.world.bundles.init_dynamic_info( + "", &mut self.world.storages, components, component_ids.as_slice(), @@ -2643,14 +2655,14 @@ impl<'w> EntityWorldMut<'w> { /// /// Panics if the given system is an exclusive system. #[track_caller] - pub fn observe( + pub fn observe( &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { self.observe_with_caller(observer, MaybeLocation::caller()) } - pub(crate) fn observe_with_caller( + pub(crate) fn observe_with_caller( &mut self, observer: impl IntoObserverSystem, caller: MaybeLocation, @@ -2777,7 +2789,7 @@ impl<'w> EntityWorldMut<'w> { /// /// - If this entity has been despawned while this `EntityWorldMut` is still alive. /// - If the target entity does not exist. - pub fn clone_components(&mut self, target: Entity) -> &mut Self { + pub fn clone_components(&mut self, target: Entity) -> &mut Self { self.assert_not_despawned(); EntityCloner::build(self.world) @@ -2800,7 +2812,7 @@ impl<'w> EntityWorldMut<'w> { /// /// - If this entity has been despawned while this `EntityWorldMut` is still alive. /// - If the target entity does not exist. - pub fn move_components(&mut self, target: Entity) -> &mut Self { + pub fn move_components(&mut self, target: Entity) -> &mut Self { self.assert_not_despawned(); EntityCloner::build(self.world) @@ -3465,7 +3477,7 @@ impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a> { } } -impl<'a, B: Bundle> From<&'a EntityRefExcept<'_, B>> for FilteredEntityRef<'a> { +impl<'a, B: StaticBundle> From<&'a EntityRefExcept<'_, B>> for FilteredEntityRef<'a> { fn from(value: &'a EntityRefExcept<'_, B>) -> Self { // SAFETY: // - The FilteredEntityRef has the same component access as the given EntityRefExcept. @@ -3813,7 +3825,7 @@ impl<'a> From<&'a mut EntityWorldMut<'_>> for FilteredEntityMut<'a> { } } -impl<'a, B: Bundle> From<&'a EntityMutExcept<'_, B>> for FilteredEntityMut<'a> { +impl<'a, B: StaticBundle> From<&'a EntityMutExcept<'_, B>> for FilteredEntityMut<'a> { fn from(value: &'a EntityMutExcept<'_, B>) -> Self { // SAFETY: // - The FilteredEntityMut has the same component access as the given EntityMutExcept. @@ -3887,7 +3899,7 @@ pub enum TryFromFilteredError { /// for an explicitly-enumerated set. pub struct EntityRefExcept<'w, B> where - B: Bundle, + B: StaticBundle, { entity: UnsafeEntityCell<'w>, phantom: PhantomData, @@ -3895,7 +3907,7 @@ where impl<'w, B> EntityRefExcept<'w, B> where - B: Bundle, + B: StaticBundle, { /// # Safety /// Other users of `UnsafeEntityCell` must only have mutable access to the components in `B`. @@ -4056,7 +4068,7 @@ where impl<'a, B> From<&'a EntityMutExcept<'_, B>> for EntityRefExcept<'a, B> where - B: Bundle, + B: StaticBundle, { fn from(entity: &'a EntityMutExcept<'_, B>) -> Self { // SAFETY: All accesses that `EntityRefExcept` provides are also @@ -4065,23 +4077,23 @@ where } } -impl Clone for EntityRefExcept<'_, B> { +impl Clone for EntityRefExcept<'_, B> { fn clone(&self) -> Self { *self } } -impl Copy for EntityRefExcept<'_, B> {} +impl Copy for EntityRefExcept<'_, B> {} -impl PartialEq for EntityRefExcept<'_, B> { +impl PartialEq for EntityRefExcept<'_, B> { fn eq(&self, other: &Self) -> bool { self.entity() == other.entity() } } -impl Eq for EntityRefExcept<'_, B> {} +impl Eq for EntityRefExcept<'_, B> {} -impl PartialOrd for EntityRefExcept<'_, B> { +impl PartialOrd for EntityRefExcept<'_, B> { /// [`EntityRefExcept`]'s comparison trait implementations match the underlying [`Entity`], /// and cannot discern between different worlds. fn partial_cmp(&self, other: &Self) -> Option { @@ -4089,26 +4101,26 @@ impl PartialOrd for EntityRefExcept<'_, B> { } } -impl Ord for EntityRefExcept<'_, B> { +impl Ord for EntityRefExcept<'_, B> { fn cmp(&self, other: &Self) -> Ordering { self.entity().cmp(&other.entity()) } } -impl Hash for EntityRefExcept<'_, B> { +impl Hash for EntityRefExcept<'_, B> { fn hash(&self, state: &mut H) { self.entity().hash(state); } } -impl ContainsEntity for EntityRefExcept<'_, B> { +impl ContainsEntity for EntityRefExcept<'_, B> { fn entity(&self) -> Entity { self.id() } } // SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl EntityEquivalent for EntityRefExcept<'_, B> {} +unsafe impl EntityEquivalent for EntityRefExcept<'_, B> {} /// Provides mutable access to all components of an entity, with the exception /// of an explicit set. @@ -4120,7 +4132,7 @@ unsafe impl EntityEquivalent for EntityRefExcept<'_, B> {} /// [`crate::query::Without`] filter. pub struct EntityMutExcept<'w, B> where - B: Bundle, + B: StaticBundle, { entity: UnsafeEntityCell<'w>, phantom: PhantomData, @@ -4128,7 +4140,7 @@ where impl<'w, B> EntityMutExcept<'w, B> where - B: Bundle, + B: StaticBundle, { /// # Safety /// Other users of `UnsafeEntityCell` must not have access to any components not in `B`. @@ -4288,15 +4300,15 @@ where } } -impl PartialEq for EntityMutExcept<'_, B> { +impl PartialEq for EntityMutExcept<'_, B> { fn eq(&self, other: &Self) -> bool { self.entity() == other.entity() } } -impl Eq for EntityMutExcept<'_, B> {} +impl Eq for EntityMutExcept<'_, B> {} -impl PartialOrd for EntityMutExcept<'_, B> { +impl PartialOrd for EntityMutExcept<'_, B> { /// [`EntityMutExcept`]'s comparison trait implementations match the underlying [`Entity`], /// and cannot discern between different worlds. fn partial_cmp(&self, other: &Self) -> Option { @@ -4304,30 +4316,30 @@ impl PartialOrd for EntityMutExcept<'_, B> { } } -impl Ord for EntityMutExcept<'_, B> { +impl Ord for EntityMutExcept<'_, B> { fn cmp(&self, other: &Self) -> Ordering { self.entity().cmp(&other.entity()) } } -impl Hash for EntityMutExcept<'_, B> { +impl Hash for EntityMutExcept<'_, B> { fn hash(&self, state: &mut H) { self.entity().hash(state); } } -impl ContainsEntity for EntityMutExcept<'_, B> { +impl ContainsEntity for EntityMutExcept<'_, B> { fn entity(&self) -> Entity { self.id() } } // SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl EntityEquivalent for EntityMutExcept<'_, B> {} +unsafe impl EntityEquivalent for EntityMutExcept<'_, B> {} fn bundle_contains_component(components: &Components, query_id: ComponentId) -> bool where - B: Bundle, + B: StaticBundle, { let mut found = false; B::get_component_ids(components, &mut |maybe_id| { @@ -4363,7 +4375,7 @@ unsafe fn insert_dynamic_bundle< components: I, } - impl<'a, I: Iterator)>> DynamicBundle + impl<'a, I: Iterator)>> ComponentsFromBundle for DynamicInsertBundle<'a, I> { type Effect = (); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ed7c1f2cdd82b..e4fa821dad3bc 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -17,12 +17,6 @@ pub use crate::{ change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}, world::command_queue::CommandQueue, }; -use crate::{ - error::{DefaultErrorHandler, ErrorHandler}, - event::BufferedEvent, - lifecycle::{ComponentHooks, ADD, DESPAWN, INSERT, REMOVE, REPLACE}, - prelude::{Add, Despawn, Insert, Remove, Replace}, -}; pub use bevy_ecs_macros::FromWorld; pub use deferred_world::DeferredWorld; pub use entity_fetch::{EntityFetcher, WorldEntityFetch}; @@ -39,7 +33,7 @@ use crate::{ archetype::{ArchetypeId, Archetypes}, bundle::{ Bundle, BundleEffect, BundleInfo, BundleInserter, BundleSpawner, Bundles, InsertMode, - NoBundleEffect, + NoBundleEffect, StaticBundle, }, change_detection::{MaybeLocation, MutUntyped, TicksMut}, component::{ @@ -49,8 +43,12 @@ use crate::{ }, entity::{Entities, Entity, EntityDoesNotExistError}, entity_disabling::DefaultQueryFilters, - event::{Event, EventId, Events, SendBatchIds}, - lifecycle::RemovedComponentEvents, + error::{DefaultErrorHandler, ErrorHandler}, + event::{BufferedEvent, Event, EventId, Events, SendBatchIds}, + lifecycle::{ + Add, ComponentHooks, Despawn, Insert, Remove, RemovedComponentEvents, Replace, ADD, + DESPAWN, INSERT, REMOVE, REPLACE, + }, observer::Observers, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, relationship::RelationshipHookMode, @@ -1156,7 +1154,7 @@ impl World { self.flush(); let change_tick = self.change_tick(); let entity = self.entities.alloc(); - let mut bundle_spawner = BundleSpawner::new::(self, change_tick); + let mut bundle_spawner = BundleSpawner::new(&bundle, self, change_tick); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent let (entity_location, after_effect) = unsafe { bundle_spawner.spawn_non_existent(entity, bundle, caller) }; @@ -1222,7 +1220,7 @@ impl World { pub fn spawn_batch(&mut self, iter: I) -> SpawnBatchIter<'_, I::IntoIter> where I: IntoIterator, - I::Item: Bundle, + I::Item: Bundle + StaticBundle, { SpawnBatchIter::new(self, iter.into_iter(), MaybeLocation::caller()) } @@ -2227,7 +2225,7 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle + StaticBundle, { self.insert_batch_with_caller(batch, InsertMode::Replace, MaybeLocation::caller()); } @@ -2252,7 +2250,7 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle + StaticBundle, { self.insert_batch_with_caller(batch, InsertMode::Keep, MaybeLocation::caller()); } @@ -2271,7 +2269,7 @@ impl World { ) where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle + StaticBundle, { struct InserterArchetypeCache<'w> { inserter: BundleInserter<'w>, @@ -2285,7 +2283,7 @@ impl World { unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.component_ids) }; let bundle_id = self .bundles - .register_info::(&mut registrator, &mut self.storages); + .register_static_info::(&mut registrator, &mut self.storages); let mut batch_iter = batch.into_iter(); @@ -2370,7 +2368,7 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle + StaticBundle, { self.try_insert_batch_with_caller(batch, InsertMode::Replace, MaybeLocation::caller()) } @@ -2392,7 +2390,7 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle + StaticBundle, { self.try_insert_batch_with_caller(batch, InsertMode::Keep, MaybeLocation::caller()) } @@ -2416,7 +2414,7 @@ impl World { where I: IntoIterator, I::IntoIter: Iterator, - B: Bundle, + B: Bundle + StaticBundle, { struct InserterArchetypeCache<'w> { inserter: BundleInserter<'w>, @@ -2430,7 +2428,7 @@ impl World { unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.component_ids) }; let bundle_id = self .bundles - .register_info::(&mut registrator, &mut self.storages); + .register_static_info::(&mut registrator, &mut self.storages); let mut invalid_entities = Vec::::new(); let mut batch_iter = batch.into_iter(); @@ -3013,13 +3011,13 @@ impl World { /// This is largely equivalent to calling [`register_component`](Self::register_component) on each /// component in the bundle. #[inline] - pub fn register_bundle(&mut self) -> &BundleInfo { + pub fn register_static_bundle(&mut self) -> &BundleInfo { // SAFETY: These come from the same world. `Self.components_registrator` can't be used since we borrow other fields too. let mut registrator = unsafe { ComponentsRegistrator::new(&mut self.components, &mut self.component_ids) }; let id = self .bundles - .register_info::(&mut registrator, &mut self.storages); + .register_static_info::(&mut registrator, &mut self.storages); // SAFETY: We just initialized the bundle so its id should definitely be valid. unsafe { self.bundles.get(id).debug_checked_unwrap() } } @@ -3029,16 +3027,19 @@ impl World { /// Note that the components need to be registered first, this function only creates a bundle combining them. Components /// can be registered with [`World::register_component`]/[`_with_descriptor`](World::register_component_with_descriptor). /// - /// **You should prefer to use the typed API [`World::register_bundle`] where possible and only use this in cases where + /// **You should prefer to use the typed API [`World::register_static_bundle`] where possible and only use this in cases where /// not all of the actual types are known at compile time.** /// /// # Panics /// This function will panic if any of the provided component ids do not belong to a component known to this [`World`]. #[inline] pub fn register_dynamic_bundle(&mut self, component_ids: &[ComponentId]) -> &BundleInfo { - let id = - self.bundles - .init_dynamic_info(&mut self.storages, &self.components, component_ids); + let id = self.bundles.init_dynamic_info( + "", + &mut self.storages, + &self.components, + component_ids, + ); // SAFETY: We just initialized the bundle so its id should definitely be valid. unsafe { self.bundles.get(id).debug_checked_unwrap() } } diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 16bd9bb8059b4..c2bbd8c0d05fd 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -1,5 +1,5 @@ use crate::{ - bundle::{Bundle, BundleSpawner, NoBundleEffect}, + bundle::{Bundle, BundleSpawner, NoBundleEffect, StaticBundle}, change_detection::MaybeLocation, entity::{Entity, EntitySetIterator}, world::World, @@ -13,7 +13,7 @@ use core::iter::FusedIterator; pub struct SpawnBatchIter<'w, I> where I: Iterator, - I::Item: Bundle, + I::Item: Bundle + StaticBundle, { inner: I, spawner: BundleSpawner<'w>, @@ -23,7 +23,7 @@ where impl<'w, I> SpawnBatchIter<'w, I> where I: Iterator, - I::Item: Bundle, + I::Item: Bundle + StaticBundle, { #[inline] #[track_caller] @@ -38,7 +38,7 @@ where let length = upper.unwrap_or(lower); world.entities.reserve(length as u32); - let mut spawner = BundleSpawner::new::(world, change_tick); + let mut spawner = BundleSpawner::new_static::(world, change_tick); spawner.reserve_storage(length); Self { @@ -52,7 +52,7 @@ where impl Drop for SpawnBatchIter<'_, I> where I: Iterator, - I::Item: Bundle, + I::Item: Bundle + StaticBundle, { fn drop(&mut self) { // Iterate through self in order to spawn remaining bundles. @@ -66,7 +66,7 @@ where impl Iterator for SpawnBatchIter<'_, I> where I: Iterator, - I::Item: Bundle, + I::Item: Bundle + StaticBundle, { type Item = Entity; @@ -84,7 +84,7 @@ where impl ExactSizeIterator for SpawnBatchIter<'_, I> where I: ExactSizeIterator, - T: Bundle, + T: Bundle + StaticBundle, { fn len(&self) -> usize { self.inner.len() @@ -94,7 +94,7 @@ where impl FusedIterator for SpawnBatchIter<'_, I> where I: FusedIterator, - T: Bundle, + T: Bundle + StaticBundle, { } @@ -102,6 +102,6 @@ where unsafe impl EntitySetIterator for SpawnBatchIter<'_, I> where I: FusedIterator, - T: Bundle, + T: Bundle + StaticBundle, { } diff --git a/crates/bevy_render/src/extract_component.rs b/crates/bevy_render/src/extract_component.rs index e1f528d6ab2bd..1459dc29cee31 100644 --- a/crates/bevy_render/src/extract_component.rs +++ b/crates/bevy_render/src/extract_component.rs @@ -8,7 +8,7 @@ use crate::{ }; use bevy_app::{App, Plugin}; use bevy_ecs::{ - bundle::NoBundleEffect, + bundle::{NoBundleEffect, StaticBundle}, component::Component, prelude::*, query::{QueryFilter, QueryItem, ReadOnlyQueryData}, @@ -54,7 +54,7 @@ pub trait ExtractComponent: Component { /// /// `Out` has a [`Bundle`] trait bound instead of a [`Component`] trait bound in order to allow use cases /// such as tuples of components as output. - type Out: Bundle; + type Out: Bundle + StaticBundle; // TODO: https://github.com/rust-lang/rust/issues/29661 // type Out: Component = Self; diff --git a/release-content/migration-guides/static-bundles.md b/release-content/migration-guides/static-bundles.md new file mode 100644 index 0000000000000..3e9fcf112609b --- /dev/null +++ b/release-content/migration-guides/static-bundles.md @@ -0,0 +1,55 @@ +--- +title: StaticBundle split off from Bundle +pull_requests: [19491] +--- + +The `StaticBundle` trait has been split off from the `Bundle` trait to avoid conflating the concept of a type whose values can be inserted into an entity (`Bundle`) with the concept of a statically known set of components (`StaticBundle`). This required the update of existing APIs that were using `Bundle` as a statically known set of components to use `StaticBundle` instead. + +Changes for most users will be zero or pretty minimal, since `#[derive(Bundle)]` will automatically derive `StaticBundle` and most types that implemented `Bundle` will now also implement `StaticBundle`. The main exception will be generic APIs or types, which now will need to update or add a bound on `StaticBundle`. For example: + +```rs +// 0.16 +#[derive(Bundle)] +struct MyBundleWrapper { + inner: T +} + +fn my_register_bundle(world: &mut World) { + world.register_bundle::(); +} + + +// 0.17 +#[derive(Bundle)] +struct MyBundleWrapper { // Add a StaticBundle bound + inner: T +} + +fn my_register_bundle(world: &mut World) { // Replace Bundle with StaticBundle + world.register_bundle::(); +} +``` + +The following APIs now require the `StaticBundle` trait instead of the `Bundle` trait: + +- `World::register_bundle`, which has been renamed to `World::register_static_bundle` +- the `B` type parameter of `EntityRefExcept` and `EntityMutExcept` +- `EntityClonerBuilder::allow` and `EntityClonerBuilder::deny` +- `EntityCommands::clone_components` and `EntityCommands::move_components` +- `EntityWorldMut::clone_components` and `EntityWorldMut::move_components` +- the `B` type parameter of `IntoObserverSystem`, `Trigger`, `App::add_observer`, `World::add_observer`, `Observer::new`, `Commands::add_observer`, `EntityCommands::observe` and `EntityWorldMut::observe` +- `EntityWorldMut::remove_recursive` and `Commands::remove_recursive` +- `EntityCommands::remove`, `EntityCommands::remove_if`, `EntityCommands::try_remove_if`, `EntityCommands::try_remove`, `EntityCommands::remove_with_requires`, `EntityWorldMut::remove` and `EntityWorldMut::remove_with_requires` +- `EntityWorldMut::take` +- `EntityWorldMut::retain` and `EntityCommands::retain` + +The following APIs now require the `StaticBundle` trait in addition to the `Bundle` trait: + +- `Commands::spawn_batch`, `Commands::insert_batch`, `Commands::insert_batch_if_new`, `Commands::try_insert_batch`, `Commands::try_insert_batch_if_new`, `bevy::ecs::command::spawn_batch`, `bevy::ecs::command::insert_batch`, `World::spawn_batch`, `World::insert_batch`, `World::insert_batch_if_new`, `World::try_insert_batch` and `World::try_insert_batch_if_new` +- `ReflectBundle::new`, `impl FromType` for `ReflectBundle` and `#[reflect(Bundle)]` +- `ExtractComponent::Out` + +Moreover, some APIs have been renamed: + +- `World::register_bundle` has been renamed to `World::register_static_bundle` +- the `DynamicBundle` trait has been renamed to `ComponentsFromBundle` diff --git a/release-content/release-notes/dynamic-bundles.md b/release-content/release-notes/dynamic-bundles.md new file mode 100644 index 0000000000000..54a6be9e3efa5 --- /dev/null +++ b/release-content/release-notes/dynamic-bundles.md @@ -0,0 +1,53 @@ +--- +title: Option and dynamic bundles +authors: ["@SkiFire13"] +pull_requests: [19491] +--- + +Until now bundles have always represented a static set of components, meaning you couldn't create a `Bundle` that sometimes inserts a component and sometimes doesn't, or a totally dynamic bundle like you would expect from a `Box`. + +The `Bundle` trait has now been reworked to support these usecases, in particular: + +- `Option` implements `Bundle` +- `Vec>` also implements `Bundle` + +```rs +fn enemy_bundle(name: String, attack_power: u32, is_boss: bool) -> impl Bundle { + ( + EnemyMarker, + Name::new(name), + AttackPower(attack_power), + if is_boss { Some(BossMarker) } else { None } + ) +} + +fn bundle_from_reflected_data(components: Vec>, registry: &TypeRegistry) -> impl Bundle { + components + .into_iter() + .map(|data| { + let Some(reflect_bundle) = registry.get_type_data::(data.type_id()) else { todo!() }; + let Ok(bundle_box) = reflect_bundle.get_boxed(data) else { todo!() }; + bundle_box + }) + .collect::>() +} +``` + +## `StaticBundle` + +In order to support these changes to `Bundle` we had to introduce a new trait, `StaticBundle`, for the cases where statically knowing the set of components is required, like for example `World::remove`. + +The `Bundle` derive macro will automatically implement this trait for you, so most things should continue working like before, but in any case check out the migration guide! + +In case however that your bundle contains some kind of dynamic bundle then this won't be possible, and you'll have to opt-out of automatically implementing `StaticBundle` and `BundleFromComponents` by adding the `#[reflect(dynamic)]` attribute. + +```rs +#[derive(Bundle)] +#[derive(dynamic)] // This is required if any field is like the ones below! +struct MyBundle { + foo: Option, + extra: Box, + extras: Vec>, +} +```