diff --git a/benches/benches/bevy_ecs/components/fragmenting_values.rs b/benches/benches/bevy_ecs/components/fragmenting_values.rs new file mode 100644 index 0000000000000..f234961701ab1 --- /dev/null +++ b/benches/benches/bevy_ecs/components/fragmenting_values.rs @@ -0,0 +1,123 @@ +use bevy_ecs::prelude::*; +use criterion::Criterion; +use glam::*; + +#[derive(Component, PartialEq, Eq, Hash, Clone)] +#[component(immutable, key=Self)] +struct Fragmenting(u32); + +#[derive(Component)] +struct NonFragmenting(Vec3); + +pub fn insert_fragmenting_value(c: &mut Criterion) { + let mut group = c.benchmark_group("insert_fragmenting_value"); + group.warm_up_time(core::time::Duration::from_millis(500)); + group.measurement_time(core::time::Duration::from_secs(5)); + group.bench_function("base", |b| { + b.iter(move || { + let mut world = World::new(); + world.spawn_batch((0..10_000).map(|_| { + ( + Fragmenting::<1>(1), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + ) + })); + }); + }); + group.bench_function("unbatched", |b| { + b.iter(move || { + let mut world = World::new(); + for _ in 0..10_000 { + world.spawn(( + Fragmenting::<1>(1), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + )); + } + }); + }); + group.bench_function("high_fragmentation_base", |b| { + b.iter(move || { + let mut world = World::new(); + world.spawn_batch((0..10_000).map(|i| { + ( + Fragmenting::<1>(i % 100), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + ) + })); + }); + }); + group.bench_function("high_fragmentation_unbatched", |b| { + b.iter(move || { + let mut world = World::new(); + for i in 0..10_000 { + world.spawn(( + Fragmenting::<1>(i % 100), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + )); + } + }); + }); + group.finish(); +} + +pub fn add_remove_fragmenting_value(c: &mut Criterion) { + let mut group = c.benchmark_group("add_remove_fragmenting_value"); + group.warm_up_time(core::time::Duration::from_millis(500)); + group.measurement_time(core::time::Duration::from_secs(5)); + + group.bench_function("non_fragmenting", |b| { + let mut world = World::new(); + let entities: Vec<_> = world + .spawn_batch((0..10_000).map(|_| { + ( + Fragmenting::<1>(1), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + ) + })) + .collect(); + b.iter(move || { + for entity in &entities { + world + .entity_mut(*entity) + .insert(NonFragmenting::<4>(Vec3::ZERO)); + } + + for entity in &entities { + world.entity_mut(*entity).remove::>(); + } + }); + }); + + group.bench_function("fragmenting", |b| { + let mut world = World::new(); + let entities: Vec<_> = world + .spawn_batch((0..10_000).map(|_| { + ( + Fragmenting::<1>(1), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + ) + })) + .collect(); + b.iter(move || { + for entity in &entities { + world.entity_mut(*entity).insert(Fragmenting::<2>(1)); + } + + for entity in &entities { + world.entity_mut(*entity).remove::>(); + } + }); + }); +} diff --git a/benches/benches/bevy_ecs/components/mod.rs b/benches/benches/bevy_ecs/components/mod.rs index aec44ed27c9d0..52e070c82ec2b 100644 --- a/benches/benches/bevy_ecs/components/mod.rs +++ b/benches/benches/bevy_ecs/components/mod.rs @@ -5,11 +5,13 @@ mod add_remove_sparse_set; mod add_remove_table; mod add_remove_very_big_table; mod archetype_updates; +mod fragmenting_values; mod insert_simple; mod insert_simple_unbatched; use archetype_updates::*; use criterion::{criterion_group, Criterion}; +use fragmenting_values::*; criterion_group!( benches, @@ -19,6 +21,8 @@ criterion_group!( insert_simple, no_archetypes, added_archetypes, + insert_fragmenting_value, + add_remove_fragmenting_value ); fn add_remove(c: &mut Criterion) { diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 80763ee37bf3f..183b877c1553c 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -62,6 +62,7 @@ impl Default for OrderIndependentTransparencySettings { impl Component for OrderIndependentTransparencySettings { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; + type Key = NoKey; fn on_add() -> Option { Some(|world, context| { diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 6f14e9e980f1e..c534760555018 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -201,6 +201,11 @@ pub fn derive_component(input: TokenStream) -> TokenStream { ) }; + let key = attrs + .key + .map(ToTokens::into_token_stream) + .unwrap_or_else(|| quote! {#bevy_ecs_path::component::NoKey}); + // This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top // level components are initialized first, giving them precedence over recursively defined constructors for the same component type TokenStream::from(quote! { @@ -208,6 +213,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage; type Mutability = #mutable_type; + type Key = #key; fn register_required_components( _requiree: #bevy_ecs_path::component::ComponentId, required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator, @@ -342,6 +348,7 @@ pub const MAP_ENTITIES: &str = "map_entities"; pub const IMMUTABLE: &str = "immutable"; pub const CLONE_BEHAVIOR: &str = "clone_behavior"; +pub const KEY: &str = "key"; /// All allowed attribute value expression kinds for component hooks. /// This doesn't simply use general expressions because of conflicting needs: @@ -460,6 +467,7 @@ struct Attrs { immutable: bool, clone_behavior: Option, map_entities: Option, + key: Option, } #[derive(Clone, Copy)] @@ -500,6 +508,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { immutable: false, clone_behavior: None, map_entities: None, + key: None, }; let mut require_paths = HashSet::new(); @@ -541,6 +550,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else if nested.path.is_ident(MAP_ENTITIES) { attrs.map_entities = Some(nested.input.parse::()?); Ok(()) + } else if nested.path.is_ident(KEY) { + attrs.key = Some(nested.value()?.parse()?); + Ok(()) } else { Err(nested.error("Unsupported attribute")) } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index a388a30cb696d..461e889a1b016 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -164,6 +164,21 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { ) { #(<#active_field_types as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);)* } + + #[allow(unused_variables)] + #[inline] + fn get_fragmenting_values<'a>( + &'a self, + components: &#ecs_path::component::Components, + values: &mut impl FnMut(#ecs_path::component::FragmentingValueBorrowed<'a>) + ) { + #(self.#active_field_tokens.get_fragmenting_values(components, &mut *values);)* + } + + #[inline(always)] + fn count_fragmenting_values() -> usize { + 0 #(+ <#active_field_types as #ecs_path::bundle::Bundle>::count_fragmenting_values())* + } } }; diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 1885c801e8802..6942f32ce373f 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -21,7 +21,10 @@ use crate::{ bundle::BundleId, - component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, + component::{ + ComponentId, Components, FragmentingValue, FragmentingValues, FragmentingValuesBorrowed, + RequiredComponentConstructor, StorageType, TupleKey, + }, entity::{Entity, EntityLocation}, event::Event, observer::Observers, @@ -135,7 +138,8 @@ pub(crate) enum ComponentStatus { /// Used in [`Edges`] to cache the result of inserting a bundle into the source archetype. pub(crate) struct ArchetypeAfterBundleInsert { /// The target archetype after the bundle is inserted into the source archetype. - pub archetype_id: ArchetypeId, + /// This will be `None` if bundle contains fragmenting value components. + pub archetype_id: Option, /// For each component iterated in the same order as the source [`Bundle`](crate::bundle::Bundle), /// indicate if the component is newly added to the target archetype or if it already existed. bundle_status: Box<[ComponentStatus]>, @@ -216,6 +220,13 @@ pub struct Edges { insert_bundle: SparseArray, remove_bundle: SparseArray>, take_bundle: SparseArray>, + + /// Maps archetype to other component-identical archetypes based on [`FragmentingValue`]s of components. + /// All [`Archetype`]s this maps to differ only by their identity due to different [`FragmentingValues`], otherwise they're identical. + /// We need this map only when inserting bundles since when removing a fragmenting component all versions of the archetype will + /// point to the same archetype after transition. + pub(crate) insert_bundle_fragmenting_components: + HashMap, ArchetypeId>, } impl Edges { @@ -224,10 +235,23 @@ impl Edges { /// /// If this returns `None`, it means there has not been a transition from /// the source archetype via the provided bundle. + /// + /// # Safety + /// `value_components` must be from the same world as `self`. #[inline] - pub fn get_archetype_after_bundle_insert(&self, bundle_id: BundleId) -> Option { + pub unsafe fn get_archetype_after_bundle_insert( + &self, + bundle_id: BundleId, + value_components: &FragmentingValuesBorrowed, + ) -> Option { self.get_archetype_after_bundle_insert_internal(bundle_id) - .map(|bundle| bundle.archetype_id) + .and_then(|bundle| bundle.archetype_id) + .or_else(|| { + self.insert_bundle_fragmenting_components + // Safety: `value_components` will be compared with values from the same world. + .get(&(bundle_id, unsafe { value_components.as_equivalent() })) + .copied() + }) } /// Internal version of `get_archetype_after_bundle_insert` that @@ -250,21 +274,24 @@ impl Edges { required_components: impl Into>, mut added: Vec, existing: Vec, + value_components: FragmentingValues, ) { let added_len = added.len(); // Make sure `extend` doesn't over-reserve, since the conversion to `Box<[_]>` would reallocate to shrink. added.reserve_exact(existing.len()); added.extend(existing); - self.insert_bundle.insert( - bundle_id, - ArchetypeAfterBundleInsert { - archetype_id, - bundle_status: bundle_status.into(), - required_components: required_components.into(), - added_len, - inserted: added.into(), - }, - ); + let bundle = ArchetypeAfterBundleInsert { + archetype_id: (value_components.is_empty()).then_some(archetype_id), + bundle_status: bundle_status.into(), + required_components: required_components.into(), + added_len, + inserted: added.into(), + }; + if bundle.archetype_id.is_none() { + self.insert_bundle_fragmenting_components + .insert(TupleKey(bundle_id, value_components), archetype_id); + } + self.insert_bundle.insert(bundle_id, bundle); } /// Checks the cache for the target archetype when removing a bundle from the @@ -362,6 +389,7 @@ pub(crate) struct ArchetypeSwapRemoveResult { /// [`Component`]: crate::component::Component struct ArchetypeComponentInfo { storage_type: StorageType, + fragmenting_value: Option, } bitflags::bitflags! { @@ -380,6 +408,7 @@ bitflags::bitflags! { const ON_REPLACE_OBSERVER = (1 << 7); const ON_REMOVE_OBSERVER = (1 << 8); const ON_DESPAWN_OBSERVER = (1 << 9); + const FRAGMENTING_VALUE_COMPONENTS = (1 << 10); } } @@ -400,7 +429,7 @@ pub struct Archetype { impl Archetype { /// `table_components` and `sparse_set_components` must be sorted - pub(crate) fn new( + pub(crate) fn new<'a>( components: &Components, component_index: &mut ComponentIndex, observers: &Observers, @@ -408,6 +437,7 @@ impl Archetype { table_id: TableId, table_components: impl Iterator, sparse_set_components: impl Iterator, + value_components: impl Iterator, ) -> Self { let (min_table, _) = table_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint(); @@ -422,6 +452,7 @@ impl Archetype { component_id, ArchetypeComponentInfo { storage_type: StorageType::Table, + fragmenting_value: None, }, ); // NOTE: the `table_components` are sorted AND they were inserted in the `Table` in the same @@ -442,6 +473,7 @@ impl Archetype { component_id, ArchetypeComponentInfo { storage_type: StorageType::SparseSet, + fragmenting_value: None, }, ); component_index @@ -449,6 +481,14 @@ impl Archetype { .or_default() .insert(id, ArchetypeRecord { column: None }); } + + for value in value_components { + if let Some(info) = archetype_components.get_mut(value.component_id()) { + info.fragmenting_value = Some(value.clone()); + } + flags.insert(ArchetypeFlags::FRAGMENTING_VALUE_COMPONENTS); + } + Self { id, table_id, @@ -554,6 +594,14 @@ impl Archetype { self.components.len() } + /// Gets an iterator of all of the components that fragment archetypes by value. + #[inline] + pub fn fragmenting_value_components(&self) -> impl Iterator { + self.components + .values() + .filter_map(|info| info.fragmenting_value.as_ref()) + } + /// Fetches an immutable reference to the archetype's [`Edges`], a cache of /// archetypal relationships. #[inline] @@ -672,6 +720,24 @@ impl Archetype { .map(|info| info.storage_type) } + /// Returns [`FragmentingValue`] for this archetype of the requested `component_id`. + /// + /// This will return `None` if requested component isn't a part of this archetype or isn't fragmenting. + pub fn get_fragmenting_value_component( + &self, + component_id: ComponentId, + ) -> Option<&FragmentingValue> { + self.components + .get(component_id) + .and_then(|info| info.fragmenting_value.as_ref()) + } + + /// Returns `true` if this archetype contains any components that fragment by value. + pub fn has_fragmenting_value_components(&self) -> bool { + self.flags() + .contains(ArchetypeFlags::FRAGMENTING_VALUE_COMPONENTS) + } + /// Clears all entities from the archetype. pub(crate) fn clear_entities(&mut self) { self.entities.clear(); @@ -767,6 +833,7 @@ impl ArchetypeGeneration { struct ArchetypeComponents { table_components: Box<[ComponentId]>, sparse_set_components: Box<[ComponentId]>, + value_components: FragmentingValues, } /// Maps a [`ComponentId`] to the list of [`Archetypes`]([`Archetype`]) that contain the [`Component`](crate::component::Component), @@ -813,6 +880,7 @@ impl Archetypes { TableId::empty(), Vec::new(), Vec::new(), + Default::default(), ); } archetypes @@ -906,10 +974,12 @@ impl Archetypes { table_id: TableId, table_components: Vec, sparse_set_components: Vec, + value_components: FragmentingValues, ) -> (ArchetypeId, bool) { let archetype_identity = ArchetypeComponents { sparse_set_components: sparse_set_components.into_boxed_slice(), table_components: table_components.into_boxed_slice(), + value_components, }; let archetypes = &mut self.archetypes; @@ -920,6 +990,7 @@ impl Archetypes { let ArchetypeComponents { table_components, sparse_set_components, + value_components, } = vacant.key(); let id = ArchetypeId::new(archetypes.len()); archetypes.push(Archetype::new( @@ -930,6 +1001,7 @@ impl Archetypes { table_id, table_components.iter().copied(), sparse_set_components.iter().copied(), + value_components.iter(), )); vacant.insert(id); (id, true) diff --git a/crates/bevy_ecs/src/bundle/impls.rs b/crates/bevy_ecs/src/bundle/impls.rs index d4ab96414c19c..174778859b3f8 100644 --- a/crates/bevy_ecs/src/bundle/impls.rs +++ b/crates/bevy_ecs/src/bundle/impls.rs @@ -6,13 +6,17 @@ use variadics_please::all_tuples_enumerated; use crate::{ bundle::{Bundle, BundleFromComponents, DynamicBundle, NoBundleEffect}, - component::{Component, ComponentId, Components, ComponentsRegistrator, StorageType}, + component::{ + Component, ComponentId, Components, ComponentsRegistrator, FragmentingValueBorrowed, + StorageType, + }, world::EntityWorldMut, }; // 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. +// - `Bundle::get_fragmenting_values` uses only the passed in `Components` to create `FragmentingValueBorrowed` unsafe impl Bundle for C { fn component_ids(components: &mut ComponentsRegistrator, ids: &mut impl FnMut(ComponentId)) { ids(components.register_component::()); @@ -21,6 +25,26 @@ unsafe impl Bundle for C { fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)) { ids(components.get_id(TypeId::of::())); } + + #[inline] + fn get_fragmenting_values<'a>( + &'a self, + components: &Components, + values: &mut impl FnMut(FragmentingValueBorrowed<'a>), + ) { + if let Some(component) = FragmentingValueBorrowed::from_component(components, self) { + values(component); + } + } + + #[inline] + fn count_fragmenting_values() -> usize { + if TypeId::of::() == TypeId::of::() { + 1 + } else { + 0 + } + } } // SAFETY: @@ -78,6 +102,23 @@ macro_rules! tuple_impl { fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)){ $(<$name as Bundle>::get_component_ids(components, ids);)* } + + fn get_fragmenting_values<'a>(&'a self, components: &Components, values: &mut impl FnMut(FragmentingValueBorrowed<'a>)) { + #[allow( + non_snake_case, + reason = "The names of these variables are provided by the caller, not by us." + )] + let ($($name,)*) = &self; + $( + $name.get_fragmenting_values(components, &mut *values); + )* + + } + + #[inline(always)] + fn count_fragmenting_values() -> usize { + 0 $(+ <$name as Bundle>::count_fragmenting_values())* + } } #[expect( diff --git a/crates/bevy_ecs/src/bundle/insert.rs b/crates/bevy_ecs/src/bundle/insert.rs index 69c669d6406f3..91e12f0401585 100644 --- a/crates/bevy_ecs/src/bundle/insert.rs +++ b/crates/bevy_ecs/src/bundle/insert.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use bevy_platform::collections::HashMap; use bevy_ptr::{ConstNonNull, MovingPtr}; use core::ptr::NonNull; @@ -9,7 +10,7 @@ use crate::{ }, bundle::{ArchetypeMoveType, Bundle, BundleId, BundleInfo, DynamicBundle, InsertMode}, change_detection::MaybeLocation, - component::{Components, StorageType, Tick}, + component::{Components, FragmentingValues, FragmentingValuesBorrowed, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, event::EntityComponentsTrigger, lifecycle::{Add, Insert, Replace, ADD, INSERT, REPLACE}, @@ -37,11 +38,21 @@ impl<'w> BundleInserter<'w> { world: &'w mut World, archetype_id: ArchetypeId, change_tick: Tick, + bundle: &T, ) -> Self { let bundle_id = world.register_bundle_info::(); + let value_components = FragmentingValuesBorrowed::from_bundle(world.components(), bundle); // SAFETY: We just ensured this bundle exists - unsafe { Self::new_with_id(world, archetype_id, bundle_id, change_tick) } + unsafe { + Self::new_with_id( + world, + archetype_id, + bundle_id, + change_tick, + &value_components, + ) + } } /// Creates a new [`BundleInserter`]. @@ -54,6 +65,7 @@ impl<'w> BundleInserter<'w> { archetype_id: ArchetypeId, bundle_id: BundleId, change_tick: Tick, + value_components: &FragmentingValuesBorrowed, ) -> Self { // SAFETY: We will not make any accesses to the command queue, component or resource data of this world let bundle_info = world.bundles.get_unchecked(bundle_id); @@ -64,6 +76,7 @@ impl<'w> BundleInserter<'w> { &world.components, &world.observers, archetype_id, + value_components, ); let inserter = if new_archetype_id == archetype_id { @@ -432,7 +445,8 @@ impl BundleInfo { /// Results are cached in the [`Archetype`] graph to avoid redundant work. /// /// # Safety - /// `components` must be the same components as passed in [`Self::new`] + /// - `components` must be the same components as passed in [`Self::new`] + /// - `value_components` must be created using `components` pub(crate) unsafe fn insert_bundle_into_archetype( &self, archetypes: &mut Archetypes, @@ -440,15 +454,20 @@ impl BundleInfo { components: &Components, observers: &Observers, archetype_id: ArchetypeId, + value_components: &FragmentingValuesBorrowed, ) -> (ArchetypeId, bool) { if let Some(archetype_after_insert_id) = archetypes[archetype_id] .edges() - .get_archetype_after_bundle_insert(self.id) + .get_archetype_after_bundle_insert(self.id, value_components) { return (archetype_after_insert_id, false); } + let value_components = + value_components.to_owned(components, &mut storages.fragmenting_values); + let mut new_table_components = Vec::new(); let mut new_sparse_set_components = Vec::new(); + let mut new_value_components = HashMap::new(); let mut bundle_status = Vec::with_capacity(self.explicit_components_len()); let mut added_required_components = Vec::new(); let mut added = Vec::new(); @@ -488,7 +507,22 @@ impl BundleInfo { } } - if new_table_components.is_empty() && new_sparse_set_components.is_empty() { + for value in value_components.iter() { + let component_id = value.component_id(); + if current_archetype + .get_fragmenting_value_component(component_id) + .is_none_or(|old_value| value != old_value) + { + new_value_components.insert(component_id, value); + } + } + + if new_table_components.is_empty() + && new_sparse_set_components.is_empty() + && new_value_components.is_empty() + { + drop(new_value_components); + let edges = current_archetype.edges_mut(); // The archetype does not change when we insert this bundle. edges.cache_archetype_after_bundle_insert( @@ -498,12 +532,14 @@ impl BundleInfo { added_required_components, added, existing, + value_components, ); (archetype_id, false) } else { let table_id; let table_components; let sparse_set_components; + let archetype_value_components; // The archetype changes when we insert this bundle. Prepare the new archetype and storages. { let current_archetype = &archetypes[archetype_id]; @@ -533,7 +569,23 @@ impl BundleInfo { new_sparse_set_components.sort_unstable(); new_sparse_set_components }; + + archetype_value_components = if new_value_components.is_empty() { + FragmentingValues::from_sorted( + current_archetype.fragmenting_value_components().cloned(), + ) + } else { + current_archetype + .fragmenting_value_components() + .filter(|value| !new_value_components.contains_key(&value.component_id())) + .cloned() + .chain(new_value_components.values().map(|v| (*v).clone())) + .collect() + }; + + drop(new_value_components); }; + // SAFETY: ids in self must be valid let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert( components, @@ -541,6 +593,7 @@ impl BundleInfo { table_id, table_components, sparse_set_components, + archetype_value_components, ); // Add an edge from the old archetype to the new archetype. @@ -553,6 +606,7 @@ impl BundleInfo { added_required_components, added, existing, + value_components, ); (new_archetype_id, is_new_created) } diff --git a/crates/bevy_ecs/src/bundle/mod.rs b/crates/bevy_ecs/src/bundle/mod.rs index 17d894d40b660..eed3feb87f9a6 100644 --- a/crates/bevy_ecs/src/bundle/mod.rs +++ b/crates/bevy_ecs/src/bundle/mod.rs @@ -72,7 +72,9 @@ pub use info::*; pub use bevy_ecs_macros::Bundle; use crate::{ - component::{ComponentId, Components, ComponentsRegistrator, StorageType}, + component::{ + ComponentId, Components, ComponentsRegistrator, FragmentingValueBorrowed, StorageType, + }, world::EntityWorldMut, }; use bevy_ptr::OwningPtr; @@ -192,6 +194,7 @@ use bevy_ptr::OwningPtr; // 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`]. +// - [`Bundle::get_fragmenting_value`] must return [`FragmentingValueBorrowed`] created using only the passed in [`Components`]. #[diagnostic::on_unimplemented( message = "`{Self}` is not a `Bundle`", label = "invalid `Bundle`", @@ -204,6 +207,23 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { /// Gets this [`Bundle`]'s component ids. This will be [`None`] if the component has not been registered. fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)); + + /// Gets all [`FragmentingValue`]s of this bundle. `values` will be called at most [`Bundle::count_fragmenting_values`] times, + /// but won't run for unregistered but otherwise valid fragmenting components. + /// + /// [`FragmentingValue`]: crate::component::FragmentingValue + #[doc(hidden)] + fn get_fragmenting_values<'a>( + &'a self, + components: &Components, + values: &mut impl FnMut(FragmentingValueBorrowed<'a>), + ); + + /// Returns the exact number of [`FragmentingValueComponent`]s in this bundle. + /// + /// [`FragmentingValueComponent`]: crate::component::FragmentingValueComponent + #[doc(hidden)] + fn count_fragmenting_values() -> usize; } /// Creates a [`Bundle`] by taking it from internal storage. diff --git a/crates/bevy_ecs/src/bundle/remove.rs b/crates/bevy_ecs/src/bundle/remove.rs index f40c82c7f7d67..fea98c872a6ff 100644 --- a/crates/bevy_ecs/src/bundle/remove.rs +++ b/crates/bevy_ecs/src/bundle/remove.rs @@ -343,6 +343,7 @@ impl BundleInfo { let mut next_table_components; let mut next_sparse_set_components; let next_table_id; + let next_value_components; { let current_archetype = &mut archetypes[archetype_id]; let mut removed_table_components = Vec::new(); @@ -389,6 +390,19 @@ impl BundleInfo { .get_id_or_insert(&next_table_components, components) } }; + + next_value_components = current_archetype + .fragmenting_value_components() + .filter(|v| { + removed_sparse_set_components + .binary_search(&v.component_id()) + .is_err() + && removed_table_components + .binary_search(&v.component_id()) + .is_err() + }) + .cloned() + .collect(); } let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert( @@ -397,6 +411,7 @@ impl BundleInfo { next_table_id, next_table_components, next_sparse_set_components, + next_value_components, ); (Some(new_archetype_id), is_new_created) }; diff --git a/crates/bevy_ecs/src/bundle/spawner.rs b/crates/bevy_ecs/src/bundle/spawner.rs index b4f32147aecdf..f5903c1d6c6f9 100644 --- a/crates/bevy_ecs/src/bundle/spawner.rs +++ b/crates/bevy_ecs/src/bundle/spawner.rs @@ -6,7 +6,7 @@ use crate::{ archetype::{Archetype, ArchetypeCreated, ArchetypeId, SpawnBundleStatus}, bundle::{Bundle, BundleId, BundleInfo, DynamicBundle, InsertMode}, change_detection::MaybeLocation, - component::Tick, + component::{FragmentingValuesBorrowed, Tick}, entity::{Entities, Entity, EntityLocation}, event::EntityComponentsTrigger, lifecycle::{Add, Insert, ADD, INSERT}, @@ -26,11 +26,12 @@ pub(crate) struct BundleSpawner<'w> { impl<'w> BundleSpawner<'w> { #[inline] - pub fn new(world: &'w mut World, change_tick: Tick) -> Self { + pub fn new(world: &'w mut World, change_tick: Tick, bundle: &T) -> Self { let bundle_id = world.register_bundle_info::(); + let value_components = FragmentingValuesBorrowed::from_bundle(world.components(), bundle); // SAFETY: we initialized this bundle_id in `init_info` - unsafe { Self::new_with_id(world, bundle_id, change_tick) } + unsafe { Self::new_with_id(world, bundle_id, change_tick, value_components) } } /// Creates a new [`BundleSpawner`]. @@ -42,6 +43,7 @@ impl<'w> BundleSpawner<'w> { world: &'w mut World, bundle_id: BundleId, change_tick: Tick, + value_components: FragmentingValuesBorrowed, ) -> Self { let bundle_info = world.bundles.get_unchecked(bundle_id); let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype( @@ -50,6 +52,7 @@ impl<'w> BundleSpawner<'w> { &world.components, &world.observers, ArchetypeId::EMPTY, + &value_components, ); let archetype = &mut world.archetypes[new_archetype_id]; @@ -211,4 +214,11 @@ impl<'w> BundleSpawner<'w> { // SAFETY: pointers on self can be invalidated, self.world.world_mut().flush(); } + + #[inline] + pub(crate) fn replace(&mut self, bundle: &T) { + // Safety: There can't exist any other mutable references to world since current + // BundleSpawner requires `&mut World` to create. + *self = BundleSpawner::new(unsafe { self.world.world_mut() }, self.change_tick, bundle); + } } diff --git a/crates/bevy_ecs/src/component/fragmenting_value.rs b/crates/bevy_ecs/src/component/fragmenting_value.rs new file mode 100644 index 0000000000000..3b179986d7ec0 --- /dev/null +++ b/crates/bevy_ecs/src/component/fragmenting_value.rs @@ -0,0 +1,825 @@ +//! This module defines the concept of "fragmenting value" - a type that can be used to fragment +//! archetypes based on it's value in addition to it's type. The main trait is [`FragmentingValue`], +//! which is used to give each value that implements it a value-based identity, which is used by +//! other ecs functions to fragment archetypes. + +use alloc::boxed::Box; +use alloc::vec::Vec; +use bevy_ecs::component::Component; +use bevy_platform::{hash::FixedHasher, sync::Arc}; +use bevy_ptr::{OwningPtr, Ptr}; +use core::{ + any::{Any, TypeId}, + hash::{BuildHasher, Hash, Hasher}, + ops::Deref, + ptr::NonNull, +}; +use indexmap::Equivalent; + +use crate::{ + bundle::Bundle, + component::{ComponentId, Components, Immutable}, + query::DebugCheckedUnwrap, + storage::FragmentingValuesStorage, +}; + +/// Trait defining a [`Component`] that fragments archetypes by it's value. +pub trait FragmentingValueComponent: + Component + Eq + Hash + Clone + private::Seal +{ + /// Returns hash of this [`Component`] as a `u64`. + #[inline] + fn hash_data(&self) -> u64 { + FixedHasher.hash_one(self) + } +} + +impl FragmentingValueComponent for C where + C: Component + Eq + Hash + Clone +{ +} + +impl private::Seal for C where C: Component + Eq + Hash + Clone {} + +mod private { + pub trait Seal {} +} + +/// A [`FragmentingValueComponent`] that is used to mark components as **non**-fragmenting by value. +/// See [`Component::Key`] for more detail. +#[derive(Component, PartialEq, Eq, Hash, Clone)] +#[component(immutable)] +pub enum NoKey {} + +/// A type-erased version of [`FragmentingValueComponent`]. +/// +/// Each combination of component type + value is unique and is stored exactly once in [`FragmentingValuesStorage`]. +#[derive(Clone)] +pub struct FragmentingValue { + inner: Arc, +} + +impl FragmentingValue { + #[inline] + /// Returns [`ComponentId`] of this fragmenting value component. + pub fn component_id(&self) -> ComponentId { + self.inner.component_id + } + + #[inline] + /// Returns pointer to data of this fragmenting value component. + pub fn component_data(&self) -> Ptr<'_> { + // Safety: data points to properly-aligned, valid value of the type this component id is registered for. + unsafe { Ptr::new(self.inner.data) } + } +} + +impl Hash for FragmentingValue { + fn hash(&self, state: &mut H) { + // This must be implemented exactly the same way as Hash for FragmentingValueBorrowed! + self.component_id().hash(state); + state.write_u64(self.inner.data_hash); + } +} + +impl PartialEq for FragmentingValue { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.inner, &other.inner) + } +} + +struct FragmentingValueInner { + component_id: ComponentId, + data_hash: u64, + data_eq: for<'a> unsafe fn(Ptr<'a>, Ptr<'a>) -> bool, + data_drop: Option unsafe fn(OwningPtr<'a>)>, + data: NonNull, +} + +// Safety: data points to a type that is Sync, all other field are Sync +unsafe impl Sync for FragmentingValueInner {} + +// Safety: data points to a type that is Send, all other field are Send +unsafe impl Send for FragmentingValueInner {} + +impl Drop for FragmentingValueInner { + fn drop(&mut self) { + if let Some(drop) = self.data_drop { + // Safety: + // - `data` points to properly-aligned, valid value of the type this component id is registered for. + // - `drop` is valid to call for data with the type of of this component. + unsafe { drop(OwningPtr::new(self.data)) } + } + } +} + +impl Eq for FragmentingValue {} + +/// A collection of [`FragmentingValue`]. +/// +/// This collection is sorted internally to allow for order-independent comparison and can be used as a key in maps. +/// Can be created from [`FragmentingValuesBorrowed`]. +#[derive(Hash, PartialEq, Eq, Default, Clone)] +pub(crate) struct FragmentingValues { + values: Box<[FragmentingValue]>, +} + +impl Deref for FragmentingValues { + type Target = [FragmentingValue]; + + fn deref(&self) -> &Self::Target { + &self.values + } +} + +impl FromIterator for FragmentingValues { + fn from_iter>(iter: T) -> Self { + let mut values: Box<_> = iter.into_iter().collect(); + values.sort_unstable_by_key(FragmentingValue::component_id); + Self { values } + } +} + +impl FragmentingValues { + pub(crate) fn from_sorted>(iter: T) -> Self { + Self { + values: iter.into_iter().collect(), + } + } +} + +/// Type representing a [`FragmentingValueComponent`] borrowed from a bundle. +pub struct FragmentingValueBorrowed<'a> { + component_id: ComponentId, + data_hash: u64, + data: Ptr<'a>, +} + +impl<'a> FragmentingValueBorrowed<'a> { + /// Create a new [`FragmentingValueBorrowed`] from raw component data. + /// + /// This will return `None` if: + /// - Component isn't registered in the `components`. + /// - Component isn't a [`FragmentingValueComponent`]. + /// + /// # Safety + /// Data behind `component_data` pointer must match the component registered with this `component_id` in `components`. + #[inline] + pub unsafe fn new( + components: &Components, + component_id: ComponentId, + component_data: Ptr<'a>, + ) -> Option { + components + .get_info(component_id) + .and_then(|info| info.value_component_vtable()) + .map(|vtable| { + // Safety: component_data is a valid data of type represented by this ComponentId. + let data_hash = unsafe { (vtable.hash)(component_data) }; + Self { + component_id, + data_hash, + data: component_data, + } + }) + } + + /// Create a new [`FragmentingValueBorrowed`] from a [`Component`]. + /// + /// This will return `None` if: + /// - `C` isn't registered in `components`. + /// - `C` isn't a [`FragmentingValueComponent`]. + #[inline] + pub fn from_component(components: &Components, component: &'a C) -> Option { + (component as &dyn Any) + .downcast_ref::() + .and_then(|component| { + components + .get_id(TypeId::of::()) + .map(|component_id| Self { + component_id, + data_hash: component.hash_data(), + data: Ptr::from(component), + }) + }) + } + + /// Return [`ComponentId`] of this borrowed [`FragmentingValueComponent`]. + #[inline] + pub fn component_id(&self) -> ComponentId { + self.component_id + } + + /// Return pointer to data of this borrowed [`FragmentingValueComponent`]. + #[inline] + pub fn component_data(&self) -> Ptr<'a> { + self.data + } + + /// Create [`FragmentingValue`] by cloning data pointed to by this [`FragmentingValueBorrowed`] or getting existing one from [`FragmentingValuesStorage`]. + /// + /// # Safety + /// - `components` must be the same one as the one used to create `self`. + /// - `storage` and `components` must be from the same world. + pub(crate) unsafe fn to_owned( + &self, + components: &Components, + storage: &mut FragmentingValuesStorage, + ) -> FragmentingValue { + // Safety: `self` was created using same `Components` as the ones we will be comparing with + // since `components` and `storage` are from the same world. + let key = unsafe { self.as_equivalent() }; + storage + .existing_values + .get_or_insert_with(&key, |_| { + // Safety: id is a valid and registered component. + let info = unsafe { components.get_info_unchecked(self.component_id()) }; + // Safety: component is fragmenting since `FragmentingValueBorrowed` can only be created for valid fragmenting components. + let vtable = unsafe { info.value_component_vtable().debug_checked_unwrap() }; + let layout = info.layout(); + let data = if layout.size() == 0 { + NonNull::dangling() + } else { + // Safety: layout.size() != 0 + unsafe { NonNull::new(alloc::alloc::alloc(info.layout())).unwrap() } + }; + (vtable.clone)(self.data, data); + FragmentingValue { + inner: Arc::new(FragmentingValueInner { + component_id: self.component_id, + data_hash: self.data_hash, + data, + data_drop: info.drop(), + data_eq: vtable.eq, + }), + } + }) + .clone() + } + + /// Get a value that can be used to compare with and query maps containing [`FragmentingValue`]s as the key. + /// + /// # Safety + /// Caller must ensure that [`FragmentingValue`] this will compare with was created using the same [`Components`] as `self`. + #[inline] + pub(crate) unsafe fn as_equivalent(&self) -> impl AsEquivalent { + #[derive(Hash)] + pub struct FragmentingValueBorrowedKey<'a>(&'a FragmentingValueBorrowed<'a>); + + impl<'a> Equivalent for FragmentingValueBorrowedKey<'a> { + #[inline] + fn equivalent(&self, key: &FragmentingValue) -> bool { + self.0.component_id() == key.component_id() + // Safety: `self` and `key` point to the same component type since `self.component_id` and `key.component_id` are equal + // and were created using the same `Components` instance. + && unsafe { (key.inner.data_eq)(self.0.data, key.component_data()) } + } + } + + FragmentingValueBorrowedKey(self) + } +} + +impl<'a> Hash for FragmentingValueBorrowed<'a> { + fn hash(&self, state: &mut H) { + // This must be implemented exactly the same way as Hash for FragmentingValue! + self.component_id.hash(state); + state.write_u64(self.data_hash); + } +} + +/// A collection of [`FragmentingValueBorrowed`]. +/// +/// This collection is sorted internally to allow for order-independent comparison. +#[derive(Hash)] +pub struct FragmentingValuesBorrowed<'a> { + values: Vec>, +} + +impl<'a> Deref for FragmentingValuesBorrowed<'a> { + type Target = [FragmentingValueBorrowed<'a>]; + + fn deref(&self) -> &Self::Target { + self.values.as_slice() + } +} + +impl<'a> FragmentingValuesBorrowed<'a> { + /// Create a new [`FragmentingValuesBorrowed`] from a [`Bundle`]. + /// + /// NOTE: If any of the components in the bundle weren't registered, this might return incorrect result! + pub fn from_bundle(components: &Components, bundle: &'a B) -> Self { + let mut values = Vec::with_capacity(B::count_fragmenting_values()); + bundle.get_fragmenting_values(components, &mut |value| { + values.push(value); + }); + values.sort_unstable_by_key(FragmentingValueBorrowed::component_id); + FragmentingValuesBorrowed { values } + } + + /// Create a new [`FragmentingValuesBorrowed`] from a raw component data + their [`ComponentId`]. + /// + /// NOTE: If any of the components in the bundle weren't registered, this might return incorrect result! + /// + /// # Safety + /// Pointer to data for the corresponding [`ComponentId`] must point to a valid component data represented by the id. + pub unsafe fn from_components( + components: &Components, + iter: impl IntoIterator)>, + ) -> Self { + let mut values: Vec<_> = iter + .into_iter() + .filter_map(|(id, data)| FragmentingValueBorrowed::new(components, id, data)) + .collect(); + values.sort_unstable_by_key(FragmentingValueBorrowed::component_id); + FragmentingValuesBorrowed { values } + } + + /// Create [`FragmentingValues`] by cloning data pointed to by this [`FragmentingValuesBorrowed`] or getting existing ones from [`FragmentingValuesStorage`]. + /// + /// # Safety + /// `components` must be the same one as the one used to create `self`. + pub(crate) unsafe fn to_owned( + &self, + components: &Components, + storage: &mut FragmentingValuesStorage, + ) -> FragmentingValues { + let values = self + .values + .iter() + // Safety: v was created using `components` + .map(|v| unsafe { v.to_owned(components, storage) }) + .collect(); + FragmentingValues { values } + } + + /// Get a value that can be used to compare with and query maps containing [`FragmentingValues`]s as the key. + /// + /// # Safety + /// Caller must ensure that [`FragmentingValues`] this will compare with was created using the same [`Components`] as `self`. + #[inline] + pub(crate) unsafe fn as_equivalent(&self) -> impl AsEquivalent { + #[derive(Hash)] + pub struct FragmentingValuesBorrowedKey<'a>(&'a FragmentingValuesBorrowed<'a>); + + impl<'a> Equivalent for FragmentingValuesBorrowedKey<'a> { + #[inline] + fn equivalent(&self, key: &FragmentingValues) -> bool { + { + self.0.values.len() == key.values.len() + && self + .0 + .values + .iter() + .zip(key.values.iter()) + // Safety: v1 was created using the same Component as v2 + .all(|(v1, v2)| unsafe { v1.as_equivalent().equivalent(v2) }) + } + } + } + + FragmentingValuesBorrowedKey(self) + } +} + +/// [`Hash`] + [`Equivalent`] supertrait for hashmap queries. +pub trait AsEquivalent: Hash + Equivalent {} + +impl AsEquivalent for Q +where + Q: Hash + Equivalent, + K: ?Sized, +{ +} + +/// Workaround to allow querying hashmaps using tuples of [`Equivalent`] types. +#[derive(Hash, Eq, PartialEq)] +pub(crate) struct TupleKey(pub K1, pub K2); + +impl Equivalent> for (Q1, Q2) +where + Q1: Equivalent, + Q2: Equivalent, +{ + #[inline] + fn equivalent(&self, key: &TupleKey) -> bool { + self.0.equivalent(&key.0) && self.1.equivalent(&key.1) + } +} + +/// Dynamic vtable for [`FragmentingValueComponent`]. +/// This stores the functions required to compare, hash and store fragmenting values on [`crate::component::ComponentDescriptor`]. +#[derive(Clone, Copy, Debug)] +pub struct FragmentingValueVtable { + // Safety: This functions must be safe to call with pointers to the data of the component's type this vtable registered for. + eq: for<'a> unsafe fn(Ptr<'a>, Ptr<'a>) -> bool, + // Safety: This functions must be safe to call with pointer to the data of the component's type this vtable registered for. + hash: for<'a> unsafe fn(Ptr<'a>) -> u64, + // Safety: This functions must be safe to call with pointers to the data of the component's type this vtable registered for. + clone: for<'a> unsafe fn(Ptr<'a>, NonNull), +} + +impl FragmentingValueVtable { + /// Create a new vtable from raw functions. + /// + /// All pointers passed to the functions will point to the data of the component for which this vtable will be registered for. + /// - `eq`: `(self: *const C, other: *const C)` + /// - `hash`: `(self: *const C)` + /// - `clone`: `(self: *const C, target: *mut C)` + /// + /// # Safety + /// `clone` must initialize data behind `target` pointer to valid value of the same type as the one this vtable is registered for on call. + #[inline] + pub unsafe fn new( + eq: unsafe fn(Ptr<'_>, Ptr<'_>) -> bool, + hash: unsafe fn(Ptr<'_>) -> u64, + clone: unsafe fn(Ptr<'_>, NonNull), + ) -> Self { + Self { eq, hash, clone } + } + + /// Creates [`FragmentingValueVtable`] from a [`Component`]. + /// + /// This will return `None` if the component isn't fragmenting. + #[inline] + pub fn from_component() -> Option { + if TypeId::of::() != TypeId::of::() { + return None; + } + Some(FragmentingValueVtable { + // Safety: `this` and `other` are of type T, and T::Key == T + eq: |this, other| unsafe { this.deref::() == other.deref::() }, + // Safety: `this` is of type T and T::Key == T + hash: |this| unsafe { this.deref::().hash_data() }, + // Safety: `this` and `target` are of type T, and T::Key == T + clone: |this, target| unsafe { + target + .cast::() + .write(this.deref::().clone()); + }, + }) + } +} + +#[cfg(test)] +mod tests { + use core::{alloc::Layout, hash::BuildHasher, ptr::NonNull}; + + use crate::{ + archetype::ArchetypeId, + component::{ + Component, ComponentCloneBehavior, ComponentDescriptor, FragmentingValueVtable, + StorageType, + }, + entity::Entity, + world::World, + }; + use alloc::vec::Vec; + use bevy_platform::hash::FixedHasher; + use bevy_ptr::{OwningPtr, Ptr}; + use core::hash::Hash; + + #[derive(Component, Clone, Eq, PartialEq, Hash)] + #[component( + key=Self, + immutable, + )] + struct Fragmenting(u32); + + #[derive(Component, Clone, Eq, PartialEq, Hash)] + #[component( + key=Self, + immutable, + )] + struct FragmentingN(u32); + + #[derive(Component)] + struct NonFragmenting; + + #[test] + fn fragment_on_spawn() { + let mut world = World::default(); + let e1 = world.spawn(Fragmenting(1)).id(); + let e2 = world.spawn(Fragmenting(2)).id(); + let e3 = world.spawn(Fragmenting(1)).id(); + + let [id1, id2, id3] = world.entity([e1, e2, e3]).map(|e| e.archetype().id()); + assert_eq!(id1, id3); + assert_ne!(id2, id1); + } + + #[test] + fn fragment_on_spawn_order_does_not_matter() { + let mut world = World::default(); + let e1 = world + .spawn((NonFragmenting, FragmentingN::<1>(1), FragmentingN::<2>(1))) + .id(); + let e2 = world + .spawn((NonFragmenting, FragmentingN::<2>(1), FragmentingN::<1>(1))) + .id(); + + let [id1, id2] = world.entity([e1, e2]).map(|e| e.archetype().id()); + assert_eq!(id1, id2); + } + + #[test] + fn fragment_on_spawn_batch() { + let mut world = World::default(); + let entities: [Entity; 5] = world + .spawn_batch([ + Fragmenting(1), + Fragmenting(2), + Fragmenting(1), + Fragmenting(3), + Fragmenting(1), + ]) + .collect::>() + .try_into() + .unwrap(); + + let [id1, id2, id3, id4, id5] = world.entity(entities).map(|e| e.archetype().id()); + assert_eq!(id1, id3); + assert_eq!(id1, id5); + assert_ne!(id1, id2); + assert_ne!(id1, id4); + + assert_ne!(id2, id4); + } + + #[test] + fn fragment_on_insert() { + let mut world = World::default(); + let e1 = world.spawn_empty().id(); + let e2 = world.spawn_empty().id(); + + world.entity_mut(e1).insert(Fragmenting(1)); + world.entity_mut(e2).insert(Fragmenting(2)); + + let [id1, id2] = world.entity([e1, e2]).map(|e| e.archetype().id()); + assert_ne!(id1, id2); + + world.entity_mut(e2).insert(Fragmenting(1)); + + let [id1, id2] = world.entity([e1, e2]).map(|e| e.archetype().id()); + assert_eq!(id1, id2); + + world.entity_mut(e1).insert(Fragmenting(3)); + world.entity_mut(e2).insert(Fragmenting(3)); + + let [id1, id2] = world.entity([e1, e2]).map(|e| e.archetype().id()); + assert_eq!(id1, id2); + } + + #[test] + fn fragment_on_insert_batch() { + let mut world = World::default(); + let entities: [Entity; 5] = world + .spawn_batch([ + Fragmenting(1), + Fragmenting(2), + Fragmenting(1), + Fragmenting(3), + Fragmenting(1), + ]) + .collect::>() + .try_into() + .unwrap(); + world.insert_batch(entities.into_iter().zip([ + Fragmenting(2), + Fragmenting(2), + Fragmenting(3), + Fragmenting(4), + Fragmenting(4), + ])); + + let [id1, id2, id3, id4, id5] = world.entity(entities).map(|e| e.archetype().id()); + assert_eq!(id1, id2); + assert_ne!(id1, id3); + assert_ne!(id1, id4); + + assert_ne!(id3, id4); + + assert_eq!(id4, id5); + } + + #[test] + fn fragment_on_remove() { + let mut world = World::default(); + let entities: [Entity; 4] = world + .spawn_batch([ + (Fragmenting(1), NonFragmenting), + (Fragmenting(1), NonFragmenting), + (Fragmenting(1), NonFragmenting), + (Fragmenting(2), NonFragmenting), + ]) + .collect::>() + .try_into() + .unwrap(); + + world.entity_mut(entities[1]).remove::(); + world.entity_mut(entities[2]).remove::(); + world.entity_mut(entities[3]).remove::(); + + let [id1, id2, id3, id4] = world.entity(entities).map(|e| e.archetype().id()); + assert_ne!(id1, id2); + assert_ne!(id1, id3); + assert_ne!(id1, id4); + + assert_ne!(id2, id3); + assert_eq!(id2, id4); + + world.entity_mut(entities[3]).insert(Fragmenting(1)); + let [id1, id4] = world + .entity([entities[0], entities[3]]) + .map(|e| e.archetype().id()); + assert_eq!(id1, id4); + } + + #[test] + fn fragment_dynamic() { + let mut world = World::default(); + + const COMPONENT_SIZE: usize = 10; + #[derive(Clone, PartialEq, Eq, Hash)] + struct DynamicComponent { + data: [u8; COMPONENT_SIZE], + } + + unsafe fn eq(this: Ptr<'_>, other: Ptr<'_>) -> bool { + this.deref::() == other.deref::() + } + + unsafe fn hash(this: Ptr<'_>) -> u64 { + FixedHasher.hash_one(this.deref::()) + } + + unsafe fn clone(this: Ptr<'_>, target: NonNull) { + target + .cast::() + .write(this.deref::().clone()); + } + + let layout = Layout::new::(); + // Safety: `clone` properly initializes value of type `DynamicComponent` + let vtable = unsafe { FragmentingValueVtable::new(eq, hash, clone) }; + + // SAFETY: + // - No drop command is required + // - The component is Send and Sync + // - vtable matches the component layout, mutable is false, storage type is SparseSet + let descriptor = unsafe { + ComponentDescriptor::new_with_layout( + "DynamicComp1", + StorageType::SparseSet, + layout, + None, + false, + ComponentCloneBehavior::Ignore, + Some(vtable), + ) + }; + let component_id1 = world.register_component_with_descriptor(descriptor); + // SAFETY: + // - No drop command is required + // - The component is Send and Sync + // - vtable matches the component layout, mutable is false, storage type is SparseSet + let descriptor = unsafe { + ComponentDescriptor::new_with_layout( + "DynamicComp2", + StorageType::SparseSet, + layout, + None, + false, + ComponentCloneBehavior::Ignore, + Some(vtable), + ) + }; + let component_id2 = world.register_component_with_descriptor(descriptor); + + let component1_1 = DynamicComponent { data: [5; 10] }; + let component1_2 = DynamicComponent { data: [8; 10] }; + let component2_1 = DynamicComponent { data: [5; 10] }; + + let entities: [Entity; 3] = (0..3) + .map(|_| world.spawn_empty().id()) + .collect::>() + .try_into() + .unwrap(); + // SAFETY: all ids and pointers match + unsafe { + world.entity_mut(entities[0]).insert_by_id( + component_id1, + OwningPtr::new(NonNull::from(&component1_1).cast()), + ); + world.entity_mut(entities[1]).insert_by_ids( + &[component_id1, component_id2], + [ + OwningPtr::new(NonNull::from(&component1_1).cast()), + OwningPtr::new(NonNull::from(&component2_1).cast()), + ] + .into_iter(), + ); + world.entity_mut(entities[2]).insert_by_id( + component_id1, + OwningPtr::new(NonNull::from(&component1_2).cast()), + ); + } + + let [id1, id2, id3] = world.entity(entities).map(|e| e.archetype().id()); + assert_ne!(id1, id2); + assert_ne!(id1, id3); + } + + #[test] + fn fragmenting_component_compare_with_dynamic() { + let mut world = World::default(); + + let component_id = world.register_component::(); + + let e1 = world.spawn(Fragmenting(1)).id(); + let e2 = world.spawn_empty().id(); + OwningPtr::make(Fragmenting(1), |ptr| + // SAFETY: + // - ComponentId is from the same world. + // - OwningPtr points to valid value of type represented by ComponentId + unsafe { + world.entity_mut(e2).insert_by_id(component_id, ptr); + }); + let e3 = world.spawn_empty().id(); + OwningPtr::make(Fragmenting(1), |ptr| + // SAFETY: + // - ComponentId is from the same world. + // - OwningPtr points to valid value of type represented by ComponentId + unsafe { + world + .entity_mut(e3) + .insert_by_ids(&[component_id], [ptr].into_iter()); + }); + + let e4 = world.spawn(Fragmenting(1)).id(); + let e5 = world.spawn_empty().id(); + OwningPtr::make(Fragmenting(1), |ptr| + // SAFETY: + // - ComponentId is from the same world. + // - OwningPtr points to valid value of type represented by ComponentId + unsafe { + world.entity_mut(e5).insert_by_id(component_id, ptr); + }); + let e6 = world.spawn_empty().id(); + OwningPtr::make(Fragmenting(1), |ptr| + // SAFETY: + // - ComponentId is from the same world. + // - OwningPtr points to valid value of type represented by ComponentId + unsafe { + world + .entity_mut(e6) + .insert_by_ids(&[component_id], [ptr].into_iter()); + }); + + let [id1, id2, id3, id4, id5, id6] = world + .entity([e1, e2, e3, e4, e5, e6]) + .map(|e| e.archetype().id()); + + assert_eq!(id1, id2); + assert_eq!(id1, id3); + assert_eq!(id1, id4); + assert_eq!(id1, id5); + assert_eq!(id1, id6); + } + + #[test] + fn fragmenting_value_edges_cache_does_not_reset() { + let mut world = World::default(); + + let bundle_id = world.register_bundle::().id(); + let get_keys = |world: &World, archetype_id: ArchetypeId| { + world.archetypes[archetype_id] + .edges() + .insert_bundle_fragmenting_components + .keys() + .filter(|k| k.0 == bundle_id) + .count() + }; + + let empty_archetype = world.spawn_empty().archetype().id(); + + // Added components path + let entity = world.spawn(Fragmenting(1)); + let fragmenting_archetype = entity.archetype().id(); + assert_eq!(get_keys(&world, empty_archetype), 1); + + world.spawn(Fragmenting(2)); + assert_eq!(get_keys(&world, empty_archetype), 2); + + // No new components path + let e1 = world.spawn(Fragmenting(1)).id(); + world.entity_mut(e1).insert(Fragmenting(2)); + assert_eq!(get_keys(&world, fragmenting_archetype), 1); + + world + .entity_mut(e1) + .insert(Fragmenting(1)) + .insert(Fragmenting(3)); + assert_eq!(get_keys(&world, fragmenting_archetype), 2); + } +} diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 0e222692d7fb1..c3255391c1816 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -15,8 +15,8 @@ use indexmap::IndexSet; use crate::{ archetype::ArchetypeFlags, component::{ - Component, ComponentCloneBehavior, ComponentMutability, QueuedComponents, - RequiredComponents, StorageType, + Component, ComponentCloneBehavior, ComponentMutability, FragmentingValueVtable, + QueuedComponents, RequiredComponents, StorageType, }, lifecycle::ComponentHooks, query::DebugCheckedUnwrap as _, @@ -140,6 +140,17 @@ impl ComponentInfo { pub fn required_components(&self) -> &RequiredComponents { &self.required_components } + + /// Returns this component's [`FragmentingValueVtable`], which is used to fragment by component value. + /// + /// Will return `None` if this component isn't fragmenting by value. + pub fn value_component_vtable(&self) -> Option<&FragmentingValueVtable> { + if !self.mutable() { + self.descriptor.fragmenting_value_vtable.as_ref() + } else { + None + } + } } /// A value which uniquely identifies the type of a [`Component`] or [`Resource`] within a @@ -219,6 +230,7 @@ pub struct ComponentDescriptor { drop: Option unsafe fn(OwningPtr<'a>)>, mutable: bool, clone_behavior: ComponentCloneBehavior, + fragmenting_value_vtable: Option, } // We need to ignore the `drop` field in our `Debug` impl @@ -232,6 +244,7 @@ impl Debug for ComponentDescriptor { .field("layout", &self.layout) .field("mutable", &self.mutable) .field("clone_behavior", &self.clone_behavior) + .field("fragmenting_value_vtable", &self.fragmenting_value_vtable) .finish() } } @@ -258,6 +271,7 @@ impl ComponentDescriptor { drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: T::Mutability::MUTABLE, clone_behavior: T::clone_behavior(), + fragmenting_value_vtable: FragmentingValueVtable::from_component::(), } } @@ -266,6 +280,7 @@ impl ComponentDescriptor { /// # Safety /// - the `drop` fn must be usable on a pointer with a value of the layout `layout` /// - the component type must be safe to access from any thread (Send + Sync in rust terms) + /// - if `fragmenting_value_vtable` is not `None`, it must be usable on a pointer with a value of the layout `layout` pub unsafe fn new_with_layout( name: impl Into>, storage_type: StorageType, @@ -273,6 +288,7 @@ impl ComponentDescriptor { drop: Option unsafe fn(OwningPtr<'a>)>, mutable: bool, clone_behavior: ComponentCloneBehavior, + fragmenting_value_vtable: Option, ) -> Self { Self { name: name.into().into(), @@ -283,6 +299,7 @@ impl ComponentDescriptor { drop, mutable, clone_behavior, + fragmenting_value_vtable, } } @@ -301,6 +318,7 @@ impl ComponentDescriptor { drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, clone_behavior: ComponentCloneBehavior::Default, + fragmenting_value_vtable: None, } } @@ -314,6 +332,7 @@ impl ComponentDescriptor { drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, clone_behavior: ComponentCloneBehavior::Default, + fragmenting_value_vtable: None, } } diff --git a/crates/bevy_ecs/src/component/mod.rs b/crates/bevy_ecs/src/component/mod.rs index 1d808f0e1d1ee..ed8eae728b835 100644 --- a/crates/bevy_ecs/src/component/mod.rs +++ b/crates/bevy_ecs/src/component/mod.rs @@ -1,12 +1,14 @@ //! Types for declaring and storing [`Component`]s. mod clone; +mod fragmenting_value; mod info; mod register; mod required; mod tick; pub use clone::*; +pub use fragmenting_value::*; pub use info::*; pub use register::*; pub use required::*; @@ -496,6 +498,17 @@ pub trait Component: Send + Sync + 'static { /// * For a component to be immutable, this type must be [`Immutable`]. type Mutability: ComponentMutability; + /// A component which value will be used to fragment the final archetype an entity + /// with this component will belong to. + /// Each unique value of Key component (as determined by [`PartialEq`] and [`Hash`] implementation) + /// will be stored separately. + /// + /// Setting this to `Self` will mark this component as fragmenting, while [`NoKey`] is the + /// default `Key` type for non-value fragmenting components. + /// Using any other components here is currently equivalent to [`NoKey`], but might do + /// something different in the future. + type Key: FragmentingValueComponent; + /// Gets the `on_add` [`ComponentHook`] for this [`Component`] if one is defined. fn on_add() -> Option { None diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 4c110d0057c9a..d4e78e39b17e7 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -2091,6 +2091,7 @@ mod tests { // SAFETY: // - No drop command is required // - The component will store [u8; COMPONENT_SIZE], which is Send + Sync + // - fragmenting_value_vtable is None let descriptor = unsafe { ComponentDescriptor::new_with_layout( "DynamicComp", @@ -2099,6 +2100,7 @@ mod tests { None, true, ComponentCloneBehavior::Custom(test_handler), + None, ) }; let component_id = world.register_component_with_descriptor(descriptor); diff --git a/crates/bevy_ecs/src/observer/distributed_storage.rs b/crates/bevy_ecs/src/observer/distributed_storage.rs index 3de8b486b880f..c653b672359fb 100644 --- a/crates/bevy_ecs/src/observer/distributed_storage.rs +++ b/crates/bevy_ecs/src/observer/distributed_storage.rs @@ -13,7 +13,8 @@ use core::any::Any; use crate::{ component::{ - ComponentCloneBehavior, ComponentId, Mutable, RequiredComponentsRegistrator, StorageType, + ComponentCloneBehavior, ComponentId, Mutable, NoKey, RequiredComponentsRegistrator, + StorageType, }, entity::Entity, entity_disabling::Internal, @@ -336,6 +337,7 @@ impl Observer { impl Component for Observer { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; + type Key = NoKey; fn on_add() -> Option { Some(|world, context| { let Some(observe) = world.get::(context.entity) else { @@ -471,6 +473,7 @@ impl ObservedBy { impl Component for ObservedBy { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; + type Key = NoKey; fn on_remove() -> Option { Some(|mut world, HookContext { entity, .. }| { diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index 85f2f4cea01ba..3a8cc391aeb43 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -311,8 +311,20 @@ unsafe impl + Send + Sync + 'static> Bundle ) { ::get_component_ids(components, ids); } -} + fn get_fragmenting_values<'a>( + &'a self, + _components: &crate::component::Components, + _values: &mut impl FnMut(crate::component::FragmentingValueBorrowed<'a>), + ) { + // RelationshipTargets can't fragment by value since they're mutable + } + + fn count_fragmenting_values() -> usize { + // RelationshipTargets can't fragment by value since they're mutable + 0 + } +} impl> DynamicBundle for SpawnRelatedBundle { type Effect = Self; @@ -403,6 +415,19 @@ unsafe impl Bundle for SpawnOneRelated { ) { ::get_component_ids(components, ids); } + + fn get_fragmenting_values<'a>( + &'a self, + _components: &crate::component::Components, + _values: &mut impl FnMut(crate::component::FragmentingValueBorrowed<'a>), + ) { + // RelationshipTargets can't fragment by value since they're mutable + } + + fn count_fragmenting_values() -> usize { + // RelationshipTargets can't fragment by value since they're mutable + 0 + } } /// [`RelationshipTarget`] methods that create a [`Bundle`] with a [`DynamicBundle::Effect`] that: diff --git a/crates/bevy_ecs/src/storage/fragmenting_value.rs b/crates/bevy_ecs/src/storage/fragmenting_value.rs new file mode 100644 index 0000000000000..1190d4257244f --- /dev/null +++ b/crates/bevy_ecs/src/storage/fragmenting_value.rs @@ -0,0 +1,17 @@ +use bevy_platform::collections::HashSet; + +use crate::component::{CheckChangeTicks, FragmentingValue}; + +/// Stores each unique combination of component type + value for [`FragmentingValueComponent`]s as +/// untyped [`FragmentingValue`]s. +/// +/// [`FragmentingValueComponent`]: crate::component::FragmentingValueComponent +// TODO: make this more useful and add a public api. +#[derive(Default)] +pub struct FragmentingValuesStorage { + pub(crate) existing_values: HashSet, +} + +impl FragmentingValuesStorage { + pub(crate) fn check_change_ticks(&mut self, _check: CheckChangeTicks) {} +} diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 2a5a5f184e649..e8a495c5b4489 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -22,11 +22,13 @@ mod blob_array; mod blob_vec; +mod fragmenting_value; mod resource; mod sparse_set; mod table; mod thin_array_ptr; +pub use fragmenting_value::*; pub use resource::*; pub use sparse_set::*; pub use table::*; @@ -45,6 +47,8 @@ pub struct Storages { pub resources: Resources, /// Backing storage for `!Send` resources. pub non_send_resources: Resources, + /// Backing storage for fragmenting value components. + pub fragmenting_values: FragmentingValuesStorage, } impl Storages { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 15b61afb7ee49..a480cc7cc5a7d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,10 @@ use crate::{ Bundle, BundleFromComponents, BundleInserter, BundleRemover, DynamicBundle, InsertMode, }, change_detection::{MaybeLocation, MutUntyped}, - component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick}, + component::{ + Component, ComponentId, ComponentTicks, Components, FragmentingValuesBorrowed, Mutable, + StorageType, Tick, + }, entity::{ ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent, EntityIdLocation, EntityLocation, OptIn, OptOut, @@ -2036,7 +2039,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::(self.world, location.archetype_id, change_tick, &*bundle); // SAFETY: // - `location` matches current entity and thus must currently exist in the source // archetype for this inserter and its location within the archetype. @@ -2115,9 +2118,22 @@ impl<'w> EntityWorldMut<'w> { component_id, ); let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); + // Safety: pointers are valid references to data represented by the corresponding + // `ComponentId` + let value_components = unsafe { + FragmentingValuesBorrowed::from_components( + &self.world.components, + [(component_id, component.as_ref())], + ) + }; - let bundle_inserter = - BundleInserter::new_with_id(self.world, location.archetype_id, bundle_id, change_tick); + let bundle_inserter = BundleInserter::new_with_id( + self.world, + location.archetype_id, + bundle_id, + change_tick, + &value_components, + ); self.location = Some(insert_dynamic_bundle( bundle_inserter, @@ -2175,14 +2191,32 @@ impl<'w> EntityWorldMut<'w> { ); let mut storage_types = core::mem::take(self.world.bundles.get_storages_unchecked(bundle_id)); - let bundle_inserter = - BundleInserter::new_with_id(self.world, location.archetype_id, bundle_id, change_tick); + let mut components = Vec::with_capacity(component_ids.len()); + components.extend(iter_components); + // Safety: pointers are valid references to data represented by the corresponding + // `ComponentId` + let value_components = unsafe { + FragmentingValuesBorrowed::from_components( + &self.world.components, + component_ids + .iter() + .copied() + .zip(components.iter().map(|ptr| ptr.as_ref())), + ) + }; + let bundle_inserter = BundleInserter::new_with_id( + self.world, + location.archetype_id, + bundle_id, + change_tick, + &value_components, + ); self.location = Some(insert_dynamic_bundle( bundle_inserter, self.entity, location, - iter_components, + components.into_iter(), (*storage_types).iter().cloned(), InsertMode::Replace, MaybeLocation::caller(), diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ccee6ff8d5be7..4adaaff6b9d57 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -38,8 +38,8 @@ use crate::{ change_detection::{MaybeLocation, MutUntyped, TicksMut}, component::{ CheckChangeTicks, Component, ComponentDescriptor, ComponentId, ComponentIds, ComponentInfo, - ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable, - RequiredComponents, RequiredComponentsError, Tick, + ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, + FragmentingValuesBorrowed, Mutable, RequiredComponents, RequiredComponentsError, Tick, }, entity::{Entities, Entity, EntityDoesNotExistError}, entity_disabling::DefaultQueryFilters, @@ -1164,7 +1164,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(self, change_tick, &*bundle); let (bundle, entity_location) = bundle.partial_move(|bundle| { // SAFETY: // - `B` matches `bundle_spawner`'s type @@ -2315,6 +2315,8 @@ impl World { if let Some((first_entity, first_bundle)) = batch_iter.next() { if let Some(first_location) = self.entities().get(first_entity) { + let value_components = + FragmentingValuesBorrowed::from_bundle(self.components(), &first_bundle); let mut cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` inserter: unsafe { @@ -2323,6 +2325,7 @@ impl World { first_location.archetype_id, bundle_id, change_tick, + &value_components, ) }, archetype_id: first_location.archetype_id, @@ -2346,7 +2349,11 @@ impl World { for (entity, bundle) in batch_iter { if let Some(location) = cache.inserter.entities().get(entity) { - if location.archetype_id != cache.archetype_id { + if B::count_fragmenting_values() > 0 + || location.archetype_id != cache.archetype_id + { + let value_components = + FragmentingValuesBorrowed::from_bundle(self.components(), &bundle); cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` inserter: unsafe { @@ -2355,6 +2362,7 @@ impl World { location.archetype_id, bundle_id, change_tick, + &value_components, ) }, archetype_id: location.archetype_id, @@ -2470,6 +2478,8 @@ impl World { let cache = loop { if let Some((first_entity, first_bundle)) = batch_iter.next() { if let Some(first_location) = self.entities().get(first_entity) { + let value_components = + FragmentingValuesBorrowed::from_bundle(self.components(), &first_bundle); let mut cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_bundle_info` inserter: unsafe { @@ -2478,6 +2488,7 @@ impl World { first_location.archetype_id, bundle_id, change_tick, + &value_components, ) }, archetype_id: first_location.archetype_id, @@ -2510,7 +2521,11 @@ impl World { if let Some(mut cache) = cache { for (entity, bundle) in batch_iter { if let Some(location) = cache.inserter.entities().get(entity) { - if location.archetype_id != cache.archetype_id { + if B::count_fragmenting_values() > 0 + || location.archetype_id != cache.archetype_id + { + let value_components = + FragmentingValuesBorrowed::from_bundle(self.components(), &bundle); cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` inserter: unsafe { @@ -2519,6 +2534,7 @@ impl World { location.archetype_id, bundle_id, change_tick, + &value_components, ) }, archetype_id: location.archetype_id, @@ -3030,6 +3046,7 @@ impl World { ref mut sparse_sets, ref mut resources, ref mut non_send_resources, + ref mut fragmenting_values, } = self.storages; #[cfg(feature = "trace")] @@ -3038,6 +3055,7 @@ impl World { sparse_sets.check_change_ticks(check); resources.check_change_ticks(check); non_send_resources.check_change_ticks(check); + fragmenting_values.check_change_ticks(check); self.entities.check_change_ticks(check); if let Some(mut schedules) = self.get_resource_mut::() { @@ -3989,6 +4007,7 @@ mod tests { let mut world = World::new(); // SAFETY: the drop function is valid for the layout and the data will be safe to access from any thread + // and fragmenting_value_vtable is None let descriptor = unsafe { ComponentDescriptor::new_with_layout( "Custom Test Component".to_string(), @@ -4001,6 +4020,7 @@ mod tests { }), true, ComponentCloneBehavior::Default, + None, ) }; diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 9f9c87ee572b9..0043c3016dc05 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -6,7 +6,7 @@ use crate::{ entity::{Entity, EntitySetIterator}, world::World, }; -use core::iter::FusedIterator; +use core::iter::{FusedIterator, Peekable}; /// An iterator that spawns a series of entities and returns the [ID](Entity) of /// each spawned entity. @@ -17,8 +17,8 @@ where I: Iterator, I::Item: Bundle, { - inner: I, - spawner: BundleSpawner<'w>, + inner: Peekable, + spawner: Option>, caller: MaybeLocation, } @@ -34,17 +34,24 @@ where // necessary world.flush(); - let change_tick = world.change_tick(); - let (lower, upper) = iter.size_hint(); let length = upper.unwrap_or(lower); world.entities.reserve(length as u32); + let change_tick = world.change_tick(); + let mut inner = iter.peekable(); - let mut spawner = BundleSpawner::new::(world, change_tick); - spawner.reserve_storage(length); + let spawner = if let Some(bundle) = inner.peek() { + let mut spawner = BundleSpawner::new::(world, change_tick, bundle); + if I::Item::count_fragmenting_values() == 0 { + spawner.reserve_storage(length); + } + Some(spawner) + } else { + None + }; Self { - inner: iter, + inner, spawner, caller, } @@ -60,8 +67,11 @@ where // Iterate through self in order to spawn remaining bundles. for _ in &mut *self {} // Apply any commands from those operations. - // SAFETY: `self.spawner` will be dropped immediately after this call. - unsafe { self.spawner.flush_commands() }; + + if let Some(spawner) = &mut self.spawner { + // SAFETY: `self.spawner` will be dropped immediately after this call. + unsafe { spawner.flush_commands() } + } } } @@ -74,12 +84,19 @@ where fn next(&mut self) -> Option { let bundle = self.inner.next()?; + + let spawner = self.spawner.as_mut().unwrap(); + + if I::Item::count_fragmenting_values() > 0 { + spawner.replace(&bundle); + } + move_as_ptr!(bundle); // SAFETY: // - The spawner matches `I::Item`'s type. // - `I::Item::Effect: NoBundleEffect`, thus [`apply_effect`] does not need to be called. // - `bundle` is not accessed or dropped after this function call. - unsafe { Some(self.spawner.spawn::(bundle, self.caller)) } + unsafe { Some(spawner.spawn::(bundle, self.caller)) } } fn size_hint(&self) -> (usize, Option) { diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 5fb6bc1bf6314..3121a87246fdf 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -14,7 +14,7 @@ //! between components (like hierarchies or parent-child links) and need to maintain correctness. use bevy::{ - ecs::component::{Mutable, StorageType}, + ecs::component::{Mutable, NoKey, StorageType}, ecs::lifecycle::{ComponentHook, HookContext}, prelude::*, }; @@ -32,6 +32,7 @@ struct MyComponent(KeyCode); impl Component for MyComponent { const STORAGE_TYPE: StorageType = StorageType::Table; type Mutability = Mutable; + type Key = NoKey; /// Hooks can also be registered during component initialization by /// implementing the associated method diff --git a/examples/ecs/dynamic.rs b/examples/ecs/dynamic.rs index 35eb8a8ea6c00..9c8ca5d17abc3 100644 --- a/examples/ecs/dynamic.rs +++ b/examples/ecs/dynamic.rs @@ -88,7 +88,9 @@ fn main() { _ => 0, }; // Register our new component to the world with a layout specified by it's size - // SAFETY: [u64] is Send + Sync + // SAFETY: + // - [u64] is Send + Sync + // - fragmenting_value_vtable is None let id = world.register_component_with_descriptor(unsafe { ComponentDescriptor::new_with_layout( name.to_string(), @@ -97,6 +99,7 @@ fn main() { None, true, ComponentCloneBehavior::Default, + None, ) }); let Some(info) = world.components().get_info(id) else { diff --git a/examples/ecs/immutable_components.rs b/examples/ecs/immutable_components.rs index c0ee241923931..e6839cbdb6a34 100644 --- a/examples/ecs/immutable_components.rs +++ b/examples/ecs/immutable_components.rs @@ -146,6 +146,7 @@ fn demo_3(world: &mut World) { // SAFETY: // - No drop command is required // - The component will store [u8; size], which is Send + Sync + // - fragmenting_value_vtable is None let descriptor = unsafe { ComponentDescriptor::new_with_layout( name.to_string(), @@ -154,6 +155,7 @@ fn demo_3(world: &mut World) { None, false, ComponentCloneBehavior::Default, + None, ) }; diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs index 0250f6e3c1fc2..ccbaccf9a6801 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -89,6 +89,7 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { // SAFETY: // * We don't implement a drop function // * u8 is Sync and Send + // * fragmenting_value_vtable is None unsafe { ComponentDescriptor::new_with_layout( format!("Component{i}").to_string(), @@ -97,6 +98,7 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { None, true, // is mutable ComponentCloneBehavior::Default, + None, ) }, ) diff --git a/release-content/migration-guides/fragmenting_value_components.md b/release-content/migration-guides/fragmenting_value_components.md new file mode 100644 index 0000000000000..6bb420a2849ae --- /dev/null +++ b/release-content/migration-guides/fragmenting_value_components.md @@ -0,0 +1,13 @@ +--- +title: Added support for fragmenting value components +pull_requests: [19153] +--- + +Archetypes can now be fragmented by component values. Supporting this required some changes to the public api of archetypes and a new associated type for components. + +Manual impl of `Component` trait now has a new associated type, `Key`, that should be set to `NoKey` for all existing components. + +`ComponentDescriptor::new` and `ComponentDescriptor::new_with_layout` now require an additional argument - `fragmenting_value_vtable: Option`. It should be set to `None` for all existing implementations. + +`Edges::get_archetype_after_bundle_insert` now require an additional argument - `value_components: &FragmentingValuesBorrowed`. These can be +constructed using `FragmentingValuesBorrowed::from_bundle` or `FragmentingValuesBorrowed::from_components`.