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..70c9d652a6db3 --- /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(storage = "Shared")] +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::<1>(2)); + } + + 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/benches/benches/bevy_ecs/iteration/iter_simple_shared.rs b/benches/benches/bevy_ecs/iteration/iter_simple_shared.rs new file mode 100644 index 0000000000000..b96df7cba97a4 --- /dev/null +++ b/benches/benches/bevy_ecs/iteration/iter_simple_shared.rs @@ -0,0 +1,51 @@ +use bevy_ecs::prelude::*; +use glam::*; + +#[derive(Component, Copy, Clone)] +struct Transform(Mat4); + +#[derive(Component, Copy, Clone)] +#[component(storage = "SparseSet")] +struct Position(Vec3); + +#[derive(Component, Copy, Clone)] +struct Rotation(Vec3); + +#[derive(Component, Copy, Clone)] +#[component(storage = "SparseSet")] +struct Velocity(Vec3); + +#[derive(Component, Copy, Clone, Eq, PartialEq, Hash)] +#[component(storage = "Shared")] +struct VelocityModifier(u32); + +pub struct Benchmark<'w>( + World, + QueryState<(&'w Velocity, &'w mut Position, &'w VelocityModifier)>, +); + +impl<'w> Benchmark<'w> { + pub fn new() -> Self { + let mut world = World::new(); + + world.spawn_batch((0..10_000).map(|i| { + ( + Transform(Mat4::from_scale(Vec3::ONE)), + Position(Vec3::X), + Rotation(Vec3::X), + Velocity(Vec3::X), + VelocityModifier(i / 100), + ) + })); + + let query = world.query::<(&Velocity, &mut Position, &VelocityModifier)>(); + Self(world, query) + } + + #[inline(never)] + pub fn run(&mut self) { + for (velocity, mut position, modifier) in self.1.iter_mut(&mut self.0) { + position.0 += velocity.0 * (modifier.0 as f32); + } + } +} diff --git a/benches/benches/bevy_ecs/iteration/mod.rs b/benches/benches/bevy_ecs/iteration/mod.rs index 0fa7aced2894a..e496d37e4ec51 100644 --- a/benches/benches/bevy_ecs/iteration/mod.rs +++ b/benches/benches/bevy_ecs/iteration/mod.rs @@ -13,6 +13,7 @@ mod iter_simple_foreach_hybrid; mod iter_simple_foreach_sparse_set; mod iter_simple_foreach_wide; mod iter_simple_foreach_wide_sparse_set; +mod iter_simple_shared; mod iter_simple_sparse_set; mod iter_simple_system; mod iter_simple_wide; @@ -48,6 +49,10 @@ fn iter_simple(c: &mut Criterion) { let mut bench = iter_simple_system::Benchmark::new(); b.iter(move || bench.run()); }); + group.bench_function("shared", |b| { + let mut bench = iter_simple_shared::Benchmark::new(); + b.iter(move || bench.run()); + }); group.bench_function("sparse_set", |b| { let mut bench = iter_simple_sparse_set::Benchmark::new(); b.iter(move || bench.run()); diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 3ae95d71ccf12..231a3fab59c7e 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -64,6 +64,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 00268cb680050..a3ed60829d118 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -245,9 +245,10 @@ pub fn derive_component(input: TokenStream) -> TokenStream { } }); - let mutable_type = (attrs.immutable || relationship.is_some()) - .then_some(quote! { #bevy_ecs_path::component::Immutable }) - .unwrap_or(quote! { #bevy_ecs_path::component::Mutable }); + let mutable_type = + (attrs.immutable || relationship.is_some() || matches!(attrs.storage, StorageTy::Shared)) + .then_some(quote! { #bevy_ecs_path::component::Immutable }) + .unwrap_or(quote! { #bevy_ecs_path::component::Mutable }); let clone_behavior = if relationship_target.is_some() { quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::)) @@ -260,6 +261,14 @@ pub fn derive_component(input: TokenStream) -> TokenStream { ) }; + let key = if let Some(key) = attrs.key { + quote! {#bevy_ecs_path::component::OtherComponentKey} + } else if let StorageTy::Shared = attrs.storage { + quote! {#bevy_ecs_path::component::SelfKey} + } 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! { @@ -267,6 +276,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, components: &mut #bevy_ecs_path::component::ComponentsRegistrator, @@ -399,6 +409,7 @@ pub const ON_DESPAWN: &str = "on_despawn"; 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 #[derive(Debug)] @@ -462,12 +473,14 @@ struct Attrs { relationship_target: Option, immutable: bool, clone_behavior: Option, + key: Option, } #[derive(Clone, Copy)] enum StorageTy { Table, SparseSet, + Shared, } struct Require { @@ -487,6 +500,7 @@ struct RelationshipTarget { // values for `storage` attribute const TABLE: &str = "Table"; const SPARSE_SET: &str = "SparseSet"; +const SHARED: &str = "Shared"; fn parse_component_attr(ast: &DeriveInput) -> Result { let mut attrs = Attrs { @@ -501,6 +515,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { relationship_target: None, immutable: false, clone_behavior: None, + key: None, }; let mut require_paths = HashSet::new(); @@ -511,9 +526,10 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { attrs.storage = match nested.value()?.parse::()?.value() { s if s == TABLE => StorageTy::Table, s if s == SPARSE_SET => StorageTy::SparseSet, + s if s == SHARED => StorageTy::Shared, s => { return Err(nested.error(format!( - "Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.", + "Invalid storage type `{s}`, expected '{TABLE}', '{SPARSE_SET}' or '{SHARED}'.", ))); } }; @@ -539,6 +555,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else if nested.path.is_ident(CLONE_BEHAVIOR) { attrs.clone_behavior = Some(nested.value()?.parse()?); Ok(()) + } else if nested.path.is_ident(KEY) { + attrs.key = Some(nested.value()?.parse()?); + Ok(()) } else { Err(nested.error("Unsupported attribute")) } @@ -646,6 +665,7 @@ fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 { let storage_type = match ty { StorageTy::Table => Ident::new("Table", Span::call_site()), StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()), + StorageTy::Shared => Ident::new("Shared", Span::call_site()), }; quote! { #bevy_ecs_path::component::StorageType::#storage_type } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 114aff642b58c..13c0192edb130 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -77,6 +77,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { 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_get_fragmenting_values = Vec::new(); + let mut field_has_fragmenting_values = Vec::new(); let mut field_from_components = Vec::new(); let mut field_required_components = Vec::new(); for (((i, field_type), field_kind), field) in field_type @@ -96,6 +98,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { field_get_component_ids.push(quote! { <#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids); }); + field_has_fragmenting_values.push(quote! { + <#field_type as #ecs_path::bundle::Bundle>::has_fragmenting_values() + }); match field { Some(field) => { field_get_components.push(quote! { @@ -104,6 +109,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { field_from_components.push(quote! { #field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), }); + field_get_fragmenting_values.push(quote! { + self.#field.get_fragmenting_values(components, &mut *values); + }); } None => { let index = Index::from(i); @@ -113,6 +121,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { field_from_components.push(quote! { #index: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func), }); + field_get_fragmenting_values.push(quote! { + self.#index.get_fragmenting_values(components, &mut *values); + }); } } } @@ -155,6 +166,21 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { ){ #(#field_required_components)* } + + #[allow(unused_variables)] + #[inline] + fn get_fragmenting_values<'a>( + &'a self, + components: &mut #ecs_path::component::ComponentsRegistrator, + values: &mut impl FnMut(#ecs_path::component::ComponentId, &'a dyn #ecs_path::fragmenting_value::FragmentingValue) + ) { + #(#field_get_fragmenting_values)* + } + + #[inline(always)] + fn has_fragmenting_values() -> bool { + false #(|| #field_has_fragmenting_values)* + } } // SAFETY: diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index f12cd03a69dbd..ff1a5050936fa 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -23,8 +23,11 @@ use crate::{ bundle::BundleId, component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, entity::{Entity, EntityLocation}, + fragmenting_value::{FragmentingValuesBorrowed, FragmentingValuesShared}, observer::Observers, - storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow}, + storage::{ + ImmutableSparseSet, SharedFragmentingValue, SparseArray, SparseSet, TableId, TableRow, + }, }; use alloc::{boxed::Box, vec::Vec}; use bevy_platform::collections::HashMap; @@ -142,6 +145,11 @@ pub(crate) struct ArchetypeAfterBundleInsert { /// The components that were explicitly contributed by this bundle, but already existed in the archetype. This _does not_ include any /// Required Components. pub existing: Vec, + /// Maps this archetype to other component-identical archetypes based on [`FragmentingValue`](crate::fragmenting_value::FragmentingValue)s of components. + /// All [`Archetype`]s this maps to differ only by their identity due to different [`FragmentingValuesShared`], 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 fragmenting_values_map: HashMap, } impl ArchetypeAfterBundleInsert { @@ -216,9 +224,19 @@ impl Edges { /// If this returns `None`, it means there has not been a transition from /// the source archetype via the provided bundle. #[inline] - pub fn get_archetype_after_bundle_insert(&self, bundle_id: BundleId) -> Option { + pub 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| { + if value_components.is_empty() { + Some(bundle.archetype_id) + } else { + bundle.fragmenting_values_map.get(value_components).copied() + } + }) } /// Internal version of `get_archetype_after_bundle_insert` that @@ -232,6 +250,9 @@ impl Edges { } /// Caches the target archetype when inserting a bundle into the source archetype. + /// + /// **NOTE**: to get the proper final archetype id use [`ArchetypeAfterBundleInsert::fragmenting_values_map`] + /// if there are any fragmenting value components present in the bundle. #[inline] pub(crate) fn cache_archetype_after_bundle_insert( &mut self, @@ -250,10 +271,28 @@ impl Edges { required_components, added, existing, + fragmenting_values_map: Default::default(), }, ); } + /// Caches the target archetype for this combination of [`BundleId`] and [`FragmentingValuesShared`]. + /// + /// For this to work, first cache the target archetype for this `bundle_id` without any fragmenting values + /// using [`Self::cache_archetype_after_bundle_insert`] if not already present, and then run this function. + pub(crate) fn cache_archetype_value_components_after_bundle_insert( + &mut self, + bundle_id: BundleId, + value_components: FragmentingValuesShared, + value_archetype_id: ArchetypeId, + ) { + if let Some(bundle) = self.insert_bundle.get_mut(bundle_id) { + bundle + .fragmenting_values_map + .insert(value_components, value_archetype_id); + } + } + /// Checks the cache for the target archetype when removing a bundle from the /// source archetype. /// @@ -349,6 +388,7 @@ pub(crate) struct ArchetypeSwapRemoveResult { /// [`Component`]: crate::component::Component struct ArchetypeComponentInfo { storage_type: StorageType, + fragmenting_value: Option, } bitflags::bitflags! { @@ -367,6 +407,7 @@ bitflags::bitflags! { const ON_REPLACE_OBSERVER = (1 << 7); const ON_REMOVE_OBSERVER = (1 << 8); const ON_DESPAWN_OBSERVER = (1 << 9); + const HAS_VALUE_COMPONENTS = (1 << 10); } } @@ -395,6 +436,7 @@ impl Archetype { table_id: TableId, table_components: impl Iterator, sparse_set_components: impl Iterator, + value_components: &FragmentingValuesShared, ) -> Self { let (min_table, _) = table_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint(); @@ -409,6 +451,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 @@ -429,6 +472,7 @@ impl Archetype { component_id, ArchetypeComponentInfo { storage_type: StorageType::SparseSet, + fragmenting_value: None, }, ); component_index @@ -436,6 +480,26 @@ impl Archetype { .or_default() .insert(id, ArchetypeRecord { column: None }); } + + for (component_id, value) in value_components.iter_ids_and_values() { + if let Some(info) = archetype_components.get_mut(component_id) { + info.fragmenting_value = Some(value.clone()); + } else { + archetype_components.insert( + component_id, + ArchetypeComponentInfo { + storage_type: StorageType::Shared, + fragmenting_value: Some(value.clone()), + }, + ); + component_index + .entry(component_id) + .or_default() + .insert(id, ArchetypeRecord { column: None }); + } + flags.insert(ArchetypeFlags::HAS_VALUE_COMPONENTS); + } + Self { id, table_id, @@ -519,6 +583,19 @@ impl Archetype { .map(|(id, _)| *id) } + /// Gets an iterator of all of the components stored in [`Shared`]. + /// + /// All of the IDs are unique. + /// + /// [`Shared`]: crate::storage::Shared + #[inline] + pub fn shared_components(&self) -> impl Iterator + '_ { + self.components + .iter() + .filter(|(_, component)| component.storage_type == StorageType::Shared) + .map(|(id, _)| *id) + } + /// Gets an iterator of all of the components in the archetype. /// /// All of the IDs are unique. @@ -533,6 +610,14 @@ impl Archetype { self.components.len() } + pub(crate) fn components_with_fragmenting_values( + &self, + ) -> impl Iterator { + self.components + .iter() + .filter_map(|(id, info)| Some((*id, info.fragmenting_value.as_ref()?))) + } + /// Fetches an immutable reference to the archetype's [`Edges`], a cache of /// archetypal relationships. #[inline] @@ -651,6 +736,23 @@ impl Archetype { .map(|info| info.storage_type) } + /// Returns [`FragmentingValue`](crate::fragmenting_value::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_value_component( + &self, + component_id: ComponentId, + ) -> Option<&SharedFragmentingValue> { + 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_values(&self) -> bool { + self.flags().contains(ArchetypeFlags::HAS_VALUE_COMPONENTS) + } + /// Clears all entities from the archetype. pub(crate) fn clear_entities(&mut self) { self.entities.clear(); @@ -746,6 +848,7 @@ impl ArchetypeGeneration { struct ArchetypeComponents { table_components: Box<[ComponentId]>, sparse_set_components: Box<[ComponentId]>, + value_components: FragmentingValuesShared, } /// Maps a [`ComponentId`] to the list of [`Archetypes`]([`Archetype`]) that contain the [`Component`](crate::component::Component), @@ -792,6 +895,7 @@ impl Archetypes { TableId::empty(), Vec::new(), Vec::new(), + Default::default(), ); } archetypes @@ -881,10 +985,12 @@ impl Archetypes { table_id: TableId, table_components: Vec, sparse_set_components: Vec, + value_components: FragmentingValuesShared, ) -> ArchetypeId { 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; @@ -896,6 +1002,7 @@ impl Archetypes { let ArchetypeComponents { table_components, sparse_set_components, + value_components, } = identity; let id = ArchetypeId::new(archetypes.len()); archetypes.push(Archetype::new( @@ -906,6 +1013,7 @@ impl Archetypes { table_id, table_components.iter().copied(), sparse_set_components.iter().copied(), + value_components, )); id }) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index df5c51eef1b86..63936cd814113 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -11,10 +11,11 @@ use crate::{ }, change_detection::MaybeLocation, component::{ - Component, ComponentId, Components, ComponentsRegistrator, RequiredComponentConstructor, - RequiredComponents, StorageType, Tick, + Component, ComponentId, ComponentKey, Components, ComponentsRegistrator, + RequiredComponentConstructor, RequiredComponents, StorageType, Tick, }, entity::{Entities, Entity, EntityLocation}, + fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed}, observer::Observers, prelude::World, query::DebugCheckedUnwrap, @@ -29,7 +30,10 @@ use alloc::{boxed::Box, vec, vec::Vec}; use bevy_platform::collections::{HashMap, HashSet}; use bevy_ptr::{ConstNonNull, OwningPtr}; use bevy_utils::TypeIdMap; -use core::{any::TypeId, ptr::NonNull}; +use core::{ + any::{Any, TypeId}, + ptr::NonNull, +}; use variadics_please::all_tuples; /// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity. @@ -164,6 +168,16 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { _components: &mut ComponentsRegistrator, _required_components: &mut RequiredComponents, ); + + /// Returns all component values that can fragment the archetype. + fn get_fragmenting_values<'a>( + &'a self, + components: &mut ComponentsRegistrator, + values: &mut impl FnMut(ComponentId, &'a dyn FragmentingValue), + ); + + /// Returns `true` if this bundle contains any components that can fragment the archetype by value. + fn has_fragmenting_values() -> bool; } /// Creates a [`Bundle`] by taking it from internal storage. @@ -247,6 +261,22 @@ 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: &mut ComponentsRegistrator, + values: &mut impl FnMut(ComponentId, &'a dyn FragmentingValue), + ) { + if let Some(key) = (self as &dyn Any).downcast_ref::<::KeyType>() { + C::component_ids(components, &mut |id| values(id, key)); + } + } + + #[inline(always)] + fn has_fragmenting_values() -> bool { + C::is_fragmenting_value_component() + } } // SAFETY: @@ -305,6 +335,23 @@ macro_rules! tuple_impl { ) { $(<$name as Bundle>::register_required_components(components, required_components);)* } + + fn get_fragmenting_values<'a>(&'a self, components: &mut ComponentsRegistrator, values: &mut impl FnMut(ComponentId, &'a dyn FragmentingValue)) { + #[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 has_fragmenting_values() -> bool { + false $(|| <$name as Bundle>::has_fragmenting_values())* + } } #[expect( @@ -619,6 +666,7 @@ impl BundleInfo { &self, table: &mut Table, sparse_sets: &mut SparseSets, + components: &Components, bundle_component_status: &S, required_components: impl Iterator, entity: Entity, @@ -671,6 +719,16 @@ impl BundleInfo { } } } + StorageType::Shared => { + // Shared components are inserted earlier during archetype creation by cloning when necessary + // and so we can safely drop them here. + if let Some(drop_fn) = components + .get_info(component_id) + .and_then(|info| info.drop()) + { + drop_fn(component_ptr); + } + } } bundle_component += 1; }); @@ -728,6 +786,9 @@ impl BundleInfo { unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() }; sparse_set.insert(entity, component_ptr, change_tick, caller); } + StorageType::Shared => { + // Nothing needs to be done since Shared components are stored on the archetype + } } } } @@ -747,14 +808,24 @@ impl BundleInfo { components: &Components, observers: &Observers, archetype_id: ArchetypeId, + value_components: &FragmentingValuesBorrowed, + current_tick: Tick, + caller: MaybeLocation, ) -> ArchetypeId { 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; } + let base_archetype_cached = archetypes[archetype_id] + .edges() + .get_archetype_after_bundle_insert_internal(self.id) + .map(|bundle| bundle.archetype_id); + let mut new_table_components = Vec::new(); + let mut replaced_value_components = HashMap::new(); + let mut new_value_components = Vec::new(); let mut new_sparse_set_components = Vec::new(); let mut bundle_status = Vec::with_capacity(self.explicit_components_len); let mut added_required_components = Vec::new(); @@ -774,6 +845,8 @@ impl BundleInfo { match component_info.storage_type() { StorageType::Table => new_table_components.push(component_id), StorageType::SparseSet => new_sparse_set_components.push(component_id), + // Shared components are always value components, so there is no reason to store them multiple times + StorageType::Shared => (), } } } @@ -791,21 +864,45 @@ impl BundleInfo { StorageType::SparseSet => { new_sparse_set_components.push(component_id); } + // Shared components are always value components, so there is no reason to store them multiple times + StorageType::Shared => (), } } } - if new_table_components.is_empty() && new_sparse_set_components.is_empty() { - let edges = current_archetype.edges_mut(); + for (component_id, component_value) in value_components.iter_ids_and_values() { + if let Some(old_value) = current_archetype.get_value_component(component_id) { + if old_value.as_ref() != component_value { + replaced_value_components.insert(component_id, component_value); + } + } else { + new_value_components.push(( + component_id, + storages + .shared + .get_or_insert(current_tick, component_id, component_value, caller) + .value() + .clone(), + )); + } + } + + let base_archetype_id = if let Some(base_archetype_id) = base_archetype_cached { + // This path will be taken if bundle is already cached for the base archetype, but some value components are not. + base_archetype_id + } else if new_table_components.is_empty() && new_sparse_set_components.is_empty() { // The archetype does not change when we insert this bundle. - edges.cache_archetype_after_bundle_insert( - self.id, - archetype_id, - bundle_status, - added_required_components, - added, - existing, - ); + current_archetype + .edges_mut() + .cache_archetype_after_bundle_insert( + self.id, + archetype_id, + bundle_status, + added_required_components, + added, + existing, + ); + archetype_id } else { let table_id; @@ -841,26 +938,75 @@ impl BundleInfo { new_sparse_set_components }; }; + // This crates (or retrieves) "base" archetype (without value components). It should always exist for edges to point towards. // SAFETY: ids in self must be valid - let new_archetype_id = archetypes.get_id_or_insert( + let new_base_archetype_id = archetypes.get_id_or_insert( components, observers, table_id, - table_components, - sparse_set_components, + table_components.clone(), + sparse_set_components.clone(), + Default::default(), ); // Add an edge from the old archetype to the new archetype. archetypes[archetype_id] .edges_mut() .cache_archetype_after_bundle_insert( self.id, - new_archetype_id, + new_base_archetype_id, bundle_status, added_required_components, added, existing, ); - new_archetype_id + + new_base_archetype_id + }; + + if value_components.is_empty() { + base_archetype_id + } else { + // Cache new archetype based on the value components. This new archetype shares pretty much everything with the base archetype + // except for identity based on fragmenting values. + let current_archetype = &archetypes[base_archetype_id]; + let table_id = current_archetype.table_id(); + let table_components = current_archetype.table_components().collect(); + let sparse_set_components = current_archetype.sparse_set_components().collect(); + let fragmenting_value_components = current_archetype + .components_with_fragmenting_values() + .map(|(component_id, value)| { + ( + component_id, + replaced_value_components + .get(&component_id) + .map(|value| { + storages + .shared + .get_or_insert(current_tick, component_id, *value, caller) + .value() + }) + .unwrap_or(value) + .clone(), + ) + }) + .chain(new_value_components) + .collect(); + let value_archetype_id = archetypes.get_id_or_insert( + components, + observers, + table_id, + table_components, + sparse_set_components, + fragmenting_value_components, + ); + archetypes[archetype_id] + .edges_mut() + .cache_archetype_value_components_after_bundle_insert( + self.id, + value_components.to_shared(current_tick, &mut storages.shared, caller), + value_archetype_id, + ); + value_archetype_id } } @@ -904,11 +1050,13 @@ impl BundleInfo { } else { let mut next_table_components; let mut next_sparse_set_components; + let next_value_components; let next_table_id; { let current_archetype = &mut archetypes[archetype_id]; let mut removed_table_components = Vec::new(); let mut removed_sparse_set_components = Vec::new(); + let mut removed_shared_components = Vec::new(); for component_id in self.iter_explicit_components() { if current_archetype.contains(component_id) { // SAFETY: bundle components were already initialized by bundles.get_info @@ -918,6 +1066,9 @@ impl BundleInfo { StorageType::SparseSet => { removed_sparse_set_components.push(component_id); } + StorageType::Shared => { + removed_shared_components.push(component_id); + } } } else if !intersection { // A component in the bundle was not present in the entity's archetype, so this @@ -951,6 +1102,16 @@ impl BundleInfo { .get_id_or_insert(&next_table_components, components) } }; + + next_value_components = current_archetype + .components_with_fragmenting_values() + .filter(|(id, _)| { + removed_sparse_set_components.binary_search(id).is_err() + && removed_table_components.binary_search(id).is_err() + && removed_shared_components.binary_search(id).is_err() + }) + .map(|(id, value)| (id, value.clone())) + .collect(); } let new_archetype_id = archetypes.get_id_or_insert( @@ -959,6 +1120,7 @@ impl BundleInfo { next_table_id, next_table_components, next_sparse_set_components, + next_value_components, ); Some(new_archetype_id) }; @@ -1011,6 +1173,8 @@ impl<'w> BundleInserter<'w> { world: &'w mut World, archetype_id: ArchetypeId, change_tick: Tick, + value_components: &FragmentingValuesBorrowed, + caller: MaybeLocation, ) -> Self { // SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too. let mut registrator = @@ -1019,7 +1183,16 @@ impl<'w> BundleInserter<'w> { .bundles .register_info::(&mut registrator, &mut world.storages); // 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, + caller, + ) + } } /// Creates a new [`BundleInserter`]. @@ -1032,6 +1205,8 @@ impl<'w> BundleInserter<'w> { archetype_id: ArchetypeId, bundle_id: BundleId, change_tick: Tick, + value_components: &FragmentingValuesBorrowed, + caller: MaybeLocation, ) -> 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); @@ -1042,6 +1217,9 @@ impl<'w> BundleInserter<'w> { &world.components, &world.observers, archetype_id, + value_components, + change_tick, + caller, ); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; @@ -1157,14 +1335,15 @@ impl<'w> BundleInserter<'w> { let (new_archetype, new_location, after_effect) = match &mut self.archetype_move_type { ArchetypeMoveType::SameArchetype => { // SAFETY: Mutable references do not alias and will be dropped after this block - let sparse_sets = { + let (sparse_sets, components) = { let world = self.world.world_mut(); - &mut world.storages.sparse_sets + (&mut world.storages.sparse_sets, &world.components) }; let after_effect = bundle_info.write_components( table, sparse_sets, + components, archetype_after_insert, archetype_after_insert.required_components.iter(), entity, @@ -1181,9 +1360,13 @@ impl<'w> BundleInserter<'w> { let new_archetype = new_archetype.as_mut(); // SAFETY: Mutable references do not alias and will be dropped after this block - let (sparse_sets, entities) = { + let (sparse_sets, entities, components) = { let world = self.world.world_mut(); - (&mut world.storages.sparse_sets, &mut world.entities) + ( + &mut world.storages.sparse_sets, + &mut world.entities, + &world.components, + ) }; let result = archetype.swap_remove(location.archetype_row); @@ -1206,6 +1389,7 @@ impl<'w> BundleInserter<'w> { let after_effect = bundle_info.write_components( table, sparse_sets, + components, archetype_after_insert, archetype_after_insert.required_components.iter(), entity, @@ -1226,13 +1410,14 @@ impl<'w> BundleInserter<'w> { let new_archetype = new_archetype.as_mut(); // SAFETY: Mutable references do not alias and will be dropped after this block - let (archetypes_ptr, sparse_sets, entities) = { + let (archetypes_ptr, sparse_sets, entities, components) = { let world = self.world.world_mut(); let archetype_ptr: *mut Archetype = world.archetypes.archetypes.as_mut_ptr(); ( archetype_ptr, &mut world.storages.sparse_sets, &mut world.entities, + &world.components, ) }; let result = archetype.swap_remove(location.archetype_row); @@ -1288,6 +1473,7 @@ impl<'w> BundleInserter<'w> { let after_effect = bundle_info.write_components( new_table, sparse_sets, + components, archetype_after_insert, archetype_after_insert.required_components.iter(), entity, @@ -1458,6 +1644,7 @@ impl<'w> BundleRemover<'w> { /// This can be passed to [`remove`](Self::remove) as the `pre_remove` function if you don't want to do anything before removing. pub fn empty_pre_remove( + _: &Archetype, _: &mut SparseSets, _: Option<&mut Table>, _: &Components, @@ -1479,6 +1666,7 @@ impl<'w> BundleRemover<'w> { location: EntityLocation, caller: MaybeLocation, pre_remove: impl FnOnce( + &Archetype, &mut SparseSets, Option<&mut Table>, &Components, @@ -1531,6 +1719,7 @@ impl<'w> BundleRemover<'w> { let world = unsafe { self.world.world_mut() }; let (needs_drop, pre_remove_result) = pre_remove( + &world.archetypes[location.archetype_id], &mut world.storages.sparse_sets, self.old_and_new_table .as_ref() @@ -1653,15 +1842,40 @@ 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, + caller: MaybeLocation, + ) -> 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); + let value_components = FragmentingValuesBorrowed::from_bundle(&mut registrator, bundle); + // SAFETY: we initialized this bundle_id in `init_info` + unsafe { Self::new_with_id(world, bundle_id, change_tick, &value_components, caller) } + } + + /// Same as [`BundleSpawner::new`] but doesn't require to pass [`Bundle`] by value and ignores [`FragmentingValue`] components. + /// This should be used only if it is known that `T` doesn't have fragmenting value components. + #[inline] + pub(crate) fn new_uniform( + world: &'w mut World, + change_tick: Tick, + caller: MaybeLocation, + ) -> 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); + let value_components = [].into_iter().collect(); // 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, caller) } } /// Creates a new [`BundleSpawner`]. @@ -1673,6 +1887,8 @@ impl<'w> BundleSpawner<'w> { world: &'w mut World, bundle_id: BundleId, change_tick: Tick, + value_components: &FragmentingValuesBorrowed, + caller: MaybeLocation, ) -> Self { let bundle_info = world.bundles.get_unchecked(bundle_id); let new_archetype_id = bundle_info.insert_bundle_into_archetype( @@ -1681,6 +1897,9 @@ impl<'w> BundleSpawner<'w> { &world.components, &world.observers, ArchetypeId::EMPTY, + value_components, + change_tick, + caller, ); let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; @@ -1718,15 +1937,20 @@ impl<'w> BundleSpawner<'w> { let archetype = self.archetype.as_mut(); // SAFETY: Mutable references do not alias and will be dropped after this block - let (sparse_sets, entities) = { + let (sparse_sets, entities, components) = { let world = self.world.world_mut(); - (&mut world.storages.sparse_sets, &mut world.entities) + ( + &mut world.storages.sparse_sets, + &mut world.entities, + &world.components, + ) }; let table_row = table.allocate(entity); let location = archetype.allocate(entity, table_row); let after_effect = bundle_info.write_components( table, sparse_sets, + components, &SpawnBundleStatus, bundle_info.required_components.iter(), entity, diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 80e60a88600c0..75e411a59637b 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -5,6 +5,7 @@ use crate::{ bundle::BundleInfo, change_detection::{MaybeLocation, MAX_CHANGE_AGE}, entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent}, + fragmenting_value::{FragmentingValue, FragmentingValueVtable}, query::DebugCheckedUnwrap, relationship::RelationshipHookMode, resource::Resource, @@ -501,6 +502,9 @@ pub trait Component: Send + Sync + 'static { /// A constant indicating the storage type used for this component. const STORAGE_TYPE: StorageType; + /// A type used to define the "component key" of this component. Currently can be set to [`NoKey`] or [`SelfKey`]. + type Key: ComponentKey; + /// A marker type to assist Bevy with determining if this component is /// mutable, or immutable. Mutable components will have [`Component`], /// while immutable components will instead have [`Component`]. @@ -578,6 +582,12 @@ pub trait Component: Send + Sync + 'static { /// ``` #[inline] fn map_entities(_this: &mut Self, _mapper: &mut E) {} + + /// Returns `true` if this component is a [`FragmentingValue`] component and its value can be used to fragment archetypes. + #[inline(always)] + fn is_fragmenting_value_component() -> bool { + TypeId::of::<::KeyType>() == TypeId::of::() + } } mod private { @@ -635,6 +645,48 @@ impl ComponentMutability for Mutable { const MUTABLE: bool = true; } +/// This trait defines a concept of "component key". Component keys are components that fragment archetype by value, also known as +/// "fragmenting components". Right now the only supported keys are [`NoKey`] - default behavior, and [`SelfKey`] - the component +/// itself acts as the fragmenting key. Defining other key as a component might be used in the future to define component groups. +pub trait ComponentKey: private::Seal { + /// The value that will be used to fragment the archetypes. + type KeyType: FragmentingValue; + /// The component this key is set to. Currently used only for validation. + type ValueType: Component; + /// A `const` to assert values not enforceable by type system at compile time. + /// Sadly this doesn't work with `cargo check`, but it will still fail when running `cargo build`. + const INVARIANT_ASSERT: (); +} + +/// This component doesn't have a fragmenting key. This is the default behavior. +pub struct NoKey(PhantomData); + +impl private::Seal for NoKey {} +impl ComponentKey for NoKey { + type KeyType = (); + type ValueType = C; + const INVARIANT_ASSERT: () = { + if matches!(C::STORAGE_TYPE, StorageType::Shared) { + panic!("Shared components must have a key") + } + }; +} + +/// Select other component as the key of this component. Currently this doesn't do anything useful, but in the future it might be +/// possible to define component groups by selecting common key component. +pub struct OtherComponentKey(PhantomData<(C, K)>); + +impl private::Seal for OtherComponentKey {} +impl ComponentKey for OtherComponentKey { + type KeyType = K; + type ValueType = C; + const INVARIANT_ASSERT: () = (); +} + +/// Set this component as the fragmenting key. This means every different value of this component (as defined by [`PartialEq::eq`]) +/// will exist only in it's own archetype. +pub type SelfKey = OtherComponentKey; + /// The storage used for a specific component type. /// /// # Examples @@ -654,6 +706,13 @@ pub enum StorageType { Table, /// Provides fast addition and removal of components, but slower iteration. SparseSet, + /// Stores each component type and value combination once for the whole archetype. + /// This provides addition and removal performance similar to [`StorageType::SparseSet`], but with faster iteration + /// and lower memory usage as long as there are many entities with the same component value. + /// The downside is that this storage type is less efficient if the component is expected to have a large number of different values. + /// + /// Shared components are always immutable. + Shared, } /// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`. @@ -1008,6 +1067,13 @@ 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> { + self.descriptor.fragmenting_value_vtable.as_ref() + } } /// A value which uniquely identifies the type of a [`Component`] or [`Resource`] within a @@ -1086,6 +1152,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 @@ -1099,6 +1166,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() } } @@ -1125,6 +1193,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::(), } } @@ -1133,6 +1202,11 @@ 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` + /// + /// # Panics + /// - Will panic if `fragmenting_value_vtable` is not `None` and `mutable` is `true`. Fragmenting value components must be immutable. + /// - `fragmenting_value_vtable` must not be `None` if `storage_type` is [`StorageType::Shared`]. pub unsafe fn new_with_layout( name: impl Into>, storage_type: StorageType, @@ -1140,7 +1214,14 @@ impl ComponentDescriptor { drop: Option unsafe fn(OwningPtr<'a>)>, mutable: bool, clone_behavior: ComponentCloneBehavior, + fragmenting_value_vtable: Option, ) -> Self { + if storage_type == StorageType::Shared && fragmenting_value_vtable.is_none() { + panic!("Shared components must have valid fragmenting_value_vtable"); + } + if fragmenting_value_vtable.is_some() && mutable { + panic!("Fragmenting value components must be immutable"); + } Self { name: name.into(), storage_type, @@ -1150,6 +1231,7 @@ impl ComponentDescriptor { drop, mutable, clone_behavior, + fragmenting_value_vtable, } } @@ -1168,6 +1250,7 @@ impl ComponentDescriptor { drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, clone_behavior: ComponentCloneBehavior::Default, + fragmenting_value_vtable: None, } } @@ -1181,6 +1264,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/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index a7a1f84403218..049905e6aa3f2 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1309,6 +1309,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", @@ -1317,6 +1318,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/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs new file mode 100644 index 0000000000000..b3442a31b5bee --- /dev/null +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -0,0 +1,727 @@ +//! 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; +use bevy_ptr::Ptr; +use core::{ + any::{Any, TypeId}, + hash::{BuildHasher, Hash, Hasher}, + ptr::NonNull, +}; +use indexmap::Equivalent; + +use crate::{ + bundle::Bundle, + change_detection::MaybeLocation, + component::{ComponentId, ComponentKey, Components, ComponentsRegistrator, Immutable, Tick}, + storage::{Shared, SharedFragmentingValue}, +}; + +/// Trait used to define values that can fragment archetypes. +/// +/// This trait is automatically implemented for all immutable components that also implement [`Eq`], [`Hash`] and [`Clone`]. +/// +/// For dynamic components see [`DynamicFragmentingValue`] and [`FragmentingValueVtable`]. +pub trait FragmentingValue: Any { + /// Return `true` if `self == other`. Dynamic version of [`PartialEq::eq`]. + /// + /// **NOTE**: This method must be implemented similarly [`PartialEq::eq`], however + /// when comparing `dyn FragmentingValue`s prefer to use `==` to support values created using [`DynamicFragmentingValue`]. + fn value_eq(&self, other: &dyn FragmentingValue) -> bool; + /// Returns the hash value of `self`. Dynamic version of [`Hash::hash`]. + fn value_hash(&self) -> u64; + /// Returns a boxed clone of `self`. Dynamic version of [`Clone::clone`]. + fn clone_boxed(&self) -> Box; +} + +impl FragmentingValue for T +where + T: Component + Eq + Hash + Clone, +{ + fn value_eq(&self, other: &dyn FragmentingValue) -> bool { + #[expect(clippy::let_unit_value, reason = "This is used for static asserts")] + { + _ = T::Key::INVARIANT_ASSERT; + } + if let Some(other) = (other as &dyn Any).downcast_ref::() { + return self == other; + } + false + } + + fn value_hash(&self) -> u64 { + FixedHasher.hash_one(self) + } + + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } +} + +impl FragmentingValue for () { + fn value_eq(&self, other: &dyn FragmentingValue) -> bool { + (other as &dyn Any).is::<()>() + } + + fn value_hash(&self) -> u64 { + FixedHasher.hash_one(()) + } + + fn clone_boxed(&self) -> Box { + Box::new(()) + } +} + +impl PartialEq for dyn FragmentingValue { + fn eq(&self, other: &Self) -> bool { + Self::value_eq_dynamic(self, other) + } +} + +impl Eq for dyn FragmentingValue {} + +impl Hash for dyn FragmentingValue { + fn hash(&self, state: &mut H) { + let key_hash = FragmentingValue::value_hash(self); + state.write_u64(key_hash); + } +} + +impl dyn FragmentingValue { + /// Return `true` if `self == other`. This version supports values created using [`DynamicFragmentingValue`]. + #[inline(always)] + fn value_eq_dynamic(&self, other: &dyn FragmentingValue) -> bool { + self.value_eq(other) + || (other as &dyn Any) + .downcast_ref::() + .is_some_and(|other| other.value_eq(self)) + } +} + +/// A collection of fragmenting component values and ids. +/// This collection is sorted internally to allow for order-independent comparison. +/// +/// Owned version can be used as a key in maps. [`FragmentingValuesBorrowed`] is a version that doesn't require cloning the values. +#[derive(Hash, PartialEq, Eq, Default, Clone)] +pub struct FragmentingValuesShared { + values: Box<[(ComponentId, SharedFragmentingValue)]>, +} + +impl FragmentingValuesShared { + /// Returns `true` if there are no fragmenting values. + pub fn is_empty(&self) -> bool { + self.values.is_empty() + } + + /// Returns fragmenting component values with their corresponding [`ComponentId`]s. + pub fn iter_ids_and_values( + &self, + ) -> impl Iterator { + self.values.iter().map(|(id, v)| (*id, v)) + } +} + +impl FromIterator<(ComponentId, SharedFragmentingValue)> for FragmentingValuesShared { + fn from_iter>(iter: I) -> Self { + FragmentingValuesShared { + values: iter.into_iter().collect(), + } + } +} + +/// A collection of fragmenting component values and ids. +/// This collection is sorted internally to allow for order-independent comparison. +/// +/// Borrowed version is used to query maps with [`FragmentingValuesShared`] keys. +#[derive(Hash, PartialEq, Eq, Default)] +pub struct FragmentingValuesBorrowed<'a> { + values: Box<[(ComponentId, &'a dyn FragmentingValue)]>, +} + +impl<'a> FragmentingValuesBorrowed<'a> { + /// Borrows fragmenting values from a [`Bundle`]. + /// This is used to compare fragmenting values without cloning bundle's data. + pub fn from_bundle(components: &mut ComponentsRegistrator, bundle: &'a B) -> Self { + let mut values = Vec::new(); + bundle.get_fragmenting_values(components, &mut |id, value| values.push((id, value))); + values.sort_unstable_by_key(|(id, _)| *id); + FragmentingValuesBorrowed { + values: values.into_boxed_slice(), + } + } + + /// Returns `true` if there are no fragmenting values. + pub fn is_empty(&self) -> bool { + self.values.is_empty() + } + + /// Creates [`FragmentingValuesShared`] by cloning or retrieving from [`Shared`] all fragmenting values. + pub fn to_shared( + &self, + current_tick: Tick, + shared_component_storage: &mut Shared, + caller: MaybeLocation, + ) -> FragmentingValuesShared { + self.values + .iter() + .map(|(id, v)| { + ( + *id, + shared_component_storage + .get_or_insert(current_tick, *id, *v, caller) + .value() + .clone(), + ) + }) + .collect() + } + + /// Returns fragmenting component values with their corresponding [`ComponentId`]s. + pub fn iter_ids_and_values( + &self, + ) -> impl Iterator { + self.values.iter().map(|(id, v)| (*id, *v)) + } +} + +impl<'a> FromIterator<(ComponentId, &'a dyn FragmentingValue)> for FragmentingValuesBorrowed<'a> { + fn from_iter>(iter: I) -> Self { + let mut values = Vec::new(); + for (id, value) in iter { + values.push((id, value)); + } + values.sort_unstable_by_key(|(id, _)| *id); + FragmentingValuesBorrowed { + values: values.into_boxed_slice(), + } + } +} + +impl<'a> Equivalent for FragmentingValuesBorrowed<'a> { + fn equivalent(&self, key: &FragmentingValuesShared) -> bool { + self.values.len() == key.values.len() + && self + .values + .iter() + .zip(key.values.iter()) + // We know that v2 is never an instance of DynamicFragmentingValue since it is from FragmentingValuesShared. + // Because FragmentingValuesShared is created by calling clone_boxed, it always creates Box of a proper type that DynamicFragmentingValues abstracts. + // This means that we don't have to use value_eq_dynamic implementation and can compare with value_eq instead. + .all(|((id1, v1), (id2, v2))| id1 == id2 && v1.value_eq(&**v2)) + } +} + +#[derive(Default)] +enum DynamicFragmentingValueInner { + #[default] + Uninit, + Borrowed { + value: NonNull, + vtable: FragmentingValueVtable, + }, +} + +/// Holder for a reference to a dynamic fragmenting component and a vtable. +#[derive(Default)] +pub struct DynamicFragmentingValue(DynamicFragmentingValueInner); + +impl FragmentingValue for DynamicFragmentingValueInner { + fn value_eq(&self, other: &dyn FragmentingValue) -> bool { + match self { + DynamicFragmentingValueInner::Uninit => panic!("Uninitialized"), + DynamicFragmentingValueInner::Borrowed { value, vtable } => (vtable.eq)(*value, other), + } + } + + fn value_hash(&self) -> u64 { + match self { + DynamicFragmentingValueInner::Uninit => panic!("Uninitialized"), + DynamicFragmentingValueInner::Borrowed { value, vtable } => (vtable.hash)(*value), + } + } + + fn clone_boxed(&self) -> Box { + match self { + DynamicFragmentingValueInner::Uninit => panic!("Uninitialized"), + DynamicFragmentingValueInner::Borrowed { value, vtable } => { + (vtable.clone_boxed)(*value) + } + } + } +} + +impl DynamicFragmentingValue { + /// Create a new `&dyn` [`FragmentingValue`] from passed component data and id. + /// This is used mostly to construct [`FragmentingValuesBorrowed`] to compare dynamic components without copying data. + /// + /// Will return `None` if component isn't fragmenting. + /// + /// # Safety + /// - `component_id` must match data which `component` points to. + pub unsafe fn from_component<'a>( + &'a mut self, + components: &Components, + component_id: ComponentId, + component: Ptr<'a>, + ) -> Option<&'a dyn FragmentingValue> { + let info = components.get_info(component_id)?; + let vtable = info.value_component_vtable()?; + let inner = DynamicFragmentingValueInner::Borrowed { + value: NonNull::new_unchecked(component.as_ptr()), + vtable: *vtable, + }; + self.0 = inner; + Some(&self.0) + } + + /// Create a new `&dyn` [`FragmentingValue`] from passed component data and [`FragmentingValueVtable`]. + /// + /// # Safety + /// - `vtable` must be usable for the data which `component` points to. + pub unsafe fn from_vtable<'a>( + &'a mut self, + vtable: FragmentingValueVtable, + component: Ptr<'a>, + ) -> &'a dyn FragmentingValue { + let inner = DynamicFragmentingValueInner::Borrowed { + value: NonNull::new_unchecked(component.as_ptr()), + vtable, + }; + self.0 = inner; + &self.0 + } +} + +/// Dynamic vtable for [`FragmentingValue`]. +/// This is used by [`crate::component::ComponentDescriptor`] to work with dynamic fragmenting components. +#[derive(Clone, Copy, Debug)] +pub struct FragmentingValueVtable { + eq: fn(NonNull, &dyn FragmentingValue) -> bool, + hash: fn(NonNull) -> u64, + clone_boxed: fn(NonNull) -> Box, +} +impl FragmentingValueVtable { + /// Create a new vtable from raw functions. + /// + /// Also see [`from_fragmenting_value`](FragmentingValueVtable::from_fragmenting_value) and + /// [`from_component`](FragmentingValueVtable::from_component) for more convenient constructors. + pub fn new( + eq: fn(NonNull, &dyn FragmentingValue) -> bool, + hash: fn(NonNull) -> u64, + clone_boxed: fn(NonNull) -> Box, + ) -> Self { + Self { + eq, + hash, + clone_boxed, + } + } + + /// Creates [`FragmentingValueVtable`] from existing [`FragmentingValue`]. + pub fn from_fragmenting_value() -> Self { + FragmentingValueVtable { + eq: |ptr, other| { + // SAFETY: caller is responsible for using this vtable only with correct values + *(unsafe { ptr.cast::().as_ref() } as &dyn FragmentingValue) == *other + }, + hash: |ptr| { + // SAFETY: caller is responsible for using this vtable only with correct values + unsafe { ptr.cast::().as_ref() }.value_hash() + }, + clone_boxed: |ptr| { + // SAFETY: caller is responsible for using this vtable only with correct values + unsafe { ptr.cast::().as_ref() }.clone_boxed() + }, + } + } + + /// Creates [`FragmentingValueVtable`] from a [`Component`]. + /// + /// This will return `None` if the component isn't fragmenting. + pub fn from_component() -> Option { + if TypeId::of::<::KeyType>() == TypeId::of::() { + Some(Self::from_fragmenting_value::< + ::KeyType, + >()) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use core::{ + alloc::Layout, + any::Any, + hash::{BuildHasher, Hasher}, + ptr::NonNull, + }; + + use crate::{ + archetype::ArchetypeId, + component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, + entity::Entity, + fragmenting_value::{DynamicFragmentingValue, FragmentingValue, FragmentingValueVtable}, + world::World, + }; + use alloc::boxed::Box; + 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(storage = "Shared")] + struct Fragmenting(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_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)] + struct DynamicComponentWrapper { + id: u64, + data: T, + } + + type DynamicComponent = DynamicComponentWrapper<[u8; COMPONENT_SIZE]>; + + impl FragmentingValue for DynamicComponentWrapper { + fn value_eq(&self, other: &dyn FragmentingValue) -> bool { + (other as &dyn Any) + .downcast_ref::>() + .is_some_and(|other| other.id == self.id && other.data == self.data) + } + + fn value_hash(&self) -> u64 { + let mut hasher = FixedHasher.build_hasher(); + self.id.hash(&mut hasher); + self.data.hash(&mut hasher); + hasher.finish() + } + + fn clone_boxed(&self) -> Box { + Box::new(self.clone()) + } + } + let layout = Layout::new::(); + let vtable = FragmentingValueVtable::from_fragmenting_value::(); + + // 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 { + id: 1, + data: [5; 10], + }; + let component1_2 = DynamicComponent { + id: 1, + data: [8; 10], + }; + let component2_1 = DynamicComponent { + id: 2, + 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_compare_with_dynamic() { + let value: &dyn FragmentingValue = &Fragmenting(1); + let mut dynamic_holder = DynamicFragmentingValue::default(); + let vtable = FragmentingValueVtable::from_fragmenting_value::(); + // SAFETY: + // - vtable matches component data + let dynamic = unsafe { dynamic_holder.from_vtable(vtable, Ptr::from(&Fragmenting(1))) }; + + assert!(*value == *dynamic); + assert!(*dynamic == *value); + } + + #[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() + .get_archetype_after_bundle_insert_internal(bundle_id) + .unwrap() + .fragmenting_values_map + .keys() + .len() + }; + + 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/lib.rs b/crates/bevy_ecs/src/lib.rs index d8846b29a1b83..86b08fb5b3085 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -38,6 +38,7 @@ pub mod entity; pub mod entity_disabling; pub mod error; pub mod event; +pub mod fragmenting_value; pub mod hierarchy; pub mod intern; pub mod label; diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index 2c2d42b1c91be..1085974bb0483 100644 --- a/crates/bevy_ecs/src/observer/entity_observer.rs +++ b/crates/bevy_ecs/src/observer/entity_observer.rs @@ -1,6 +1,6 @@ use crate::{ component::{ - Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType, + Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, NoKey, StorageType, }, entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent}, world::World, @@ -16,6 +16,7 @@ pub struct ObservedBy(pub(crate) Vec); 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/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index 520147d4385e9..2a3494ddc290d 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -2,7 +2,7 @@ use alloc::{boxed::Box, vec}; use core::any::Any; use crate::{ - component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType}, + component::{ComponentHook, ComponentId, HookContext, Mutable, NoKey, StorageType}, error::{ErrorContext, ErrorHandler}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, @@ -301,6 +301,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 { diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index cffba8cda1689..044283619aaa0 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -5,7 +5,7 @@ use crate::{ component::{Component, ComponentId, Components, Mutable, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, - storage::{ComponentSparseSet, Table, TableRow}, + storage::{ComponentSparseSet, Shared, SharedComponent, Table, TableRow}, world::{ unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, FilteredEntityMut, FilteredEntityRef, Mut, Ref, World, @@ -1374,6 +1374,8 @@ pub struct ReadFetch<'w, T: Component> { Option>>, // T::STORAGE_TYPE = StorageType::SparseSet Option<&'w ComponentSparseSet>, + // T::STORAGE_TYPE = StorageType::Shared + Option<&'w T>, >, } @@ -1414,6 +1416,7 @@ unsafe impl WorldQuery for &T { // reference to the sparse set, which is used to access the components in `Self::fetch`. unsafe { world.storages().sparse_sets.get(component_id) } }, + || None, ), } } @@ -1421,7 +1424,7 @@ unsafe impl WorldQuery for &T { const IS_DENSE: bool = { match T::STORAGE_TYPE { StorageType::Table => true, - StorageType::SparseSet => false, + StorageType::SparseSet | StorageType::Shared => false, } }; @@ -1429,7 +1432,7 @@ unsafe impl WorldQuery for &T { unsafe fn set_archetype<'w>( fetch: &mut ReadFetch<'w, T>, component_id: &ComponentId, - _archetype: &'w Archetype, + archetype: &'w Archetype, table: &'w Table, ) { if Self::IS_DENSE { @@ -1437,6 +1440,13 @@ unsafe impl WorldQuery for &T { unsafe { Self::set_table(fetch, component_id, table); } + } else if matches!(T::STORAGE_TYPE, StorageType::Shared) { + fetch.components.set_shared(|_| { + archetype + .get_value_component(*component_id) + .debug_checked_unwrap() + .try_deref() + }); } } @@ -1518,6 +1528,7 @@ unsafe impl QueryData for &T { }; item.deref() }, + |shared| shared.debug_checked_unwrap(), ) } } @@ -1539,6 +1550,8 @@ pub struct RefFetch<'w, T: Component> { // T::STORAGE_TYPE = StorageType::SparseSet // Can be `None` when the component has never been inserted Option<&'w ComponentSparseSet>, + // T::STORAGE_TYPE = StorageType::Shared + (&'w Shared, Option<&'w SharedComponent>), >, last_run: Tick, this_run: Tick, @@ -1581,6 +1594,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { // reference to the sparse set, which is used to access the components in `Self::fetch`. unsafe { world.storages().sparse_sets.get(component_id) } }, + // SAFETY: Shared storage is only accessed immutably. + || unsafe { (&world.storages().shared, None) }, ), last_run, this_run, @@ -1590,7 +1605,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { const IS_DENSE: bool = { match T::STORAGE_TYPE { StorageType::Table => true, - StorageType::SparseSet => false, + StorageType::SparseSet | StorageType::Shared => false, } }; @@ -1598,7 +1613,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { unsafe fn set_archetype<'w>( fetch: &mut RefFetch<'w, T>, component_id: &ComponentId, - _archetype: &'w Archetype, + archetype: &'w Archetype, table: &'w Table, ) { if Self::IS_DENSE { @@ -1606,6 +1621,13 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { unsafe { Self::set_table(fetch, component_id, table); } + } else if matches!(T::STORAGE_TYPE, StorageType::Shared) { + fetch.components.set_shared(|(shared, _)| { + let component_value = archetype + .get_value_component(*component_id) + .debug_checked_unwrap(); + (shared, shared.get_shared(component_value)) + }); } } @@ -1717,6 +1739,19 @@ unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> { changed_by: caller.map(|caller| caller.deref()), } }, + |(_, shared_component)| { + let component = shared_component.debug_checked_unwrap(); + Ref { + value: component.value().try_deref().debug_checked_unwrap(), + ticks: Ticks { + added: component.added_ref(), + changed: component.added_ref(), + last_run: fetch.last_run, + this_run: fetch.this_run, + }, + changed_by: component.location().as_ref(), + } + }, ) } } @@ -1738,6 +1773,9 @@ pub struct WriteFetch<'w, T: Component> { // T::STORAGE_TYPE = StorageType::SparseSet // Can be `None` when the component has never been inserted Option<&'w ComponentSparseSet>, + // T::STORAGE_TYPE = StorageType::Shared + // This is always () since only immutable components can be shared + (), >, last_run: Tick, this_run: Tick, @@ -1780,6 +1818,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { // reference to the sparse set, which is used to access the components in `Self::fetch`. unsafe { world.storages().sparse_sets.get(component_id) } }, + || {}, ), last_run, this_run, @@ -1789,7 +1828,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { const IS_DENSE: bool = { match T::STORAGE_TYPE { StorageType::Table => true, - StorageType::SparseSet => false, + StorageType::SparseSet | StorageType::Shared => false, } }; @@ -1916,6 +1955,7 @@ unsafe impl<'__w, T: Component> QueryData for &'__w mut T changed_by: caller.map(|caller| caller.deref_mut()), } }, + |_| unreachable!("Shared components can never be accessed mutably"), ) } } @@ -2251,7 +2291,7 @@ unsafe impl WorldQuery for Has { const IS_DENSE: bool = { match T::STORAGE_TYPE { StorageType::Table => true, - StorageType::SparseSet => false, + StorageType::SparseSet | StorageType::Shared => false, } }; @@ -2712,23 +2752,30 @@ unsafe impl ReadOnlyQueryData for PhantomData {} /// A compile-time checked union of two different types that differs based on the /// [`StorageType`] of a given component. -pub(super) union StorageSwitch { +pub(super) union StorageSwitch { /// The table variant. Requires the component to be a table component. table: T, /// The sparse set variant. Requires the component to be a sparse set component. sparse_set: S, + + shared: H, _marker: PhantomData, } -impl StorageSwitch { +impl StorageSwitch { /// Creates a new [`StorageSwitch`] using the given closures to initialize /// the variant corresponding to the component's [`StorageType`]. - pub fn new(table: impl FnOnce() -> T, sparse_set: impl FnOnce() -> S) -> Self { + pub fn new( + table: impl FnOnce() -> T, + sparse_set: impl FnOnce() -> S, + shared: impl FnOnce() -> H, + ) -> Self { match C::STORAGE_TYPE { StorageType::Table => Self { table: table() }, StorageType::SparseSet => Self { sparse_set: sparse_set(), }, + StorageType::Shared => Self { shared: shared() }, } } @@ -2754,9 +2801,26 @@ impl StorageSwitch { } } + pub unsafe fn set_shared(&mut self, shared: impl Fn(H) -> H) { + match C::STORAGE_TYPE { + StorageType::Shared => self.shared = shared(self.shared), + _ => { + #[cfg(debug_assertions)] + unreachable!(); + #[cfg(not(debug_assertions))] + core::hint::unreachable_unchecked() + } + } + } + /// Fetches the internal value from the variant that corresponds to the /// component's [`StorageType`]. - pub fn extract(&self, table: impl FnOnce(T) -> R, sparse_set: impl FnOnce(S) -> R) -> R { + pub fn extract( + &self, + table: impl FnOnce(T) -> R, + sparse_set: impl FnOnce(S) -> R, + shared: impl FnOnce(H) -> R, + ) -> R { match C::STORAGE_TYPE { StorageType::Table => table( // SAFETY: C::STORAGE_TYPE == StorageType::Table @@ -2766,17 +2830,21 @@ impl StorageSwitch { // SAFETY: C::STORAGE_TYPE == StorageType::SparseSet unsafe { self.sparse_set }, ), + StorageType::Shared => shared( + // SAFETY: C::STORAGE_TYPE == StorageType::Shared + unsafe { self.shared }, + ), } } } -impl Clone for StorageSwitch { +impl Clone for StorageSwitch { fn clone(&self) -> Self { *self } } -impl Copy for StorageSwitch {} +impl Copy for StorageSwitch {} #[cfg(test)] mod tests { diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index eccc819ca7cb3..603a85c61eeee 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -3,7 +3,7 @@ use crate::{ component::{Component, ComponentId, Components, StorageType, Tick}, entity::{Entities, Entity}, query::{DebugCheckedUnwrap, FilteredAccess, StorageSwitch, WorldQuery}, - storage::{ComponentSparseSet, Table, TableRow}, + storage::{ComponentSparseSet, Shared, SharedComponent, Table, TableRow}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; @@ -161,7 +161,7 @@ unsafe impl WorldQuery for With { const IS_DENSE: bool = { match T::STORAGE_TYPE { StorageType::Table => true, - StorageType::SparseSet => false, + StorageType::SparseSet | StorageType::Shared => false, } }; @@ -261,7 +261,7 @@ unsafe impl WorldQuery for Without { const IS_DENSE: bool = { match T::STORAGE_TYPE { StorageType::Table => true, - StorageType::SparseSet => false, + StorageType::SparseSet | StorageType::Shared => false, } }; @@ -689,6 +689,8 @@ pub struct AddedFetch<'w, T: Component> { // T::STORAGE_TYPE = StorageType::SparseSet // Can be `None` when the component has never been inserted Option<&'w ComponentSparseSet>, + // T::STORAGE_TYPE = StorageType::Shared + (&'w Shared, Option), >, last_run: Tick, this_run: Tick, @@ -734,6 +736,8 @@ unsafe impl WorldQuery for Added { // reference to the sparse set, which is used to access the components' ticks in `Self::fetch`. unsafe { world.storages().sparse_sets.get(id) } }, + // SAFETY: Shared storage is only accessed immutably. + || unsafe { (&world.storages().shared, None) }, ), last_run, this_run, @@ -743,7 +747,7 @@ unsafe impl WorldQuery for Added { const IS_DENSE: bool = { match T::STORAGE_TYPE { StorageType::Table => true, - StorageType::SparseSet => false, + StorageType::SparseSet | StorageType::Shared => false, } }; @@ -751,7 +755,7 @@ unsafe impl WorldQuery for Added { unsafe fn set_archetype<'w>( fetch: &mut Self::Fetch<'w>, component_id: &ComponentId, - _archetype: &'w Archetype, + archetype: &'w Archetype, table: &'w Table, ) { if Self::IS_DENSE { @@ -759,6 +763,18 @@ unsafe impl WorldQuery for Added { unsafe { Self::set_table(fetch, component_id, table); } + } else if matches!(T::STORAGE_TYPE, StorageType::Shared) { + fetch.ticks.set_shared(|(shared, _)| { + let component_value = archetype + .get_value_component(*component_id) + .debug_checked_unwrap(); + ( + shared, + shared + .get_shared(component_value) + .map(SharedComponent::added), + ) + }); } } @@ -832,6 +848,10 @@ unsafe impl QueryFilter for Added { tick.deref().is_newer_than(fetch.last_run, fetch.this_run) }, + |(_, tick)| { + tick.debug_checked_unwrap() + .is_newer_than(fetch.last_run, fetch.this_run) + }, ) } } @@ -915,6 +935,8 @@ pub struct ChangedFetch<'w, T: Component> { Option>>, // Can be `None` when the component has never been inserted Option<&'w ComponentSparseSet>, + // T::STORAGE_TYPE = StorageType::Shared + (&'w Shared, Option), >, last_run: Tick, this_run: Tick, @@ -960,6 +982,8 @@ unsafe impl WorldQuery for Changed { // reference to the sparse set, which is used to access the components' ticks in `Self::fetch`. unsafe { world.storages().sparse_sets.get(id) } }, + // SAFETY: Shared storage is only accessed immutably. + || unsafe { (&world.storages().shared, None) }, ), last_run, this_run, @@ -969,7 +993,7 @@ unsafe impl WorldQuery for Changed { const IS_DENSE: bool = { match T::STORAGE_TYPE { StorageType::Table => true, - StorageType::SparseSet => false, + StorageType::SparseSet | StorageType::Shared => false, } }; @@ -977,7 +1001,7 @@ unsafe impl WorldQuery for Changed { unsafe fn set_archetype<'w>( fetch: &mut Self::Fetch<'w>, component_id: &ComponentId, - _archetype: &'w Archetype, + archetype: &'w Archetype, table: &'w Table, ) { if Self::IS_DENSE { @@ -985,6 +1009,18 @@ unsafe impl WorldQuery for Changed { unsafe { Self::set_table(fetch, component_id, table); } + } else if matches!(T::STORAGE_TYPE, StorageType::Shared) { + fetch.ticks.set_shared(|(shared, _)| { + let component_value = archetype + .get_value_component(*component_id) + .debug_checked_unwrap(); + ( + shared, + shared + .get_shared(component_value) + .map(SharedComponent::added), + ) + }); } } @@ -1059,6 +1095,10 @@ unsafe impl QueryFilter for Changed { tick.deref().is_newer_than(fetch.last_run, fetch.this_run) }, + |(_, tick)| { + tick.debug_checked_unwrap() + .is_newer_than(fetch.last_run, fetch.this_run) + }, ) } } diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index d5014f22409fd..515935785eae8 100644 --- a/crates/bevy_ecs/src/spawn.rs +++ b/crates/bevy_ecs/src/spawn.rs @@ -209,6 +209,22 @@ unsafe impl + Send + Sync + 'static> Bundle required_components, ); } + + fn get_fragmenting_values<'a>( + &'a self, + _components: &mut crate::component::ComponentsRegistrator, + _values: &mut impl FnMut( + crate::component::ComponentId, + &'a dyn crate::fragmenting_value::FragmentingValue, + ), + ) { + // RelationshipTargets never fragment by value since only immutable components can fragment. + } + + fn has_fragmenting_values() -> bool { + // RelationshipTargets never fragment by value since only immutable components can fragment. + false + } } impl> DynamicBundle for SpawnRelatedBundle { type Effect = Self; @@ -276,6 +292,22 @@ unsafe impl Bundle for SpawnOneRelated { required_components, ); } + + fn get_fragmenting_values<'a>( + &'a self, + _components: &mut crate::component::ComponentsRegistrator, + _values: &mut impl FnMut( + crate::component::ComponentId, + &'a dyn crate::fragmenting_value::FragmentingValue, + ), + ) { + // RelationshipTargets never fragment by value since only immutable components can fragment. + } + + fn has_fragmenting_values() -> bool { + // RelationshipTargets never fragment by value since only immutable components can fragment. + false + } } /// [`RelationshipTarget`] methods that create a [`Bundle`] with a [`DynamicBundle::Effect`] that: diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 2a5a5f184e649..5da01b8291cf6 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -23,11 +23,13 @@ mod blob_array; mod blob_vec; mod resource; +mod shared; mod sparse_set; mod table; mod thin_array_ptr; pub use resource::*; +pub use shared::*; pub use sparse_set::*; pub use table::*; @@ -45,18 +47,20 @@ pub struct Storages { pub resources: Resources, /// Backing storage for `!Send` resources. pub non_send_resources: Resources, + /// Backing storage for [`Shared`] components. + pub shared: Shared, } impl Storages { /// ensures that the component has its necessary storage initialize. pub fn prepare_component(&mut self, component: &ComponentInfo) { match component.storage_type() { - StorageType::Table => { - // table needs no preparation - } StorageType::SparseSet => { self.sparse_sets.get_or_insert(component); } + StorageType::Shared | StorageType::Table => { + // needs no preparation + } } } } diff --git a/crates/bevy_ecs/src/storage/shared.rs b/crates/bevy_ecs/src/storage/shared.rs new file mode 100644 index 0000000000000..482ea6d94e1fc --- /dev/null +++ b/crates/bevy_ecs/src/storage/shared.rs @@ -0,0 +1,231 @@ +use bevy_platform::{collections::HashSet, sync::Arc}; +use core::{ + any::Any, + cell::Cell, + hash::{Hash, Hasher}, + ops::Deref, +}; +use indexmap::Equivalent; + +use crate::{ + change_detection::MaybeLocation, + component::{ComponentId, Tick}, + fragmenting_value::FragmentingValue, +}; + +/// Stores data associated with a shared component. +pub struct SharedComponent { + component_id: ComponentId, + added: Cell, + location: MaybeLocation, + value: SharedFragmentingValue, +} + +impl SharedComponent { + /// Returns [`ComponentId`] of this component. + pub fn component_id(&self) -> ComponentId { + self.component_id + } + + /// Returns the [`Tick`] this component and value combination was first added to the world. + pub fn added(&self) -> Tick { + self.added.get() + } + + /// Returns [`SharedFragmentingValue`] of this component. + pub fn value(&self) -> &SharedFragmentingValue { + &self.value + } + + /// Returns [`MaybeLocation`] of this component. + pub fn location(&self) -> &MaybeLocation { + &self.location + } + + /// Same as [`added`](Self::added), but returns `&Tick` instead. + pub(crate) fn added_ref(&self) -> &Tick { + // SAFETY: + // The only way to obtain a `&SharedComponent` is through `Shared` storage. + // Mutating `added` only happens in `Shared::check_change_ticks`, which requires `&mut Shared`. + // Therefore, since &SharedComponent's lifetime is bound to &Shared's lifetime, the resulting &Tick's lifetime + // is bound to &Shared's lifetime and there's no mutable aliasing. + unsafe { self.added.as_ptr().as_ref().unwrap() } + } +} + +impl PartialEq for SharedComponent { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl Eq for SharedComponent {} + +impl Hash for SharedComponent { + fn hash(&self, state: &mut H) { + self.value.hash(state); + } +} + +/// Stores [`SharedComponent`]s. +/// +/// There is one [`SharedComponent`] per each [`FragmentingValue`] of a [`Shared`] component. +/// This acts as a central storage for these components and provides a way to do faster value comparison by +/// giving access to [`SharedFragmentingValue`], which are faster to compare than `dyn FragmentingValue`s +/// +/// [`Shared`]: crate::component::StorageType::Shared +#[derive(Default)] +pub struct Shared { + values_set: HashSet, +} + +impl Shared { + /// Get [`SharedComponent`] for the provided [`FragmentingValue`] or insert one if it doesn't exist yet. + pub fn get_or_insert( + &mut self, + current_tick: Tick, + component_id: ComponentId, + value: &dyn FragmentingValue, + caller: MaybeLocation, + ) -> &SharedComponent { + self.values_set + .get_or_insert_with(value, |key| SharedComponent { + component_id, + added: Cell::new(current_tick), + value: SharedFragmentingValue(Arc::from(key.clone_boxed())), + location: caller, + }) + } + + /// Get [`SharedComponent`] for the provided [`FragmentingValue`]. + /// + /// Returns `None` if it isn't stored. + pub fn get(&self, value: &dyn FragmentingValue) -> Option<&SharedComponent> { + self.values_set.get(value) + } + + /// Get [`SharedComponent`] for the provided [`SharedFragmentingValue`]. + /// Should be preferred to using [`get`](Self::get) wherever possible. + /// + /// Returns `None` if it isn't stored. + pub fn get_shared(&self, value: &SharedFragmentingValue) -> Option<&SharedComponent> { + self.values_set.get(value) + } + + /// Clear all the stored values and reset the storage. + /// + /// Does not remove existing [`SharedFragmentingValue`] if they were cloned once before. + pub fn clear(&mut self) { + self.values_set.clear(); + } + + pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { + for component in self.values_set.iter() { + let mut tick = component.added.get(); + tick.check_tick(change_tick); + component.added.set(tick); + } + } +} + +impl Equivalent for dyn FragmentingValue { + fn equivalent(&self, key: &SharedComponent) -> bool { + *self == *key.value.as_ref() + } +} + +impl Equivalent for SharedFragmentingValue { + fn equivalent(&self, key: &SharedComponent) -> bool { + *self == key.value + } +} + +/// A version of [`FragmentingValue`] that stores the value only once and can be cloned. +/// +/// Essentially, this is just a wrapper around `Arc` with some additional type safety. +#[derive(Clone)] +pub struct SharedFragmentingValue(Arc); + +impl SharedFragmentingValue { + /// Try to downcast the stored [`FragmentingValue`] to `C`. + pub fn try_deref(&self) -> Option<&C> { + (self.0.as_ref() as &dyn Any).downcast_ref() + } +} + +impl AsRef for SharedFragmentingValue { + fn as_ref(&self) -> &dyn FragmentingValue { + self.0.as_ref() + } +} + +impl Deref for SharedFragmentingValue { + type Target = dyn FragmentingValue; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl PartialEq for SharedFragmentingValue { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for SharedFragmentingValue {} + +impl Hash for SharedFragmentingValue { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +#[cfg(test)] +mod tests { + use alloc::{vec, vec::Vec}; + + use crate::{ + change_detection::DetectChanges, + component::Component, + entity::Entity, + query::With, + world::{Ref, World}, + }; + + use super::*; + + #[derive(Component, Clone, Hash, PartialEq, Eq, Debug)] + #[component(storage = "Shared")] + struct SharedComponent(Vec); + + #[test] + fn take_shared_value() { + let mut world = World::new(); + let comp = SharedComponent(vec![1, 2, 3]); + let e = world.spawn(comp.clone()).id(); + let taken_comp = world.entity_mut(e).take::(); + assert_eq!(taken_comp, Some(comp)); + assert!(!world.entity(e).contains::()); + } + + #[test] + fn query_shared_components() { + let mut world = World::new(); + let comp1 = SharedComponent(vec![1, 2, 3]); + let comp2 = SharedComponent(vec![0]); + let e1 = world.spawn(comp1.clone()).id(); + let e2 = world.spawn(comp2.clone()).id(); + + let mut query = world.query::<&SharedComponent>(); + assert_eq!(query.get(&world, e1), Ok(&comp1)); + assert_eq!(query.iter(&world).collect::>(), vec![&comp1, &comp2]); + + let mut query = world.query_filtered::>(); + assert_eq!(query.iter(&world).collect::>(), vec![e1, e2]); + + let mut query = world.query::>(); + let comp1_ref = query.get(&world, e1).unwrap(); + assert_eq!(comp1_ref.added(), Tick::new(1)); + } +} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 9842ee54e365d..11a1f9c5df811 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -14,6 +14,7 @@ use crate::{ EntityIdLocation, EntityLocation, }, event::Event, + fragmenting_value::{DynamicFragmentingValue, FragmentingValuesBorrowed}, observer::Observer, query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData}, relationship::RelationshipHookMode, @@ -24,7 +25,7 @@ use crate::{ ON_DESPAWN, ON_REMOVE, ON_REPLACE, }, }; -use alloc::vec::Vec; +use alloc::{boxed::Box, vec::Vec}; use bevy_platform::collections::{HashMap, HashSet}; use bevy_ptr::{OwningPtr, Ptr}; use core::{ @@ -33,6 +34,7 @@ use core::{ hash::{Hash, Hasher}, marker::PhantomData, mem::MaybeUninit, + ptr::NonNull, }; use thiserror::Error; @@ -1835,8 +1837,21 @@ impl<'w> EntityWorldMut<'w> { ) -> &mut Self { let location = self.location(); let change_tick = self.world.change_tick(); - let mut bundle_inserter = - BundleInserter::new::(self.world, location.archetype_id, change_tick); + + let value_components = if T::has_fragmenting_values() { + let mut registrator = self.world.components_registrator(); + FragmentingValuesBorrowed::from_bundle(&mut registrator, &bundle) + } else { + FragmentingValuesBorrowed::default() + }; + + let mut bundle_inserter = BundleInserter::new::( + self.world, + location.archetype_id, + change_tick, + &value_components, + caller, + ); // SAFETY: location matches current entity. `T` matches `bundle_info` let (location, after_effect) = unsafe { bundle_inserter.insert( @@ -1905,9 +1920,22 @@ impl<'w> EntityWorldMut<'w> { component_id, ); let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); + let mut dynamic_value_component = DynamicFragmentingValue::default(); + let value_components = FragmentingValuesBorrowed::from_iter( + dynamic_value_component + .from_component(&self.world.components, component_id, component.as_ref()) + .iter() + .map(|v| (component_id, *v)), + ); - 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, + caller, + ); self.location = Some(insert_dynamic_bundle( bundle_inserter, @@ -1965,14 +1993,47 @@ 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<_> = iter_components.collect(); + let mut dynamic_value_components: Vec<_> = component_ids + .iter() + .zip(components.iter()) + .filter_map(|(id, value)| { + self.world + .components() + .get_info(*id)? + .value_component_vtable() + .map(|_| (*id, value.as_ref(), DynamicFragmentingValue::default())) + }) + .collect(); + let value_components = dynamic_value_components + .iter_mut() + .map(|(id, value, dynamic_value)| + // SAFETY: + // - `id` and `value` match + // - component is always fragmenting since we filtered it beforehand. + unsafe { + ( + *id, + dynamic_value + .from_component(self.world.components(), *id, *value) + .debug_checked_unwrap(), + ) + }) + .collect(); + let bundle_inserter = BundleInserter::new_with_id( + self.world, + location.archetype_id, + bundle_id, + change_tick, + &value_components, + MaybeLocation::caller(), + ); self.location = Some(insert_dynamic_bundle( bundle_inserter, self.entity, location, - iter_components, + components.iter_mut().map(|ptr| ptr.as_mut().promote()), (*storage_types).iter().cloned(), InsertMode::Replace, MaybeLocation::caller(), @@ -2007,31 +2068,56 @@ impl<'w> EntityWorldMut<'w> { entity, location, MaybeLocation::caller(), - |sets, table, components, bundle_components| { + |archetype, sets, table, components, bundle_components| { let mut bundle_components = bundle_components.iter().copied(); - ( + let mut shared_components_tmp_ptrs = Vec::new(); + let result = ( false, - T::from_components(&mut (sets, table), &mut |(sets, table)| { - let component_id = bundle_components.next().unwrap(); - // SAFETY: the component existed to be removed, so its id must be valid. - let component_info = components.get_info_unchecked(component_id); - match component_info.storage_type() { - StorageType::Table => { - table - .as_mut() - // SAFETY: The table must be valid if the component is in it. - .debug_checked_unwrap() - // SAFETY: The remover is cleaning this up. - .take_component(component_id, location.table_row) + T::from_components( + &mut (sets, table, &mut shared_components_tmp_ptrs), + &mut |(sets, table, shared_components_tmp_ptrs)| { + let component_id = bundle_components.next().unwrap(); + // SAFETY: the component existed to be removed, so its id must be valid. + let component_info = components.get_info_unchecked(component_id); + match component_info.storage_type() { + StorageType::Table => { + table + .as_mut() + // SAFETY: The table must be valid if the component is in it. + .debug_checked_unwrap() + // SAFETY: The remover is cleaning this up. + .take_component(component_id, location.table_row) + } + StorageType::SparseSet => sets + .get_mut(component_id) + .unwrap() + .remove_and_forget(entity) + .unwrap(), + StorageType::Shared => { + // This will leak the box and allow us to create OwningPtr for the cloned data. + // We'll clean it up later after the value has been created. + let component_ptr = Box::into_raw( + archetype + .get_value_component(component_id) + .unwrap() + .as_ref() + .clone_boxed(), + ) + .cast::(); + shared_components_tmp_ptrs + .push((component_ptr, component_info.layout())); + OwningPtr::new(NonNull::new_unchecked(component_ptr)) + } } - StorageType::SparseSet => sets - .get_mut(component_id) - .unwrap() - .remove_and_forget(entity) - .unwrap(), - } - }), - ) + }, + ), + ); + // T has been created, so the required data was copied over. + // Now we need to dealloc memory for the leaked boxes from which `from_components` copied data. + for (ptr, layout) in shared_components_tmp_ptrs { + alloc::alloc::dealloc(ptr, layout); + } + result }, ) }; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index b79f189963a84..618bf0736d5b3 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -15,6 +15,7 @@ pub mod unsafe_world_cell; pub mod reflect; use crate::error::{DefaultErrorHandler, ErrorHandler}; +use crate::fragmenting_value::FragmentingValuesBorrowed; pub use crate::{ change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}, world::command_queue::CommandQueue, @@ -1152,7 +1153,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, caller); // 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) }; @@ -2287,6 +2288,10 @@ 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( + &mut self.components_registrator(), + &first_bundle, + ); let mut cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` inserter: unsafe { @@ -2295,6 +2300,8 @@ impl World { first_location.archetype_id, bundle_id, change_tick, + &value_components, + caller, ) }, archetype_id: first_location.archetype_id, @@ -2313,7 +2320,13 @@ 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 location.archetype_id != cache.archetype_id + || B::has_fragmenting_values() + { + let value_components = FragmentingValuesBorrowed::from_bundle( + &mut self.components_registrator(), + &bundle, + ); cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` inserter: unsafe { @@ -2322,6 +2335,8 @@ impl World { location.archetype_id, bundle_id, change_tick, + &value_components, + caller, ) }, archetype_id: location.archetype_id, @@ -2437,6 +2452,10 @@ 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( + &mut self.components_registrator(), + &first_bundle, + ); let mut cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` inserter: unsafe { @@ -2445,6 +2464,8 @@ impl World { first_location.archetype_id, bundle_id, change_tick, + &value_components, + caller, ) }, archetype_id: first_location.archetype_id, @@ -2472,7 +2493,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 location.archetype_id != cache.archetype_id || B::has_fragmenting_values() { + let value_components = FragmentingValuesBorrowed::from_bundle( + &mut self.components_registrator(), + &bundle, + ); cache = InserterArchetypeCache { // SAFETY: we initialized this bundle_id in `register_info` inserter: unsafe { @@ -2481,6 +2506,8 @@ impl World { location.archetype_id, bundle_id, change_tick, + &value_components, + caller, ) }, archetype_id: location.archetype_id, @@ -2950,6 +2977,7 @@ impl World { ref mut sparse_sets, ref mut resources, ref mut non_send_resources, + ref mut shared, } = self.storages; #[cfg(feature = "trace")] @@ -2958,6 +2986,7 @@ impl World { sparse_sets.check_change_ticks(change_tick); resources.check_change_ticks(change_tick); non_send_resources.check_change_ticks(change_tick); + shared.check_change_ticks(change_tick); self.entities.check_change_ticks(change_tick); if let Some(mut schedules) = self.get_resource_mut::() { @@ -2977,6 +3006,7 @@ impl World { /// Despawns all entities in this [`World`]. pub fn clear_entities(&mut self) { self.storages.tables.clear(); + self.storages.shared.clear(); self.storages.sparse_sets.clear_entities(); self.archetypes.clear_entities(); self.entities.clear(); @@ -3883,6 +3913,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(), @@ -3895,6 +3926,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 16bd9bb8059b4..b4d6ec5078eb4 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -6,6 +6,11 @@ use crate::{ }; use core::iter::FusedIterator; +enum BundleSpawnerOrWorld<'w> { + World(&'w mut World), + Spawner(BundleSpawner<'w>), +} + /// An iterator that spawns a series of entities and returns the [ID](Entity) of /// each spawned entity. /// @@ -16,7 +21,7 @@ where I::Item: Bundle, { inner: I, - spawner: BundleSpawner<'w>, + spawner_or_world: BundleSpawnerOrWorld<'w>, caller: MaybeLocation, } @@ -32,18 +37,23 @@ 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 mut spawner = BundleSpawner::new::(world, change_tick); - spawner.reserve_storage(length); - + // We cannot reuse the same spawner if the bundle has any fragmenting value components + // since the target archetype depends on the bundle value and not just its type. + let spawner_or_world = if I::Item::has_fragmenting_values() { + BundleSpawnerOrWorld::World(world) + } else { + let change_tick = world.change_tick(); + let mut spawner = BundleSpawner::new_uniform::(world, change_tick, caller); + spawner.reserve_storage(length); + BundleSpawnerOrWorld::Spawner(spawner) + }; Self { inner: iter, - spawner, + spawner_or_world, caller, } } @@ -58,8 +68,13 @@ 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() }; + match &mut self.spawner_or_world { + BundleSpawnerOrWorld::World(world) => world.flush_commands(), + BundleSpawnerOrWorld::Spawner(bundle_spawner) => { + // SAFETY: `self.spawner` will be dropped immediately after this call. + unsafe { bundle_spawner.flush_commands() } + } + } } } @@ -72,8 +87,18 @@ where fn next(&mut self) -> Option { let bundle = self.inner.next()?; - // SAFETY: bundle matches spawner type - unsafe { Some(self.spawner.spawn(bundle, self.caller).0) } + match &mut self.spawner_or_world { + BundleSpawnerOrWorld::World(world) => { + let change_tick = world.change_tick(); + let mut spawner = BundleSpawner::new(world, change_tick, &bundle, self.caller); + // SAFETY: bundle matches spawner type + unsafe { Some(spawner.spawn(bundle, self.caller).0) } + } + BundleSpawnerOrWorld::Spawner(spawner) => { + // SAFETY: bundle matches spawner type + unsafe { Some(spawner.spawn(bundle, self.caller).0) } + } + } } fn size_hint(&self) -> (usize, Option) { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 3f8298cd2988a..e75735f515eb6 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -18,7 +18,14 @@ use crate::{ }; use bevy_platform::sync::atomic::Ordering; use bevy_ptr::{Ptr, UnsafeCellDeref}; -use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, panic::Location, ptr}; +use core::{ + any::TypeId, + cell::UnsafeCell, + fmt::Debug, + marker::PhantomData, + panic::Location, + ptr::{self, NonNull}, +}; use thiserror::Error; /// Variant of the [`World`] where resource and component accesses take `&self`, and the responsibility to avoid @@ -1236,6 +1243,11 @@ unsafe fn get_component( table.get_component(component_id, location.table_row) } StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get(entity), + StorageType::Shared => world + .archetypes() + .get(location.archetype_id) + .and_then(|archetype| archetype.get_value_component(component_id)) + .map(|v| Ptr::new(NonNull::new_unchecked(ptr::from_ref(v.as_ref()) as *mut u8))), } } @@ -1279,6 +1291,29 @@ unsafe fn get_component_and_ticks( )) } StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_with_ticks(entity), + StorageType::Shared => world + .archetypes() + .get(location.archetype_id) + .and_then(|archetype| archetype.get_value_component(component_id)) + .and_then(|component| world.storages().shared.get_shared(component)) + .map(|component| { + // All of these transformations are safe as long as component is never accessed mutably, + // which should be the case since shared components are always immutable. + ( + Ptr::new(NonNull::new_unchecked( + ptr::from_ref(component.value().as_ref()) as *mut u8, + )), + TickCells { + added: &*(ptr::from_ref(component.added_ref()).cast::>()), + changed: &*(ptr::from_ref(component.added_ref()) + .cast::>()), + }, + component + .location() + .as_ref() + .map(|l| &*(ptr::from_ref(l).cast::>())), + ) + }), } } @@ -1305,6 +1340,15 @@ unsafe fn get_ticks( table.get_ticks_unchecked(component_id, location.table_row) } StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_ticks(entity), + StorageType::Shared => world + .archetypes() + .get(location.archetype_id) + .and_then(|archetype| archetype.get_value_component(component_id)) + .and_then(|component| world.storages().shared.get_shared(component)) + .map(|component| ComponentTicks { + added: component.added(), + changed: component.added(), + }), } } diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 11c469ad1506a..06c86d41ff774 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::{ComponentHook, HookContext, Mutable, StorageType}, + ecs::component::{ComponentHook, HookContext, Mutable, NoKey, StorageType}, prelude::*, }; use std::collections::HashMap; @@ -31,6 +31,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 bd993d44139ba..84b7ed9c080bb 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 5f4f2db8c4a14..ddcff54f265f4 100644 --- a/examples/ecs/immutable_components.rs +++ b/examples/ecs/immutable_components.rs @@ -147,6 +147,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(), @@ -155,6 +156,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 59720f3b5a25c..d027974c15e1b 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..428c1f4b12221 --- /dev/null +++ b/release-content/migration-guides/fragmenting_value_components.md @@ -0,0 +1,15 @@ +--- +title: Added support for fragmenting value components +pull_requests: [19153, 19456] +--- + +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`. + +`StorageType` now has an additional variant - `Shared`. diff --git a/release-content/release-notes/shared_component_storage.md b/release-content/release-notes/shared_component_storage.md new file mode 100644 index 0000000000000..e0c562c5417a8 --- /dev/null +++ b/release-content/release-notes/shared_component_storage.md @@ -0,0 +1,25 @@ +--- +title: Shared component storage +authors: ["@eugineerd"] +pull_requests: [19153, 19456] +--- + +Components can now be stored in `Shared` storage, which is a new memory-efficient storage type for immutable components. + +Each unique component value with this storage type fragments the archetype, which means that all entities within the archetype are guaranteed to have the same value for that component. +This allows to store the component's value once for all entities, not only within the same `Archetype`, but also the `World`. +Since `Shared` components are immutable, modifying them for an entity requires to move it to a different archetype, which means that remove/insert performance of shared components is similar to `SparseSet` components. +On the other hand, iteration performance is more similar to `Table` components because the component value has to be retrieved only once per archetype. +To make it possible to store and compare these values efficiently, the components must implement the following traits: `Clone`, `Hash`, `PartialEq`, `Eq`. + +```rs +#[derive(Component, Clone, Hash, PartialEq, Eq)] +#[component(storage = "Shared")] +enum Faction { + Player, + Enemy, + Custom(u32), +} +``` + +Overall, this storage type is useful for components that are expected to have a limited number of possible values that can exist during the runtime of the app.