From 0200afd2676032e36758c71f43549d0cbddd783c Mon Sep 17 00:00:00 2001 From: eugineerd Date: Mon, 5 May 2025 11:01:13 +0000 Subject: [PATCH 01/20] typed key implementation --- crates/bevy_ecs/macros/src/component.rs | 1 + crates/bevy_ecs/macros/src/lib.rs | 16 +++ crates/bevy_ecs/src/archetype.rs | 46 +++++++-- crates/bevy_ecs/src/bundle.rs | 53 +++++++++- crates/bevy_ecs/src/component.rs | 7 +- crates/bevy_ecs/src/lib.rs | 1 + crates/bevy_ecs/src/shared_component.rs | 123 ++++++++++++++++++++++++ 7 files changed, 234 insertions(+), 13 deletions(-) create mode 100644 crates/bevy_ecs/src/shared_component.rs diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index d3199c090966c..1d2d091ead4b7 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -265,6 +265,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 = #bevy_ecs_path::shared_component::NoKey; fn register_required_components( requiree: #bevy_ecs_path::component::ComponentId, components: &mut #bevy_ecs_path::component::ComponentsRegistrator, diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index a657765ac23f9..43307c39ce2bd 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -78,6 +78,7 @@ 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_value_components = 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 @@ -105,6 +106,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_value_components.push(quote! { + self.#field.get_value_components(components, &mut *ids); + }); } None => { let index = Index::from(i); @@ -114,6 +118,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_value_components.push(quote! { + self.#index.get_value_components(components, &mut *ids); + }); } } } @@ -184,6 +191,15 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { ) { #(#field_get_components)* } + // #[allow(unused_variables)] + // #[inline] + // fn get_value_components( + // &self, + // components: &mut #ecs_path::component::ComponentsRegistrator, + // ids: &mut impl FnMut(#ecs_path::component::ComponentId) + // ) { + // #(#field_get_value_components)* + // } } }) } diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 2468c5e9a8fbf..243c2d72374e4 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -24,12 +24,17 @@ use crate::{ component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, entity::{Entity, EntityLocation}, observer::Observers, + shared_component::SharedComponentKey, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, }; use alloc::{boxed::Box, vec::Vec}; -use bevy_platform::collections::HashMap; +use bevy_platform::{ + collections::HashMap, + hash::{DefaultHasher, FixedHasher}, +}; use core::{ - hash::Hash, + any::Any, + hash::{Hash, Hasher}, ops::{Index, IndexMut, RangeFrom}, }; @@ -197,10 +202,17 @@ impl BundleComponentStatus for SpawnBundleStatus { #[derive(Default)] pub struct Edges { insert_bundle: SparseArray, + insert_value: HashMap, remove_bundle: SparseArray>, take_bundle: SparseArray>, } +#[derive(Hash, Eq, PartialEq)] +struct ValueEdge { + archetype_id: ArchetypeId, + value_components: Box<[Box]>, +} + impl Edges { /// Checks the cache for the target archetype when inserting a bundle into the /// source archetype. @@ -208,9 +220,13 @@ 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 { - self.get_archetype_after_bundle_insert_internal(bundle_id) - .map(|bundle| bundle.archetype_id) + pub fn get_archetype_after_bundle_insert( + &self, + bundle_id: BundleId, + value_components: impl Iterator)>, + ) -> Option { + self.get_archetype_after_bundle_insert_internal(bundle_id, value_components) + .map(|(_, archetype_id)| archetype_id) } /// Internal version of `get_archetype_after_bundle_insert` that @@ -219,8 +235,24 @@ impl Edges { pub(crate) fn get_archetype_after_bundle_insert_internal( &self, bundle_id: BundleId, - ) -> Option<&ArchetypeAfterBundleInsert> { - self.insert_bundle.get(bundle_id) + value_components: impl Iterator)>, + ) -> Option<(&ArchetypeAfterBundleInsert, ArchetypeId)> { + self.insert_bundle.get(bundle_id).and_then(|result| { + let mut value_components: Vec<_> = value_components.collect(); + value_components.sort_by_key(|(id, _)| *id); + let value_components: Box<_> = + value_components.into_iter().map(|(_, key)| key).collect(); + if value_components.is_empty() { + Some((result, result.archetype_id)) + } else { + self.insert_value + .get(&ValueEdge { + archetype_id: result.archetype_id, + value_components, + }) + .map(|&archetype_id| (result, archetype_id)) + } + }) } /// Caches the target archetype when inserting a bundle into the source archetype. diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 5666d90c53a42..6d83d96486412 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -19,6 +19,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, relationship::RelationshipHookMode, + shared_component::{BundleRegisterByValue, SharedComponentKey, SuperKeyTrait}, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE}, }; @@ -26,7 +27,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. @@ -204,6 +208,13 @@ pub trait DynamicBundle { /// ownership of the component values to `func`. #[doc(hidden)] fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect; + + fn get_value_components( + &self, + _components: &mut ComponentsRegistrator, + _ids: &mut impl FnMut(ComponentId, Box), + ) { + } } /// An operation on an [`Entity`] that occurs _after_ inserting the [`Bundle`] that defined this bundle effect. @@ -267,6 +278,17 @@ impl DynamicBundle for C { fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { OwningPtr::make(self, |ptr| func(C::STORAGE_TYPE, ptr)); } + + #[inline] + fn get_value_components( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId, Box), + ) { + if let Some(key) = (self as &dyn Any).downcast_ref::<::KeyType>() { + ids(components.register_component::(), key.clone_key()) + } + } } macro_rules! tuple_impl { @@ -366,6 +388,18 @@ macro_rules! tuple_impl { $name.get_components(&mut *func), )*) } + + #[inline(always)] + fn get_value_components( + &self, + components: &mut ComponentsRegistrator, + ids: &mut impl FnMut(ComponentId, Box), + ) { + let ($($name,)*) = &self; + $( + $name.get_value_components(components, &mut *ids); + )* + } } } } @@ -734,10 +768,11 @@ impl BundleInfo { components: &Components, observers: &Observers, archetype_id: ArchetypeId, + value_components: impl Iterator)>, ) -> 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; } @@ -998,6 +1033,7 @@ impl<'w> BundleInserter<'w> { world: &'w mut World, archetype_id: ArchetypeId, change_tick: Tick, + value_components: impl Iterator)>, ) -> Self { // SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too. let mut registrator = @@ -1006,7 +1042,15 @@ 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, + ) + } } /// Creates a new [`BundleInserter`]. @@ -1019,6 +1063,7 @@ impl<'w> BundleInserter<'w> { archetype_id: ArchetypeId, bundle_id: BundleId, change_tick: Tick, + value_components: impl Iterator)>, ) -> 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); @@ -1033,7 +1078,7 @@ impl<'w> BundleInserter<'w> { if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; // SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype - let archetype_after_insert = unsafe { + let (archetype_after_insert, _) = unsafe { archetype .edges() .get_archetype_after_bundle_insert_internal(bundle_id) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index bfa55804b616a..e16bb55d52c32 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -8,6 +8,7 @@ use crate::{ query::DebugCheckedUnwrap, relationship::RelationshipHookMode, resource::Resource, + shared_component::{SharedComponentKey, SuperKeyTrait}, storage::{SparseSetIndex, SparseSets, Table, TableRow}, system::{Local, SystemParam}, world::{DeferredWorld, FromWorld, World}, @@ -486,6 +487,8 @@ pub trait Component: Send + Sync + 'static { /// A constant indicating the storage type used for this component. const STORAGE_TYPE: StorageType; + type Key: SuperKeyTrait; + /// 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`]. @@ -1722,7 +1725,7 @@ impl<'w> ComponentsRegistrator<'w> { &mut self, recursion_check_stack: &mut Vec, ) -> ComponentId { - let type_id = TypeId::of::(); + let type_id = TypeId::of::<::RegisterComponent>(); if let Some(id) = self.indices.get(&type_id) { return *id; } @@ -1761,7 +1764,7 @@ impl<'w> ComponentsRegistrator<'w> { unsafe { self.register_component_inner(id, ComponentDescriptor::new::()); } - let type_id = TypeId::of::(); + let type_id = TypeId::of::<::RegisterComponent>(); let prev = self.indices.insert(type_id, id); debug_assert!(prev.is_none()); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 99f95763d572a..6bb57d033ce53 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -52,6 +52,7 @@ pub mod relationship; pub mod removal_detection; pub mod resource; pub mod schedule; +pub mod shared_component; pub mod spawn; pub mod storage; pub mod system; diff --git a/crates/bevy_ecs/src/shared_component.rs b/crates/bevy_ecs/src/shared_component.rs new file mode 100644 index 0000000000000..bbe5ea4c096c4 --- /dev/null +++ b/crates/bevy_ecs/src/shared_component.rs @@ -0,0 +1,123 @@ +use alloc::boxed::Box; +use bevy_ecs::component::Component; +use bevy_platform::hash::FixedHasher; +use core::{ + any::Any, + hash::{BuildHasher, Hash, Hasher}, + marker::PhantomData, +}; + +use crate::component::{ComponentId, ComponentsRegistrator, StorageType}; + +#[derive(Component)] +#[component(storage = "SparseSet")] +pub struct SharedComponentStorage; + +pub struct SharedComponents {} + +pub trait SharedComponentKey: Any { + fn eq_key(&self, other: &dyn SharedComponentKey) -> bool; + fn hash_key(&self) -> u64; + fn clone_key(&self) -> Box; +} + +impl SharedComponentKey for T +where + T: Component + Eq + Hash + Any + Clone, +{ + fn eq_key(&self, other: &dyn SharedComponentKey) -> bool { + let other: &dyn Any = other; + if let Some(other) = other.downcast_ref::() { + return self == other; + } + false + } + + fn hash_key(&self) -> u64 { + FixedHasher.hash_one(&self) + } + + fn clone_key(&self) -> Box { + Box::new(self.clone()) + } +} + +impl SharedComponentKey for () { + fn eq_key(&self, other: &dyn SharedComponentKey) -> bool { + (other as &dyn Any).is::() + } + + fn hash_key(&self) -> u64 { + FixedHasher.hash_one(&self) + } + + fn clone_key(&self) -> Box { + Box::new(*self) + } +} + +impl PartialEq for Box { + fn eq(&self, other: &Self) -> bool { + SharedComponentKey::eq_key(self.as_ref(), other.as_ref()) + } +} + +impl Eq for Box {} + +impl Hash for Box { + fn hash(&self, state: &mut H) { + let key_hash = SharedComponentKey::hash_key(self.as_ref()); + state.write_u64(key_hash); + } +} + +pub trait BundleRegisterByValue {} + +impl BundleRegisterByValue for T where T: Eq + Hash + Any + Clone + Component {} + +pub trait SuperKeyTrait { + type RegisterComponent: Component; + type KeyType: SharedComponentKey; + type ValueType: Component; + fn make_value_from_key(key: &Self::KeyType) -> Self::ValueType; +} + +pub struct SelfKey(PhantomData); +impl SuperKeyTrait for SelfKey { + type RegisterComponent = C; + type KeyType = C; + type ValueType = C; + fn make_value_from_key(key: &Self::KeyType) -> Self::ValueType { + Clone::clone(key) + } +} + +pub struct NoKey(PhantomData); +impl SuperKeyTrait for NoKey { + type RegisterComponent = C; + type KeyType = (); + type ValueType = C; + fn make_value_from_key(_key: &Self::KeyType) -> Self::ValueType { + panic!("NoKey: No key value") + } +} + +pub struct ComponentKey(PhantomData<(C, K)>); +impl SuperKeyTrait for ComponentKey { + type RegisterComponent = C; + type KeyType = K; + type ValueType = C; + fn make_value_from_key(_key: &Self::KeyType) -> Self::ValueType { + unimplemented!() + } +} + +pub struct KeyFor(PhantomData<(C, V)>); +impl SuperKeyTrait for KeyFor { + type RegisterComponent = V; + type KeyType = (); + type ValueType = C; + fn make_value_from_key(_key: &Self::KeyType) -> Self::ValueType { + panic!("KeyFor: No key value") + } +} From 6c4bf2074422323a24620b85fe126dc1b647d54b Mon Sep 17 00:00:00 2001 From: eugineerd Date: Fri, 9 May 2025 21:46:10 +0000 Subject: [PATCH 02/20] Implement fragmenting value components --- crates/bevy_core_pipeline/src/oit/mod.rs | 1 + crates/bevy_ecs/macros/src/component.rs | 36 +- crates/bevy_ecs/macros/src/lib.rs | 38 +- crates/bevy_ecs/src/archetype.rs | 109 ++- crates/bevy_ecs/src/bundle.rs | 231 ++++-- crates/bevy_ecs/src/component.rs | 74 +- crates/bevy_ecs/src/entity/clone_entities.rs | 5 +- crates/bevy_ecs/src/fragmenting_value.rs | 676 ++++++++++++++++++ crates/bevy_ecs/src/lib.rs | 2 +- .../bevy_ecs/src/observer/entity_observer.rs | 3 +- crates/bevy_ecs/src/observer/runner.rs | 4 +- crates/bevy_ecs/src/query/state.rs | 132 +++- crates/bevy_ecs/src/shared_component.rs | 123 ---- crates/bevy_ecs/src/spawn.rs | 32 + crates/bevy_ecs/src/system/query.rs | 44 +- crates/bevy_ecs/src/world/entity_ref.rs | 51 +- crates/bevy_ecs/src/world/mod.rs | 44 +- crates/bevy_ecs/src/world/spawn_batch.rs | 47 +- crates/bevy_render/src/sync_world.rs | 3 +- examples/ecs/component_hooks.rs | 3 +- examples/ecs/dynamic.rs | 5 +- examples/ecs/immutable_components.rs | 2 + examples/stress_tests/many_components.rs | 2 + 23 files changed, 1405 insertions(+), 262 deletions(-) create mode 100644 crates/bevy_ecs/src/fragmenting_value.rs delete mode 100644 crates/bevy_ecs/src/shared_component.rs diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index 6a15fd126c86b..6453b0a682656 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -70,6 +70,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 1d2d091ead4b7..5f9d2fcd11f6d 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -258,6 +258,34 @@ pub fn derive_component(input: TokenStream) -> TokenStream { ) }; + let key = if let Some(key) = attrs.key { + // This check exists only to show better errors earlier and is not used for checking correctness, + // so it's okay for it to not work in 100% of cases. + if let Type::Path(type_path) = &key { + if let Some(ident) = type_path.path.get_ident() { + let ident = ident.to_string(); + if (ident == "Self" || *struct_name == ident) + && matches!(attrs.storage, StorageTy::Table) + { + return syn::Error::new( + ast.span(), + "Component key must not have Table storage type.", + ) + .into_compile_error() + .into(); + } + } + if !attrs.immutable { + return syn::Error::new(ast.span(), "Component key must be immutable.") + .into_compile_error() + .into(); + } + } + quote! {#bevy_ecs_path::component::OtherComponentKey} + } 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! { @@ -265,7 +293,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 = #bevy_ecs_path::shared_component::NoKey; + type Key = #key; fn register_required_components( requiree: #bevy_ecs_path::component::ComponentId, components: &mut #bevy_ecs_path::component::ComponentsRegistrator, @@ -397,6 +425,7 @@ pub const ON_REMOVE: &str = "on_remove"; pub const ON_DESPAWN: &str = "on_despawn"; pub const IMMUTABLE: &str = "immutable"; +pub const KEY: &str = "key"; /// All allowed attribute value expression kinds for component hooks #[derive(Debug)] @@ -459,6 +488,7 @@ struct Attrs { relationship: Option, relationship_target: Option, immutable: bool, + key: Option, } #[derive(Clone, Copy)] @@ -497,6 +527,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { relationship: None, relationship_target: None, immutable: false, + key: None, }; let mut require_paths = HashSet::new(); @@ -532,6 +563,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } else if nested.path.is_ident(IMMUTABLE) { attrs.immutable = true; Ok(()) + } else if nested.path.is_ident(KEY) { + attrs.key = Some(nested.value()?.parse()?); + Ok(()) } else { Err(nested.error("Unsupported attribute")) } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 43307c39ce2bd..3888650f66000 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -78,7 +78,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_value_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 @@ -98,6 +99,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! { @@ -106,8 +110,8 @@ 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_value_components.push(quote! { - self.#field.get_value_components(components, &mut *ids); + field_get_fragmenting_values.push(quote! { + self.#field.get_fragmenting_values(components, &mut *values); }); } None => { @@ -118,8 +122,8 @@ 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_value_components.push(quote! { - self.#index.get_value_components(components, &mut *ids); + field_get_fragmenting_values.push(quote! { + self.#index.get_fragmenting_values(components, &mut *values); }); } } @@ -163,6 +167,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: @@ -191,15 +210,6 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { ) { #(#field_get_components)* } - // #[allow(unused_variables)] - // #[inline] - // fn get_value_components( - // &self, - // components: &mut #ecs_path::component::ComponentsRegistrator, - // ids: &mut impl FnMut(#ecs_path::component::ComponentId) - // ) { - // #(#field_get_value_components)* - // } } }) } diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 243c2d72374e4..f8e62672a970e 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -23,18 +23,14 @@ use crate::{ bundle::BundleId, component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, entity::{Entity, EntityLocation}, + fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed, FragmentingValuesOwned}, observer::Observers, - shared_component::SharedComponentKey, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, }; use alloc::{boxed::Box, vec::Vec}; -use bevy_platform::{ - collections::HashMap, - hash::{DefaultHasher, FixedHasher}, -}; +use bevy_platform::collections::HashMap; use core::{ - any::Any, - hash::{Hash, Hasher}, + hash::Hash, ops::{Index, IndexMut, RangeFrom}, }; @@ -139,6 +135,8 @@ 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, + + pub by_value_archetype_id: HashMap, } impl ArchetypeAfterBundleInsert { @@ -202,17 +200,10 @@ impl BundleComponentStatus for SpawnBundleStatus { #[derive(Default)] pub struct Edges { insert_bundle: SparseArray, - insert_value: HashMap, remove_bundle: SparseArray>, take_bundle: SparseArray>, } -#[derive(Hash, Eq, PartialEq)] -struct ValueEdge { - archetype_id: ArchetypeId, - value_components: Box<[Box]>, -} - impl Edges { /// Checks the cache for the target archetype when inserting a bundle into the /// source archetype. @@ -223,10 +214,16 @@ impl Edges { pub fn get_archetype_after_bundle_insert( &self, bundle_id: BundleId, - value_components: impl Iterator)>, + value_components: &FragmentingValuesBorrowed, ) -> Option { - self.get_archetype_after_bundle_insert_internal(bundle_id, value_components) - .map(|(_, archetype_id)| archetype_id) + self.get_archetype_after_bundle_insert_internal(bundle_id) + .and_then(|bundle| { + if value_components.is_empty() { + Some(bundle.archetype_id) + } else { + bundle.by_value_archetype_id.get(value_components).copied() + } + }) } /// Internal version of `get_archetype_after_bundle_insert` that @@ -235,24 +232,8 @@ impl Edges { pub(crate) fn get_archetype_after_bundle_insert_internal( &self, bundle_id: BundleId, - value_components: impl Iterator)>, - ) -> Option<(&ArchetypeAfterBundleInsert, ArchetypeId)> { - self.insert_bundle.get(bundle_id).and_then(|result| { - let mut value_components: Vec<_> = value_components.collect(); - value_components.sort_by_key(|(id, _)| *id); - let value_components: Box<_> = - value_components.into_iter().map(|(_, key)| key).collect(); - if value_components.is_empty() { - Some((result, result.archetype_id)) - } else { - self.insert_value - .get(&ValueEdge { - archetype_id: result.archetype_id, - value_components, - }) - .map(|&archetype_id| (result, archetype_id)) - } - }) + ) -> Option<&ArchetypeAfterBundleInsert> { + self.insert_bundle.get(bundle_id) } /// Caches the target archetype when inserting a bundle into the source archetype. @@ -274,10 +255,24 @@ impl Edges { required_components, added, existing, + by_value_archetype_id: Default::default(), }, ); } + pub(crate) fn cache_archetype_value_components_after_bundle_insert( + &mut self, + bundle_id: BundleId, + value_components: FragmentingValuesOwned, + value_archetype_id: ArchetypeId, + ) { + if let Some(bundle) = self.insert_bundle.get_mut(bundle_id) { + bundle + .by_value_archetype_id + .insert(value_components, value_archetype_id); + } + } + /// Checks the cache for the target archetype when removing a bundle from the /// source archetype. /// @@ -374,6 +369,7 @@ pub(crate) struct ArchetypeSwapRemoveResult { struct ArchetypeComponentInfo { storage_type: StorageType, archetype_component_id: ArchetypeComponentId, + fragmenting_value: Option>, } bitflags::bitflags! { @@ -392,6 +388,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); } } @@ -412,7 +409,7 @@ pub struct Archetype { impl Archetype { /// `table_components` and `sparse_set_components` must be sorted - pub(crate) fn new( + pub(crate) fn new<'a>( components: &Components, component_index: &mut ComponentIndex, observers: &Observers, @@ -420,6 +417,7 @@ impl Archetype { table_id: TableId, table_components: impl Iterator, sparse_set_components: impl Iterator, + value_components: impl Iterator, ) -> Self { let (min_table, _) = table_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint(); @@ -435,6 +433,7 @@ impl Archetype { ArchetypeComponentInfo { storage_type: StorageType::Table, archetype_component_id, + fragmenting_value: None, }, ); // NOTE: the `table_components` are sorted AND they were inserted in the `Table` in the same @@ -456,6 +455,7 @@ impl Archetype { ArchetypeComponentInfo { storage_type: StorageType::SparseSet, archetype_component_id, + fragmenting_value: None, }, ); component_index @@ -463,6 +463,14 @@ impl Archetype { .or_default() .insert(id, ArchetypeRecord { column: None }); } + + for (component_id, archetype_component_id) in value_components { + if let Some(info) = archetype_components.get_mut(component_id) { + info.fragmenting_value = Some(archetype_component_id.clone_boxed()); + } + flags.insert(ArchetypeFlags::HAS_VALUE_COMPONENTS); + } + Self { id, table_id, @@ -549,6 +557,14 @@ impl Archetype { .map(|(component_id, info)| (*component_id, info.archetype_component_id)) } + pub(crate) fn components_with_fragmenting_values( + &self, + ) -> impl Iterator { + self.components + .iter() + .filter_map(|(id, info)| Some((*id, info.fragmenting_value.as_ref()?.as_ref()))) + } + /// Fetches an immutable reference to the archetype's [`Edges`], a cache of /// archetypal relationships. #[inline] @@ -677,6 +693,21 @@ impl Archetype { .map(|info| info.archetype_component_id) } + /// Returns [`FragmentingValue`] for this archetype of the requested `component_id`. + /// + /// This will return `None` if requested component isn't a part of this archetype or isn't fragmenting. + pub fn get_value_component(&self, component_id: ComponentId) -> Option<&dyn FragmentingValue> { + self.components + .get(component_id) + .and_then(|info| info.fragmenting_value.as_ref()) + .map(AsRef::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(); @@ -772,6 +803,7 @@ impl ArchetypeGeneration { struct ArchetypeComponents { table_components: Box<[ComponentId]>, sparse_set_components: Box<[ComponentId]>, + value_components: FragmentingValuesOwned, } /// An opaque unique joint ID for a [`Component`] in an [`Archetype`] within a [`World`]. @@ -860,6 +892,7 @@ impl Archetypes { TableId::empty(), Vec::new(), Vec::new(), + Default::default(), ); } archetypes @@ -965,10 +998,12 @@ impl Archetypes { table_id: TableId, table_components: Vec, sparse_set_components: Vec, + value_components: FragmentingValuesOwned, ) -> 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; @@ -981,6 +1016,7 @@ impl Archetypes { let ArchetypeComponents { table_components, sparse_set_components, + value_components, } = identity; let id = ArchetypeId::new(archetypes.len()); let table_start = *archetype_component_count; @@ -1005,6 +1041,7 @@ impl Archetypes { .iter() .copied() .zip(sparse_set_archetype_components), + value_components.iter_ids_and_values(), )); id }) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 6d83d96486412..7ed58c8124dbc 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -11,15 +11,15 @@ 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, relationship::RelationshipHookMode, - shared_component::{BundleRegisterByValue, SharedComponentKey, SuperKeyTrait}, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE}, }; @@ -165,6 +165,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. @@ -208,13 +218,6 @@ pub trait DynamicBundle { /// ownership of the component values to `func`. #[doc(hidden)] fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect; - - fn get_value_components( - &self, - _components: &mut ComponentsRegistrator, - _ids: &mut impl FnMut(ComponentId, Box), - ) { - } } /// An operation on an [`Entity`] that occurs _after_ inserting the [`Bundle`] that defined this bundle effect. @@ -255,6 +258,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: @@ -278,17 +297,6 @@ impl DynamicBundle for C { fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) -> Self::Effect { OwningPtr::make(self, |ptr| func(C::STORAGE_TYPE, ptr)); } - - #[inline] - fn get_value_components( - &self, - components: &mut ComponentsRegistrator, - ids: &mut impl FnMut(ComponentId, Box), - ) { - if let Some(key) = (self as &dyn Any).downcast_ref::<::KeyType>() { - ids(components.register_component::(), key.clone_key()) - } - } } macro_rules! tuple_impl { @@ -324,6 +332,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( @@ -388,18 +413,6 @@ macro_rules! tuple_impl { $name.get_components(&mut *func), )*) } - - #[inline(always)] - fn get_value_components( - &self, - components: &mut ComponentsRegistrator, - ids: &mut impl FnMut(ComponentId, Box), - ) { - let ($($name,)*) = &self; - $( - $name.get_value_components(components, &mut *ids); - )* - } } } } @@ -768,7 +781,7 @@ impl BundleInfo { components: &Components, observers: &Observers, archetype_id: ArchetypeId, - value_components: impl Iterator)>, + value_components: &FragmentingValuesBorrowed, ) -> ArchetypeId { if let Some(archetype_after_insert_id) = archetypes[archetype_id] .edges() @@ -777,6 +790,8 @@ impl BundleInfo { return archetype_after_insert_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(); @@ -817,22 +832,68 @@ impl BundleInfo { } } + 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 != *component_value { + replaced_value_components.insert(component_id, component_value); + } + } else { + new_value_components.push((component_id, component_value)); + } + } + if new_table_components.is_empty() && new_sparse_set_components.is_empty() { - let edges = current_archetype.edges_mut(); // 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, + ); + if replaced_value_components.is_empty() { + return archetype_id; + } + // If any of the value components got replaced, this change should point to a new archetype + 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 archetype_value_components = current_archetype + .components_with_fragmenting_values() + .map(|(component_id, value)| { + ( + component_id, + replaced_value_components + .get(&component_id) + .copied() + .unwrap_or(value), + ) + }) + .collect(); + let value_archetype_id = archetypes.get_id_or_insert( + components, + observers, + table_id, + table_components, + sparse_set_components, + archetype_value_components, ); - archetype_id + archetypes[archetype_id] + .edges_mut() + .cache_archetype_value_components_after_bundle_insert( + self.id, + value_components.to_owned(), + value_archetype_id, + ); + value_archetype_id } else { let table_id; let table_components; let sparse_set_components; + let archetype_value_components; // The archetype changes when we insert this bundle. Prepare the new archetype and storages. { let current_archetype = &archetypes[archetype_id]; @@ -862,27 +923,64 @@ impl BundleInfo { new_sparse_set_components.sort_unstable(); new_sparse_set_components }; + + archetype_value_components = current_archetype + .components_with_fragmenting_values() + .map(|(component_id, value)| { + ( + component_id, + *replaced_value_components + .get(&component_id) + .unwrap_or(&value), + ) + }) + .chain(new_value_components) + .collect(); }; + // 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 + + if value_components.is_empty() { + return new_base_archetype_id; + } + + let new_value_archetype_id = archetypes.get_id_or_insert( + components, + observers, + table_id, + table_components, + sparse_set_components, + archetype_value_components, + ); + + archetypes[archetype_id] + .edges_mut() + .cache_archetype_value_components_after_bundle_insert( + self.id, + value_components.to_owned(), + new_value_archetype_id, + ); + + new_value_archetype_id } } @@ -926,6 +1024,7 @@ 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]; @@ -973,6 +1072,14 @@ 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() + }) + .collect(); } let new_archetype_id = archetypes.get_id_or_insert( @@ -981,6 +1088,7 @@ impl BundleInfo { next_table_id, next_table_components, next_sparse_set_components, + next_value_components, ); Some(new_archetype_id) }; @@ -1033,7 +1141,7 @@ impl<'w> BundleInserter<'w> { world: &'w mut World, archetype_id: ArchetypeId, change_tick: Tick, - value_components: impl Iterator)>, + value_components: &FragmentingValuesBorrowed, ) -> Self { // SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too. let mut registrator = @@ -1063,7 +1171,7 @@ impl<'w> BundleInserter<'w> { archetype_id: ArchetypeId, bundle_id: BundleId, change_tick: Tick, - value_components: impl Iterator)>, + value_components: &FragmentingValuesBorrowed, ) -> Self { // SAFETY: We will not make any accesses to the command queue, component or resource data of this world let bundle_info = world.bundles.get_unchecked(bundle_id); @@ -1074,11 +1182,12 @@ impl<'w> BundleInserter<'w> { &world.components, &world.observers, archetype_id, + value_components, ); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; // SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype - let (archetype_after_insert, _) = unsafe { + let archetype_after_insert = unsafe { archetype .edges() .get_archetype_after_bundle_insert_internal(bundle_id) @@ -1417,15 +1526,29 @@ pub(crate) struct BundleSpawner<'w> { impl<'w> BundleSpawner<'w> { #[inline] - pub fn new(world: &'w mut World, change_tick: Tick) -> Self { + pub fn new(world: &'w mut World, change_tick: Tick, bundle: &T) -> Self { + // 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) } + } + + #[inline] + pub(crate) fn new_uniform(world: &'w mut World, change_tick: Tick) -> Self { // SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too. let mut registrator = unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) }; let bundle_id = world .bundles .register_info::(&mut registrator, &mut world.storages); + 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) } } /// Creates a new [`BundleSpawner`]. @@ -1437,6 +1560,7 @@ impl<'w> BundleSpawner<'w> { world: &'w mut World, bundle_id: BundleId, change_tick: Tick, + value_components: &FragmentingValuesBorrowed, ) -> Self { let bundle_info = world.bundles.get_unchecked(bundle_id); let new_archetype_id = bundle_info.insert_bundle_into_archetype( @@ -1445,6 +1569,7 @@ impl<'w> BundleSpawner<'w> { &world.components, &world.observers, ArchetypeId::EMPTY, + value_components, ); let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index e16bb55d52c32..b5f16e7c7ae59 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -5,10 +5,10 @@ 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, - shared_component::{SharedComponentKey, SuperKeyTrait}, storage::{SparseSetIndex, SparseSets, Table, TableRow}, system::{Local, SystemParam}, world::{DeferredWorld, FromWorld, World}, @@ -487,7 +487,8 @@ pub trait Component: Send + Sync + 'static { /// A constant indicating the storage type used for this component. const STORAGE_TYPE: StorageType; - type Key: SuperKeyTrait; + /// 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`], @@ -564,6 +565,12 @@ pub trait Component: Send + Sync + 'static { /// Fields with `#[entities]` must implement [`MapEntities`](crate::entity::MapEntities). #[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 { @@ -621,6 +628,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: () = (); +} + +/// 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: () = { + if matches!(K::STORAGE_TYPE, StorageType::Table) { + panic!("Key component must not have Table storage type") + } + }; +} + +/// 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 @@ -994,6 +1043,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 @@ -1072,6 +1128,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 @@ -1111,6 +1168,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::(), } } @@ -1119,6 +1177,10 @@ 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` + /// - mutable should be `false` + /// - storage type must not be `StorageType::Table` pub unsafe fn new_with_layout( name: impl Into>, storage_type: StorageType, @@ -1126,6 +1188,7 @@ impl ComponentDescriptor { drop: Option unsafe fn(OwningPtr<'a>)>, mutable: bool, clone_behavior: ComponentCloneBehavior, + fragmenting_value_vtable: Option, ) -> Self { Self { name: name.into(), @@ -1136,6 +1199,7 @@ impl ComponentDescriptor { drop, mutable, clone_behavior, + fragmenting_value_vtable, } } @@ -1154,6 +1218,7 @@ impl ComponentDescriptor { drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, clone_behavior: ComponentCloneBehavior::Default, + fragmenting_value_vtable: None, } } @@ -1167,6 +1232,7 @@ impl ComponentDescriptor { drop: needs_drop::().then_some(Self::drop_ptr:: as _), mutable: true, clone_behavior: ComponentCloneBehavior::Default, + fragmenting_value_vtable: None, } } @@ -1725,7 +1791,7 @@ impl<'w> ComponentsRegistrator<'w> { &mut self, recursion_check_stack: &mut Vec, ) -> ComponentId { - let type_id = TypeId::of::<::RegisterComponent>(); + let type_id = TypeId::of::(); if let Some(id) = self.indices.get(&type_id) { return *id; } @@ -1764,7 +1830,7 @@ impl<'w> ComponentsRegistrator<'w> { unsafe { self.register_component_inner(id, ComponentDescriptor::new::()); } - let type_id = TypeId::of::<::RegisterComponent>(); + let type_id = TypeId::of::(); let prev = self.indices.insert(type_id, id); debug_assert!(prev.is_none()); diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index bd8eb2b4bd71b..35cee0a5b8322 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -323,13 +323,14 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> { /// Here's an example of how to do it using [`clone_behavior`](Component::clone_behavior): /// ``` /// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::component::{StorageType, ComponentCloneBehavior, Mutable}; +/// # use bevy_ecs::component::{StorageType, ComponentCloneBehavior, Mutable, NoKey}; /// #[derive(Clone)] /// struct SomeComponent; /// /// impl Component for SomeComponent { /// const STORAGE_TYPE: StorageType = StorageType::Table; /// type Mutability = Mutable; +/// type Key = NoKey; /// fn clone_behavior() -> ComponentCloneBehavior { /// ComponentCloneBehavior::clone::() /// } @@ -1296,6 +1297,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", @@ -1304,6 +1306,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..b2ad974f5eda7 --- /dev/null +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -0,0 +1,676 @@ +//! 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}, + borrow::Borrow, + hash::{BuildHasher, Hash, Hasher}, + ptr::NonNull, +}; +use indexmap::Equivalent; + +use crate::{ + bundle::Bundle, + component::{ComponentId, ComponentKey, Components, ComponentsRegistrator, Immutable}, +}; + +/// 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`]. + 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 { + FragmentingValue::value_eq(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); + } +} + +/// 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)] +pub struct FragmentingValuesOwned { + values: Box<[(ComponentId, Box)]>, +} + +impl FragmentingValuesOwned { + /// 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.as_ref())) + } +} + +impl> FromIterator<(ComponentId, T)> for FragmentingValuesOwned { + fn from_iter>(iter: I) -> Self { + let mut values = Vec::new(); + for (id, value) in iter { + values.push((id, value.borrow().clone_boxed())); + } + values.sort_unstable_by_key(|(id, _)| *id); + FragmentingValuesOwned { + values: values.into_boxed_slice(), + } + } +} + +/// 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 [`FragmentingValuesOwned`] keys. +#[derive(Hash, PartialEq, Eq, Default)] +pub struct FragmentingValuesBorrowed<'a> { + values: Vec<(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 } + } + + /// Returns `true` if there are no fragmenting values. + pub fn is_empty(&self) -> bool { + self.values.is_empty() + } + + /// Creates [`FragmentingValuesOwned`] by cloning all fragmenting values. + pub fn to_owned(&self) -> FragmentingValuesOwned { + FragmentingValuesOwned { + values: self + .values + .iter() + .map(|(id, v)| (*id, v.clone_boxed())) + .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 } + } +} + +impl<'a> Equivalent for FragmentingValuesBorrowed<'a> { + fn equivalent(&self, key: &FragmentingValuesOwned) -> bool { + self.values.len() == key.values.len() + && self + .values + .iter() + .zip(key.values.iter()) + .all(|((id1, v1), (id2, v2))| id1 == id2 && **v1 == **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) + } +} + +/// Dynamic vtable for [`FragmentingValue`]. +/// This is used by [`crate::component::ComponentDescriptor`] to work with dynamic fragmenting components. +#[derive(Clone, Copy)] +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() }.value_eq(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::{ + component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, + entity::Entity, + fragmenting_value::{FragmentingValue, FragmentingValueVtable}, + query::With, + system::{Query, RunSystemOnce}, + world::World, + }; + use alloc::boxed::Box; + use alloc::vec::Vec; + use bevy_platform::hash::FixedHasher; + use bevy_ptr::OwningPtr; + use core::hash::Hash; + + #[derive(Component, Clone, Eq, PartialEq, Hash)] + #[component( + key=Self, + storage="SparseSet", + immutable, + )] + 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 query_fragmenting_components() { + let mut world = World::default(); + _ = world.spawn_batch([ + Fragmenting(1), + Fragmenting(2), + Fragmenting(1), + Fragmenting(3), + Fragmenting(1), + ]); + + fn system_fragmenting_all(q: Query<&Fragmenting>) { + assert_eq!(q.iter().len(), 5); + } + + fn system_fragmenting_1(q: Query<&Fragmenting>) { + assert_eq!( + q.filter_by_component_value(&Fragmenting(1)) + .query() + .iter() + .len(), + 3 + ); + } + + fn system_fragmenting_2(q: Query<&Fragmenting>) { + assert_eq!( + q.filter_by_component_value(&Fragmenting(2)) + .query() + .iter() + .len(), + 1 + ); + } + + fn system_fragmenting_3(q: Query<&Fragmenting>) { + assert_eq!( + q.filter_by_component_value(&Fragmenting(3)) + .query() + .iter() + .len(), + 1 + ); + } + + fn system_fragmenting_1_with_filter(q: Query>) { + assert_eq!( + q.filter_by_component_value(&Fragmenting(1)) + .query() + .iter() + .len(), + 3 + ); + } + + world.run_system_once(system_fragmenting_all).unwrap(); + world.run_system_once(system_fragmenting_1).unwrap(); + world.run_system_once(system_fragmenting_2).unwrap(); + world.run_system_once(system_fragmenting_3).unwrap(); + world + .run_system_once(system_fragmenting_1_with_filter) + .unwrap(); + } + + #[test] + fn query_fragmenting_components_multiple() { + #[derive(Component, Clone, Eq, PartialEq, Hash)] + #[component( + key=Self, + storage="SparseSet", + immutable, + )] + struct Fragmenting2(u32); + + let mut world = World::default(); + _ = world.spawn_batch([ + (Fragmenting(1), Fragmenting2(2)), + (Fragmenting(1), Fragmenting2(1)), + (Fragmenting(1), Fragmenting2(2)), + ]); + + fn system_fragmenting(q: Query<(&Fragmenting, &Fragmenting2)>) { + let len = q + .filter_by_component_value(&Fragmenting(1)) + .query() + .filter_by_component_value(&Fragmenting2(2)) + .query() + .iter() + .len(); + assert_eq!(len, 2); + } + + world.run_system_once(system_fragmenting).unwrap(); + } + + #[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); + } +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 6bb57d033ce53..0350472d5480d 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 identifier; pub mod intern; @@ -52,7 +53,6 @@ pub mod relationship; pub mod removal_detection; pub mod resource; pub mod schedule; -pub mod shared_component; pub mod spawn; pub mod storage; pub mod system; diff --git a/crates/bevy_ecs/src/observer/entity_observer.rs b/crates/bevy_ecs/src/observer/entity_observer.rs index d69f7764fe489..5aeff9f6580b1 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}, observer::ObserverState, @@ -15,6 +15,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 d68c495dabf55..6d4924768c1c3 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, vec::Vec}; use core::any::Any; use crate::{ - component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType}, + component::{ComponentHook, ComponentId, HookContext, Mutable, NoKey, StorageType}, error::{default_error_handler, ErrorContext}, observer::{ObserverDescriptor, ObserverTrigger}, prelude::*, @@ -65,6 +65,7 @@ impl ObserverState { impl Component for ObserverState { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; + type Key = NoKey; fn on_add() -> Option { Some(|mut world, HookContext { entity, .. }| { @@ -336,6 +337,7 @@ impl Observer { impl Component for Observer { const STORAGE_TYPE: StorageType = StorageType::SparseSet; type Mutability = Mutable; + type Key = NoKey; fn on_add() -> Option { Some(|world, context| { let Some(observe) = world.get::(context.entity) else { diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e9a00f4646e1f..a4feb21a66d96 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1,8 +1,9 @@ use crate::{ - archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, - component::{ComponentId, Tick}, + archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId, Archetypes}, + component::{ComponentId, Components, Tick}, entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, entity_disabling::DefaultQueryFilters, + fragmenting_value::FragmentingValue, prelude::FromWorld, query::{Access, FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, storage::{SparseSetIndex, TableId}, @@ -1645,6 +1646,133 @@ impl QueryState { .fold(accum, &mut func); }); } + + fn clone_state(&self, components: &Components) -> QueryState { + // This should always succeed since original query exists. + let fetch_state = D::get_state(components).unwrap(); + let filter_state = F::get_state(components).unwrap(); + + QueryState { + world_id: self.world_id, + archetype_generation: self.archetype_generation, + matched_storage_ids: self.matched_storage_ids.clone(), + is_dense: self.is_dense, + fetch_state, + filter_state, + component_access: self.component_access.clone(), + matched_tables: self.matched_tables.clone(), + matched_archetypes: self.matched_archetypes.clone(), + #[cfg(feature = "trace")] + par_iter_span: self.par_iter_span.clone(), + } + } + + fn filter_by_component_id_and_value_internal( + &self, + archetypes: &Archetypes, + component_id: ComponentId, + value_component: &dyn FragmentingValue, + fetch_state: D::State, + filter_state: F::State, + ) -> QueryState { + let mut matched_archetypes = FixedBitSet::with_capacity(self.matched_archetypes.len()); + let mut matched_storage_ids = Vec::with_capacity(self.matched_storage_ids.len()); + + for archetype_id in self.matched_archetypes() { + let archetype = &archetypes[archetype_id]; + let mut add_archetype = || { + matched_archetypes.insert(archetype_id.index()); + matched_storage_ids.push(StorageId { archetype_id }); + }; + if !archetype.has_fragmenting_values() { + add_archetype(); + continue; + } + let Some(archetype_value_component) = archetype.get_value_component(component_id) + else { + add_archetype(); + continue; + }; + if value_component == archetype_value_component { + add_archetype(); + } + } + + QueryState { + world_id: self.world_id, + archetype_generation: self.archetype_generation, + matched_storage_ids, + is_dense: self.is_dense, + fetch_state, + filter_state, + component_access: self.component_access.clone(), + matched_tables: self.matched_tables.clone(), + matched_archetypes, + #[cfg(feature = "trace")] + par_iter_span: self.par_iter_span.clone(), + } + } + + pub(crate) fn filter_by_component_id_and_value<'a>( + &self, + world: impl Into>, + component_id: ComponentId, + value_component: &dyn FragmentingValue, + ) -> QueryState { + let world = world.into(); + self.validate_world(world.id()); + + // This filter only works for archetypal queries. + if self.is_dense { + return self.clone_state(world.components()); + } + + // This should always succeed since original query exists. + let fetch_state = D::get_state(world.components()).unwrap(); + let filter_state = F::get_state(world.components()).unwrap(); + + self.filter_by_component_id_and_value_internal( + world.archetypes(), + component_id, + value_component, + fetch_state, + filter_state, + ) + } + + pub(crate) fn filter_by_component_value<'a, C: FragmentingValue>( + &self, + world: impl Into>, + value_component: &C, + ) -> QueryState { + let world = world.into(); + self.validate_world(world.id()); + + // This filter only works for archetypal queries. + if self.is_dense { + return self.clone_state(world.components()); + } + + let Some(component_id) = world.components().get_id(value_component.type_id()) else { + warn!( + "Value component {} is not registered, filtering by its value will do nothing.", + disqualified::ShortName::of::() + ); + return self.clone_state(world.components()); + }; + + // This should always succeed since original query exists. + let fetch_state = D::get_state(world.components()).unwrap(); + let filter_state = F::get_state(world.components()).unwrap(); + + self.filter_by_component_id_and_value_internal( + world.archetypes(), + component_id, + value_component, + fetch_state, + filter_state, + ) + } } impl QueryState { diff --git a/crates/bevy_ecs/src/shared_component.rs b/crates/bevy_ecs/src/shared_component.rs deleted file mode 100644 index bbe5ea4c096c4..0000000000000 --- a/crates/bevy_ecs/src/shared_component.rs +++ /dev/null @@ -1,123 +0,0 @@ -use alloc::boxed::Box; -use bevy_ecs::component::Component; -use bevy_platform::hash::FixedHasher; -use core::{ - any::Any, - hash::{BuildHasher, Hash, Hasher}, - marker::PhantomData, -}; - -use crate::component::{ComponentId, ComponentsRegistrator, StorageType}; - -#[derive(Component)] -#[component(storage = "SparseSet")] -pub struct SharedComponentStorage; - -pub struct SharedComponents {} - -pub trait SharedComponentKey: Any { - fn eq_key(&self, other: &dyn SharedComponentKey) -> bool; - fn hash_key(&self) -> u64; - fn clone_key(&self) -> Box; -} - -impl SharedComponentKey for T -where - T: Component + Eq + Hash + Any + Clone, -{ - fn eq_key(&self, other: &dyn SharedComponentKey) -> bool { - let other: &dyn Any = other; - if let Some(other) = other.downcast_ref::() { - return self == other; - } - false - } - - fn hash_key(&self) -> u64 { - FixedHasher.hash_one(&self) - } - - fn clone_key(&self) -> Box { - Box::new(self.clone()) - } -} - -impl SharedComponentKey for () { - fn eq_key(&self, other: &dyn SharedComponentKey) -> bool { - (other as &dyn Any).is::() - } - - fn hash_key(&self) -> u64 { - FixedHasher.hash_one(&self) - } - - fn clone_key(&self) -> Box { - Box::new(*self) - } -} - -impl PartialEq for Box { - fn eq(&self, other: &Self) -> bool { - SharedComponentKey::eq_key(self.as_ref(), other.as_ref()) - } -} - -impl Eq for Box {} - -impl Hash for Box { - fn hash(&self, state: &mut H) { - let key_hash = SharedComponentKey::hash_key(self.as_ref()); - state.write_u64(key_hash); - } -} - -pub trait BundleRegisterByValue {} - -impl BundleRegisterByValue for T where T: Eq + Hash + Any + Clone + Component {} - -pub trait SuperKeyTrait { - type RegisterComponent: Component; - type KeyType: SharedComponentKey; - type ValueType: Component; - fn make_value_from_key(key: &Self::KeyType) -> Self::ValueType; -} - -pub struct SelfKey(PhantomData); -impl SuperKeyTrait for SelfKey { - type RegisterComponent = C; - type KeyType = C; - type ValueType = C; - fn make_value_from_key(key: &Self::KeyType) -> Self::ValueType { - Clone::clone(key) - } -} - -pub struct NoKey(PhantomData); -impl SuperKeyTrait for NoKey { - type RegisterComponent = C; - type KeyType = (); - type ValueType = C; - fn make_value_from_key(_key: &Self::KeyType) -> Self::ValueType { - panic!("NoKey: No key value") - } -} - -pub struct ComponentKey(PhantomData<(C, K)>); -impl SuperKeyTrait for ComponentKey { - type RegisterComponent = C; - type KeyType = K; - type ValueType = C; - fn make_value_from_key(_key: &Self::KeyType) -> Self::ValueType { - unimplemented!() - } -} - -pub struct KeyFor(PhantomData<(C, V)>); -impl SuperKeyTrait for KeyFor { - type RegisterComponent = V; - type KeyType = (); - type ValueType = C; - fn make_value_from_key(_key: &Self::KeyType) -> Self::ValueType { - panic!("KeyFor: No key value") - } -} diff --git a/crates/bevy_ecs/src/spawn.rs b/crates/bevy_ecs/src/spawn.rs index 5235889ffba20..7f30b7622f95d 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/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 183bdecfb4f64..2a204ed3a6475 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1,7 +1,8 @@ use crate::{ batching::BatchingStrategy, - component::Tick, + component::{ComponentId, Tick}, entity::{Entity, EntityDoesNotExistError, EntityEquivalent, EntitySet, UniqueEntityArray}, + fragmenting_value::FragmentingValue, query::{ DebugCheckedUnwrap, NopWorldQuery, QueryCombinationIter, QueryData, QueryEntityError, QueryFilter, QueryIter, QueryManyIter, QueryManyUniqueIter, QueryParIter, QueryParManyIter, @@ -2565,6 +2566,47 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { this_run: self.this_run, } } + + /// Filter this query based on fragmenting value component's value. This will create a [`QueryLens`] that will + /// run on archetypes that contain that specific component value. + /// + /// Archetypes that don't have fragmenting component will **not** be filtered out, this action will only filter + /// archetypes that contain this specific component. It is possible to chain this method to filter out by multiple component values. + pub fn filter_by_component_value( + self, + value_component: &C, + ) -> QueryLens<'w, D, F> { + let state = self + .state + .filter_by_component_value(self.world, value_component); + QueryLens { + world: self.world, + state, + last_run: self.last_run, + this_run: self.this_run, + } + } + + /// Filter this query based on fragmenting value component's value and id. This will create a [`QueryLens`] that will + /// run on archetypes that contain that specific component value. + /// + /// Archetypes that don't have fragmenting component will **not** be filtered out, this action will only filter + /// archetypes that contain this specific component. It is possible to chain this method to filter out by multiple component values. + pub fn filter_by_component_id_and_value( + self, + component_id: ComponentId, + value_component: &dyn FragmentingValue, + ) -> QueryLens<'w, D, F> { + let state = + self.state + .filter_by_component_id_and_value(self.world, component_id, value_component); + QueryLens { + world: self.world, + state, + last_run: self.last_run, + this_run: self.this_run, + } + } } impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Query<'w, 's, D, F> { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index a9887c5248673..e1c4276a239c7 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -14,8 +14,9 @@ use crate::{ EntityLocation, }, event::Event, + fragmenting_value::{DynamicFragmentingValue, FragmentingValuesBorrowed}, observer::Observer, - query::{Access, ReadOnlyQueryData}, + query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData}, relationship::RelationshipHookMode, removal_detection::RemovedComponentEvents, resource::Resource, @@ -1813,8 +1814,14 @@ impl<'w> EntityWorldMut<'w> { ) -> &mut Self { self.assert_not_despawned(); let change_tick = self.world.change_tick(); - let mut bundle_inserter = - BundleInserter::new::(self.world, self.location.archetype_id, change_tick); + let mut registrator = self.world.components_registrator(); + let value_components = FragmentingValuesBorrowed::from_bundle(&mut registrator, &bundle); + let mut bundle_inserter = BundleInserter::new::( + self.world, + self.location.archetype_id, + change_tick, + &value_components, + ); // SAFETY: location matches current entity. `T` matches `bundle_info` let (location, after_effect) = unsafe { bundle_inserter.insert( @@ -1883,12 +1890,20 @@ 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, self.location.archetype_id, bundle_id, change_tick, + &value_components, ); self.location = insert_dynamic_bundle( @@ -1947,18 +1962,46 @@ impl<'w> EntityWorldMut<'w> { ); let mut storage_types = core::mem::take(self.world.bundles.get_storages_unchecked(bundle_id)); + 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, self.location.archetype_id, bundle_id, change_tick, + &value_components, ); self.location = insert_dynamic_bundle( bundle_inserter, self.entity, self.location, - iter_components, + components.iter_mut().map(|ptr| ptr.as_mut().promote()), (*storage_types).iter().cloned(), InsertMode::Replace, MaybeLocation::caller(), diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 9bd8d699c6f21..41c717f7bb3c6 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -14,6 +14,7 @@ pub mod unsafe_world_cell; #[cfg(feature = "bevy_reflect")] pub mod reflect; +use crate::fragmenting_value::FragmentingValuesBorrowed; pub use crate::{ change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}, world::command_queue::CommandQueue, @@ -1167,7 +1168,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); // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent let (mut entity_location, after_effect) = unsafe { bundle_spawner.spawn_non_existent(entity, bundle, caller) }; @@ -2310,7 +2311,7 @@ impl World { } // SAFETY: we initialized this bundle_id in `init_info` let mut spawn_or_insert = SpawnOrInsert::Spawn(unsafe { - BundleSpawner::new_with_id(self, bundle_id, change_tick) + BundleSpawner::new_with_id(self, bundle_id, change_tick, &([].into_iter()).collect()) }); let mut invalid_entities = Vec::new(); @@ -2348,6 +2349,7 @@ impl World { location.archetype_id, bundle_id, change_tick, + &([].into_iter()).collect(), ) }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter @@ -2372,8 +2374,14 @@ impl World { unsafe { spawner.spawn_non_existent(entity, bundle, caller) }; } else { // SAFETY: we initialized this bundle_id in `init_info` - let mut spawner = - unsafe { BundleSpawner::new_with_id(self, bundle_id, change_tick) }; + let mut spawner = unsafe { + BundleSpawner::new_with_id( + self, + bundle_id, + change_tick, + &([].into_iter()).collect(), + ) + }; // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter unsafe { spawner.spawn_non_existent(entity, bundle, caller) }; spawn_or_insert = SpawnOrInsert::Spawn(spawner); @@ -2476,6 +2484,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 { @@ -2484,6 +2496,7 @@ impl World { first_location.archetype_id, bundle_id, change_tick, + &value_components, ) }, archetype_id: first_location.archetype_id, @@ -2502,7 +2515,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 { @@ -2511,6 +2530,7 @@ impl World { location.archetype_id, bundle_id, change_tick, + &value_components, ) }, archetype_id: location.archetype_id, @@ -2626,6 +2646,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 { @@ -2634,6 +2658,7 @@ impl World { first_location.archetype_id, bundle_id, change_tick, + &value_components, ) }, archetype_id: first_location.archetype_id, @@ -2661,7 +2686,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 { @@ -2670,6 +2699,7 @@ impl World { location.archetype_id, bundle_id, change_tick, + &value_components, ) }, archetype_id: location.archetype_id, @@ -4051,6 +4081,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(), @@ -4063,6 +4094,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..639e6d0d3f9a7 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 by value components + // since the target archetype depends on the bundle value, 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); + 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); + // 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_render/src/sync_world.rs b/crates/bevy_render/src/sync_world.rs index ce0408833366c..e4315596510ed 100644 --- a/crates/bevy_render/src/sync_world.rs +++ b/crates/bevy_render/src/sync_world.rs @@ -1,6 +1,6 @@ use bevy_app::Plugin; use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::component::{ComponentCloneBehavior, Mutable, StorageType}; +use bevy_ecs::component::{ComponentCloneBehavior, Mutable, NoKey, StorageType}; use bevy_ecs::entity::EntityHash; use bevy_ecs::{ component::Component, @@ -140,6 +140,7 @@ impl Component for RenderEntity { const STORAGE_TYPE: StorageType = StorageType::Table; type Mutability = Mutable; + type Key = NoKey; fn clone_behavior() -> ComponentCloneBehavior { ComponentCloneBehavior::Ignore 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 f047445f94bbe..8c3b53a9da77d 100644 --- a/examples/stress_tests/many_components.rs +++ b/examples/stress_tests/many_components.rs @@ -88,6 +88,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(), @@ -96,6 +97,7 @@ fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { None, true, // is mutable ComponentCloneBehavior::Default, + None, ) }, ) From c84f7a1b1aa0c434cd053961cbf0c1fe0015b7ae Mon Sep 17 00:00:00 2001 From: eugineerd Date: Fri, 9 May 2025 23:37:44 +0000 Subject: [PATCH 03/20] update msrv to 1.86.0 --- Cargo.toml | 2 +- crates/bevy_ecs/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a3d3a2ab63e51..86c1a1ebe2014 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"] license = "MIT OR Apache-2.0" repository = "https://github.com/bevyengine/bevy" documentation = "https://docs.rs/bevy" -rust-version = "1.85.0" +rust-version = "1.86.0" [workspace] resolver = "2" diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index 97cdcee0824b7..9198f9b79a3cf 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT OR Apache-2.0" keywords = ["ecs", "game", "bevy"] categories = ["game-engines", "data-structures"] -rust-version = "1.85.0" +rust-version = "1.86.0" [features] default = ["std", "bevy_reflect", "async_executor", "backtrace"] From c8d064c3eb919fd3917061702f64fb3734389390 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Wed, 14 May 2025 17:18:50 +0000 Subject: [PATCH 04/20] remove `filter_by_component_value` in Query implementation It was just example code for this feature and wasn't intended to be usable anyway. --- crates/bevy_ecs/src/fragmenting_value.rs | 97 ----------------- crates/bevy_ecs/src/query/state.rs | 132 +---------------------- crates/bevy_ecs/src/system/query.rs | 44 +------- 3 files changed, 3 insertions(+), 270 deletions(-) diff --git a/crates/bevy_ecs/src/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs index b2ad974f5eda7..fa8a05d67dea2 100644 --- a/crates/bevy_ecs/src/fragmenting_value.rs +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -323,8 +323,6 @@ mod tests { component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, entity::Entity, fragmenting_value::{FragmentingValue, FragmentingValueVtable}, - query::With, - system::{Query, RunSystemOnce}, world::World, }; use alloc::boxed::Box; @@ -469,101 +467,6 @@ mod tests { assert_eq!(id1, id4); } - #[test] - fn query_fragmenting_components() { - let mut world = World::default(); - _ = world.spawn_batch([ - Fragmenting(1), - Fragmenting(2), - Fragmenting(1), - Fragmenting(3), - Fragmenting(1), - ]); - - fn system_fragmenting_all(q: Query<&Fragmenting>) { - assert_eq!(q.iter().len(), 5); - } - - fn system_fragmenting_1(q: Query<&Fragmenting>) { - assert_eq!( - q.filter_by_component_value(&Fragmenting(1)) - .query() - .iter() - .len(), - 3 - ); - } - - fn system_fragmenting_2(q: Query<&Fragmenting>) { - assert_eq!( - q.filter_by_component_value(&Fragmenting(2)) - .query() - .iter() - .len(), - 1 - ); - } - - fn system_fragmenting_3(q: Query<&Fragmenting>) { - assert_eq!( - q.filter_by_component_value(&Fragmenting(3)) - .query() - .iter() - .len(), - 1 - ); - } - - fn system_fragmenting_1_with_filter(q: Query>) { - assert_eq!( - q.filter_by_component_value(&Fragmenting(1)) - .query() - .iter() - .len(), - 3 - ); - } - - world.run_system_once(system_fragmenting_all).unwrap(); - world.run_system_once(system_fragmenting_1).unwrap(); - world.run_system_once(system_fragmenting_2).unwrap(); - world.run_system_once(system_fragmenting_3).unwrap(); - world - .run_system_once(system_fragmenting_1_with_filter) - .unwrap(); - } - - #[test] - fn query_fragmenting_components_multiple() { - #[derive(Component, Clone, Eq, PartialEq, Hash)] - #[component( - key=Self, - storage="SparseSet", - immutable, - )] - struct Fragmenting2(u32); - - let mut world = World::default(); - _ = world.spawn_batch([ - (Fragmenting(1), Fragmenting2(2)), - (Fragmenting(1), Fragmenting2(1)), - (Fragmenting(1), Fragmenting2(2)), - ]); - - fn system_fragmenting(q: Query<(&Fragmenting, &Fragmenting2)>) { - let len = q - .filter_by_component_value(&Fragmenting(1)) - .query() - .filter_by_component_value(&Fragmenting2(2)) - .query() - .iter() - .len(); - assert_eq!(len, 2); - } - - world.run_system_once(system_fragmenting).unwrap(); - } - #[test] fn fragment_dynamic() { let mut world = World::default(); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index ee19f24e90cba..cae8e592e757e 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1,9 +1,8 @@ use crate::{ - archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId, Archetypes}, - component::{ComponentId, Components, Tick}, + archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, + component::{ComponentId, Tick}, entity::{Entity, EntityEquivalent, EntitySet, UniqueEntityArray}, entity_disabling::DefaultQueryFilters, - fragmenting_value::FragmentingValue, prelude::FromWorld, query::{Access, FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, WorldQuery}, storage::{SparseSetIndex, TableId}, @@ -1656,133 +1655,6 @@ impl QueryState { .fold(accum, &mut func); }); } - - fn clone_state(&self, components: &Components) -> QueryState { - // This should always succeed since original query exists. - let fetch_state = D::get_state(components).unwrap(); - let filter_state = F::get_state(components).unwrap(); - - QueryState { - world_id: self.world_id, - archetype_generation: self.archetype_generation, - matched_storage_ids: self.matched_storage_ids.clone(), - is_dense: self.is_dense, - fetch_state, - filter_state, - component_access: self.component_access.clone(), - matched_tables: self.matched_tables.clone(), - matched_archetypes: self.matched_archetypes.clone(), - #[cfg(feature = "trace")] - par_iter_span: self.par_iter_span.clone(), - } - } - - fn filter_by_component_id_and_value_internal( - &self, - archetypes: &Archetypes, - component_id: ComponentId, - value_component: &dyn FragmentingValue, - fetch_state: D::State, - filter_state: F::State, - ) -> QueryState { - let mut matched_archetypes = FixedBitSet::with_capacity(self.matched_archetypes.len()); - let mut matched_storage_ids = Vec::with_capacity(self.matched_storage_ids.len()); - - for archetype_id in self.matched_archetypes() { - let archetype = &archetypes[archetype_id]; - let mut add_archetype = || { - matched_archetypes.insert(archetype_id.index()); - matched_storage_ids.push(StorageId { archetype_id }); - }; - if !archetype.has_fragmenting_values() { - add_archetype(); - continue; - } - let Some(archetype_value_component) = archetype.get_value_component(component_id) - else { - add_archetype(); - continue; - }; - if value_component == archetype_value_component { - add_archetype(); - } - } - - QueryState { - world_id: self.world_id, - archetype_generation: self.archetype_generation, - matched_storage_ids, - is_dense: self.is_dense, - fetch_state, - filter_state, - component_access: self.component_access.clone(), - matched_tables: self.matched_tables.clone(), - matched_archetypes, - #[cfg(feature = "trace")] - par_iter_span: self.par_iter_span.clone(), - } - } - - pub(crate) fn filter_by_component_id_and_value<'a>( - &self, - world: impl Into>, - component_id: ComponentId, - value_component: &dyn FragmentingValue, - ) -> QueryState { - let world = world.into(); - self.validate_world(world.id()); - - // This filter only works for archetypal queries. - if self.is_dense { - return self.clone_state(world.components()); - } - - // This should always succeed since original query exists. - let fetch_state = D::get_state(world.components()).unwrap(); - let filter_state = F::get_state(world.components()).unwrap(); - - self.filter_by_component_id_and_value_internal( - world.archetypes(), - component_id, - value_component, - fetch_state, - filter_state, - ) - } - - pub(crate) fn filter_by_component_value<'a, C: FragmentingValue>( - &self, - world: impl Into>, - value_component: &C, - ) -> QueryState { - let world = world.into(); - self.validate_world(world.id()); - - // This filter only works for archetypal queries. - if self.is_dense { - return self.clone_state(world.components()); - } - - let Some(component_id) = world.components().get_id(value_component.type_id()) else { - warn!( - "Value component {} is not registered, filtering by its value will do nothing.", - disqualified::ShortName::of::() - ); - return self.clone_state(world.components()); - }; - - // This should always succeed since original query exists. - let fetch_state = D::get_state(world.components()).unwrap(); - let filter_state = F::get_state(world.components()).unwrap(); - - self.filter_by_component_id_and_value_internal( - world.archetypes(), - component_id, - value_component, - fetch_state, - filter_state, - ) - } } impl QueryState { diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 639a8083f37b7..c1d9e671a0e4f 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1,8 +1,7 @@ use crate::{ batching::BatchingStrategy, - component::{ComponentId, Tick}, + component::Tick, entity::{Entity, EntityDoesNotExistError, EntityEquivalent, EntitySet, UniqueEntityArray}, - fragmenting_value::FragmentingValue, query::{ DebugCheckedUnwrap, NopWorldQuery, QueryCombinationIter, QueryData, QueryEntityError, QueryFilter, QueryIter, QueryManyIter, QueryManyUniqueIter, QueryParIter, QueryParManyIter, @@ -2441,47 +2440,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { this_run: self.this_run, } } - - /// Filter this query based on fragmenting value component's value. This will create a [`QueryLens`] that will - /// run on archetypes that contain that specific component value. - /// - /// Archetypes that don't have fragmenting component will **not** be filtered out, this action will only filter - /// archetypes that contain this specific component. It is possible to chain this method to filter out by multiple component values. - pub fn filter_by_component_value( - self, - value_component: &C, - ) -> QueryLens<'w, D, F> { - let state = self - .state - .filter_by_component_value(self.world, value_component); - QueryLens { - world: self.world, - state, - last_run: self.last_run, - this_run: self.this_run, - } - } - - /// Filter this query based on fragmenting value component's value and id. This will create a [`QueryLens`] that will - /// run on archetypes that contain that specific component value. - /// - /// Archetypes that don't have fragmenting component will **not** be filtered out, this action will only filter - /// archetypes that contain this specific component. It is possible to chain this method to filter out by multiple component values. - pub fn filter_by_component_id_and_value( - self, - component_id: ComponentId, - value_component: &dyn FragmentingValue, - ) -> QueryLens<'w, D, F> { - let state = - self.state - .filter_by_component_id_and_value(self.world, component_id, value_component); - QueryLens { - world: self.world, - state, - last_run: self.last_run, - this_run: self.this_run, - } - } } impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Query<'w, 's, D, F> { From 3032426d79eec170d4e066ed7edb066bc6ca4889 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Wed, 14 May 2025 19:53:10 +0000 Subject: [PATCH 05/20] add support for `DynamicFragmentingValue` in dyn `FragmentingValue::eq` --- crates/bevy_ecs/src/fragmenting_value.rs | 116 +++++++++++++++++++++-- 1 file changed, 110 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs index fa8a05d67dea2..f3991caaaa961 100644 --- a/crates/bevy_ecs/src/fragmenting_value.rs +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -27,7 +27,10 @@ use crate::{ /// /// For dynamic components see [`DynamicFragmentingValue`] and [`FragmentingValueVtable`]. pub trait FragmentingValue: Any { - /// Return `true` if `self` `==` `other`. Dynamic version of [`PartialEq::eq`]. + /// 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; @@ -75,7 +78,7 @@ impl FragmentingValue for () { impl PartialEq for dyn FragmentingValue { fn eq(&self, other: &Self) -> bool { - FragmentingValue::value_eq(self, other) + Self::value_eq_dynamic(self, other) } } @@ -88,6 +91,17 @@ impl Hash for dyn FragmentingValue { } } +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. /// @@ -185,7 +199,10 @@ impl<'a> Equivalent for FragmentingValuesBorrowed<'a> { .values .iter() .zip(key.values.iter()) - .all(|((id1, v1), (id2, v2))| id1 == id2 && **v1 == **v2) + // We know that v2 is never an instance of DynamicFragmentingValue since it is from FragmentingValuesOwned. + // Because FragmentingValuesOwned 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)) } } @@ -251,6 +268,23 @@ impl DynamicFragmentingValue { 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`]. @@ -283,7 +317,7 @@ impl FragmentingValueVtable { FragmentingValueVtable { eq: |ptr, other| { // SAFETY: caller is responsible for using this vtable only with correct values - unsafe { ptr.cast::().as_ref() }.value_eq(other) + *(unsafe { ptr.cast::().as_ref() } as &dyn FragmentingValue) == *other }, hash: |ptr| { // SAFETY: caller is responsible for using this vtable only with correct values @@ -322,13 +356,13 @@ mod tests { use crate::{ component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, entity::Entity, - fragmenting_value::{FragmentingValue, FragmentingValueVtable}, + fragmenting_value::{DynamicFragmentingValue, FragmentingValue, FragmentingValueVtable}, world::World, }; use alloc::boxed::Box; use alloc::vec::Vec; use bevy_platform::hash::FixedHasher; - use bevy_ptr::OwningPtr; + use bevy_ptr::{OwningPtr, Ptr}; use core::hash::Hash; #[derive(Component, Clone, Eq, PartialEq, Hash)] @@ -576,4 +610,74 @@ mod tests { 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); + } } From 7fd9bf7c9e1c1609f25eaf3c7b803c951f1ef5f4 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Wed, 14 May 2025 23:09:44 +0000 Subject: [PATCH 06/20] Remove `storage!=Table` restriction for fragmenting value components. This restriction isn't really required for current implementation. --- crates/bevy_ecs/macros/src/component.rs | 22 ---------------------- crates/bevy_ecs/src/component.rs | 6 +----- crates/bevy_ecs/src/fragmenting_value.rs | 1 - 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index e2dd9ef085631..9252ee2ea3d74 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -261,28 +261,6 @@ pub fn derive_component(input: TokenStream) -> TokenStream { }; let key = if let Some(key) = attrs.key { - // This check exists only to show better errors earlier and is not used for checking correctness, - // so it's okay for it to not work in 100% of cases. - if let Type::Path(type_path) = &key { - if let Some(ident) = type_path.path.get_ident() { - let ident = ident.to_string(); - if (ident == "Self" || *struct_name == ident) - && matches!(attrs.storage, StorageTy::Table) - { - return syn::Error::new( - ast.span(), - "Component key must not have Table storage type.", - ) - .into_compile_error() - .into(); - } - } - if !attrs.immutable { - return syn::Error::new(ast.span(), "Component key must be immutable.") - .into_compile_error() - .into(); - } - } quote! {#bevy_ecs_path::component::OtherComponentKey} } else { quote! {#bevy_ecs_path::component::NoKey} diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 48aef4d9d929c..dcb0e284f21de 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -676,11 +676,7 @@ impl private::Seal for OtherComponentKey {} impl ComponentKey for OtherComponentKey { type KeyType = K; type ValueType = C; - const INVARIANT_ASSERT: () = { - if matches!(K::STORAGE_TYPE, StorageType::Table) { - panic!("Key component must not have Table storage type") - } - }; + const INVARIANT_ASSERT: () = (); } /// Set this component as the fragmenting key. This means every different value of this component (as defined by [`PartialEq::eq`]) diff --git a/crates/bevy_ecs/src/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs index f3991caaaa961..471fbb6db24d0 100644 --- a/crates/bevy_ecs/src/fragmenting_value.rs +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -368,7 +368,6 @@ mod tests { #[derive(Component, Clone, Eq, PartialEq, Hash)] #[component( key=Self, - storage="SparseSet", immutable, )] struct Fragmenting(u32); From 7cfc73398abc1715128f4bb2b145d7d640094238 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Thu, 15 May 2025 10:06:31 +0000 Subject: [PATCH 07/20] fixed fragmenting value map being reset `cache_archetype_after_bundle_insert` resets `fragmenting_values_map` when caching new edges. Now this is skipped if base archetype already exists and the only thing changed is the fragmenting value components. --- crates/bevy_ecs/src/archetype.rs | 20 +++-- crates/bevy_ecs/src/bundle.rs | 98 ++++++++++-------------- crates/bevy_ecs/src/fragmenting_value.rs | 38 +++++++++ 3 files changed, 92 insertions(+), 64 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index f8e62672a970e..770f76cee3830 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -135,8 +135,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, - - pub by_value_archetype_id: HashMap, + /// Maps this archetype to other component-identical archetypes based on [`FragmentingValue`]s of components. + /// All [`Archetype`]s this maps to differ only by their identity due to different [`FragmentingValuesOwned`], 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 { @@ -221,7 +224,7 @@ impl Edges { if value_components.is_empty() { Some(bundle.archetype_id) } else { - bundle.by_value_archetype_id.get(value_components).copied() + bundle.fragmenting_values_map.get(value_components).copied() } }) } @@ -237,6 +240,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, @@ -255,11 +261,15 @@ impl Edges { required_components, added, existing, - by_value_archetype_id: Default::default(), + fragmenting_values_map: Default::default(), }, ); } + /// Caches the target archetype for this combination of [`BundleId`] and [`FragmentingValuesOwned`]. + /// + /// 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, @@ -268,7 +278,7 @@ impl Edges { ) { if let Some(bundle) = self.insert_bundle.get_mut(bundle_id) { bundle - .by_value_archetype_id + .fragmenting_values_map .insert(value_components, value_archetype_id); } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index ff39d32eb4618..fec9360e4e101 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -802,6 +802,11 @@ impl BundleInfo { { 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(); @@ -855,7 +860,10 @@ impl BundleInfo { } } - if new_table_components.is_empty() && new_sparse_set_components.is_empty() { + 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. current_archetype .edges_mut() @@ -867,46 +875,12 @@ impl BundleInfo { added, existing, ); - if replaced_value_components.is_empty() { - return archetype_id; - } - // If any of the value components got replaced, this change should point to a new archetype - 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 archetype_value_components = current_archetype - .components_with_fragmenting_values() - .map(|(component_id, value)| { - ( - component_id, - replaced_value_components - .get(&component_id) - .copied() - .unwrap_or(value), - ) - }) - .collect(); - let value_archetype_id = archetypes.get_id_or_insert( - components, - observers, - table_id, - table_components, - sparse_set_components, - archetype_value_components, - ); - archetypes[archetype_id] - .edges_mut() - .cache_archetype_value_components_after_bundle_insert( - self.id, - value_components.to_owned(), - value_archetype_id, - ); - value_archetype_id + + archetype_id } else { let table_id; let table_components; let sparse_set_components; - let archetype_value_components; // The archetype changes when we insert this bundle. Prepare the new archetype and storages. { let current_archetype = &archetypes[archetype_id]; @@ -936,19 +910,6 @@ impl BundleInfo { new_sparse_set_components.sort_unstable(); new_sparse_set_components }; - - archetype_value_components = current_archetype - .components_with_fragmenting_values() - .map(|(component_id, value)| { - ( - component_id, - *replaced_value_components - .get(&component_id) - .unwrap_or(&value), - ) - }) - .chain(new_value_components) - .collect(); }; // 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 @@ -972,28 +933,47 @@ impl BundleInfo { existing, ); - if value_components.is_empty() { - return new_base_archetype_id; - } + new_base_archetype_id + }; - let new_value_archetype_id = archetypes.get_id_or_insert( + 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) + .copied() + .unwrap_or(value), + ) + }) + .chain(new_value_components) + .collect(); + let value_archetype_id = archetypes.get_id_or_insert( components, observers, table_id, table_components, sparse_set_components, - archetype_value_components, + fragmenting_value_components, ); - archetypes[archetype_id] .edges_mut() .cache_archetype_value_components_after_bundle_insert( self.id, value_components.to_owned(), - new_value_archetype_id, + value_archetype_id, ); - - new_value_archetype_id + value_archetype_id } } diff --git a/crates/bevy_ecs/src/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs index 471fbb6db24d0..f2cd33beb80af 100644 --- a/crates/bevy_ecs/src/fragmenting_value.rs +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -354,6 +354,7 @@ mod tests { }; use crate::{ + archetype::ArchetypeId, component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType}, entity::Entity, fragmenting_value::{DynamicFragmentingValue, FragmentingValue, FragmentingValueVtable}, @@ -679,4 +680,41 @@ mod tests { 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); + } } From d41d9fcd063ca06b5afe6695f3c9780d3f4bf51a Mon Sep 17 00:00:00 2001 From: eugineerd Date: Thu, 15 May 2025 10:33:11 +0000 Subject: [PATCH 08/20] add migration guide --- .../fragmenting_value_components.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 release-content/migration-guides/fragmenting_value_components.md 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..83bad2ff194ab --- /dev/null +++ b/release-content/migration-guides/fragmenting_value_components.md @@ -0,0 +1,13 @@ +--- +title: Added support for fragmenting value components +pull_requests: [19153] +--- + +Archetypes can now be fragmented by component values. Supporting this required some changes to the public api of archetypes and a new associated type for components. + +Manual impl of `Component` trait now has a new associated type, `Key`, that should be set to `NoKey` for all existing components. + +`ComponentDescriptor::new` and `ComponentDescriptor::new_with_layout` now require an additional argument - `fragmenting_value_vtable: Option`. It should be set to `None` for all existing implementations. + +`Edges::get_archetype_after_bundle_insert` now require an additional argument - `value_components: &FragmentingValuesBorrowed`. These can be +constructed using `FragmentingValuesBorrowed::from_bundle`. From a777ce65ba023123f2f8102080975ee219d67f1c Mon Sep 17 00:00:00 2001 From: eugineerd Date: Tue, 20 May 2025 11:57:24 +0000 Subject: [PATCH 09/20] a bit more documentation --- crates/bevy_ecs/src/bundle.rs | 2 ++ crates/bevy_ecs/src/component.rs | 12 ++++++++---- crates/bevy_ecs/src/fragmenting_value.rs | 2 +- crates/bevy_ecs/src/world/spawn_batch.rs | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index fec9360e4e101..1c7c4d110e757 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -1799,6 +1799,8 @@ impl<'w> BundleSpawner<'w> { unsafe { Self::new_with_id(world, bundle_id, change_tick, &value_components) } } + /// 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) -> Self { // SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too. diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index dcb0e284f21de..094b3e1b6d313 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1155,6 +1155,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() } } @@ -1190,10 +1191,10 @@ 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` - /// - mutable should be `false` - /// - storage type must not be `StorageType::Table` + /// - if `fragmenting_value_vtable` is not `None`, it must be usable on a pointer with a value of the layout `layout` + /// + /// # Panics + /// This will panic if `fragmenting_value_vtable` is not `None` and `mutable` is `true`. Fragmenting value components must be immutable. pub unsafe fn new_with_layout( name: impl Into>, storage_type: StorageType, @@ -1203,6 +1204,9 @@ impl ComponentDescriptor { clone_behavior: ComponentCloneBehavior, fragmenting_value_vtable: Option, ) -> Self { + if fragmenting_value_vtable.is_some() && mutable { + panic!("Fragmenting value components must be immutable"); + } Self { name: name.into(), storage_type, diff --git a/crates/bevy_ecs/src/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs index f2cd33beb80af..78306145d5129 100644 --- a/crates/bevy_ecs/src/fragmenting_value.rs +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -289,7 +289,7 @@ impl DynamicFragmentingValue { /// Dynamic vtable for [`FragmentingValue`]. /// This is used by [`crate::component::ComponentDescriptor`] to work with dynamic fragmenting components. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct FragmentingValueVtable { eq: fn(NonNull, &dyn FragmentingValue) -> bool, hash: fn(NonNull) -> u64, diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 639e6d0d3f9a7..9d94c4200f284 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -41,8 +41,8 @@ where let length = upper.unwrap_or(lower); world.entities.reserve(length as u32); - // We cannot reuse the same spawner if the bundle has any fragmenting by value components - // since the target archetype depends on the bundle value, not just its type. + // 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 { From 149634e41068f90651013043172a46de399a487c Mon Sep 17 00:00:00 2001 From: eugineerd Date: Tue, 20 May 2025 13:19:41 +0000 Subject: [PATCH 10/20] add some benches --- .../bevy_ecs/components/fragmenting_values.rs | 123 ++++++++++++++++++ benches/benches/bevy_ecs/components/mod.rs | 4 + 2 files changed, 127 insertions(+) create mode 100644 benches/benches/bevy_ecs/components/fragmenting_values.rs 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..d53a91bf652f4 --- /dev/null +++ b/benches/benches/bevy_ecs/components/fragmenting_values.rs @@ -0,0 +1,123 @@ +use bevy_ecs::prelude::*; +use criterion::Criterion; +use glam::*; + +#[derive(Component, PartialEq, Eq, Hash, Clone)] +#[component(immutable, key=Self)] +struct Fragmenting(u32); + +#[derive(Component)] +struct NonFragmenting(Vec3); + +pub fn insert_fragmenting_value(c: &mut Criterion) { + let mut group = c.benchmark_group("insert_fragmenting_value"); + group.warm_up_time(core::time::Duration::from_millis(500)); + group.measurement_time(core::time::Duration::from_secs(5)); + group.bench_function("base", |b| { + b.iter(move || { + let mut world = World::new(); + world.spawn_batch((0..10_000).map(|_| { + ( + Fragmenting::<1>(1), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + ) + })); + }); + }); + group.bench_function("unbatched", |b| { + b.iter(move || { + let mut world = World::new(); + for _ in 0..10_000 { + world.spawn(( + Fragmenting::<1>(1), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + )); + } + }); + }); + group.bench_function("high_fragmentation_base", |b| { + b.iter(move || { + let mut world = World::new(); + world.spawn_batch((0..10_000).map(|i| { + ( + Fragmenting::<1>(i % 100), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + ) + })); + }); + }); + group.bench_function("high_fragmentation_unbatched", |b| { + b.iter(move || { + let mut world = World::new(); + for i in 0..10_000 { + world.spawn(( + Fragmenting::<1>(i % 100), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + )); + } + }); + }); + group.finish(); +} + +pub fn add_remove_fragmenting_value(c: &mut Criterion) { + let mut group = c.benchmark_group("add_remove_fragmenting_value"); + group.warm_up_time(core::time::Duration::from_millis(500)); + group.measurement_time(core::time::Duration::from_secs(5)); + + group.bench_function("non_fragmenting", |b| { + let mut world = World::new(); + let entities: Vec<_> = world + .spawn_batch((0..10_000).map(|_| { + ( + Fragmenting::<1>(1), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + ) + })) + .collect(); + b.iter(move || { + for entity in &entities { + world + .entity_mut(*entity) + .insert(NonFragmenting::<4>(Vec3::ZERO)); + } + + for entity in &entities { + world.entity_mut(*entity).remove::>(); + } + }); + }); + + group.bench_function("fragmenting", |b| { + let mut world = World::new(); + let entities: Vec<_> = world + .spawn_batch((0..10_000).map(|_| { + ( + Fragmenting::<1>(1), + NonFragmenting::<1>(Vec3::ONE), + NonFragmenting::<2>(Vec3::ONE), + NonFragmenting::<3>(Vec3::ONE), + ) + })) + .collect(); + b.iter(move || { + for entity in &entities { + world.entity_mut(*entity).insert(Fragmenting::<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) { From 441f858b2e2e1c29be44394aac5baafa648d56dc Mon Sep 17 00:00:00 2001 From: eugineerd Date: Tue, 27 May 2025 19:18:59 +0000 Subject: [PATCH 11/20] add `Shared` storage Also replace `FragmentingValuesOwned` with `FragmentingValuesShared` to reduce memory overhead and make efficient batch fragmenting value comparison possible --- crates/bevy_ecs/src/archetype.rs | 38 ++++--- crates/bevy_ecs/src/bundle.rs | 29 ++++-- crates/bevy_ecs/src/fragmenting_value.rs | 66 ++++++------ crates/bevy_ecs/src/storage/mod.rs | 4 + crates/bevy_ecs/src/storage/shared.rs | 125 +++++++++++++++++++++++ crates/bevy_ecs/src/world/entity_ref.rs | 10 +- crates/bevy_ecs/src/world/mod.rs | 3 + 7 files changed, 219 insertions(+), 56 deletions(-) create mode 100644 crates/bevy_ecs/src/storage/shared.rs diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 042b2cb2404b8..db8ddc817004f 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -23,11 +23,13 @@ use crate::{ bundle::BundleId, component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, entity::{Entity, EntityLocation}, - fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed, FragmentingValuesOwned}, + fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed, FragmentingValuesShared}, observer::Observers, - storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow}, + storage::{ + ImmutableSparseSet, SharedFragmentingValue, SparseArray, SparseSet, TableId, TableRow, + }, }; -use alloc::{boxed::Box, vec::Vec}; +use alloc::{boxed::Box, sync::Arc, vec::Vec}; use bevy_platform::collections::HashMap; use core::{ hash::Hash, @@ -144,10 +146,10 @@ pub(crate) struct ArchetypeAfterBundleInsert { /// Required Components. pub existing: Vec, /// Maps this archetype to other component-identical archetypes based on [`FragmentingValue`]s of components. - /// All [`Archetype`]s this maps to differ only by their identity due to different [`FragmentingValuesOwned`], otherwise they're identical. + /// 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, + pub fragmenting_values_map: HashMap, } impl ArchetypeAfterBundleInsert { @@ -274,14 +276,14 @@ impl Edges { ); } - /// Caches the target archetype for this combination of [`BundleId`] and [`FragmentingValuesOwned`]. + /// 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: FragmentingValuesOwned, + value_components: FragmentingValuesShared, value_archetype_id: ArchetypeId, ) { if let Some(bundle) = self.insert_bundle.get_mut(bundle_id) { @@ -386,7 +388,7 @@ pub(crate) struct ArchetypeSwapRemoveResult { /// [`Component`]: crate::component::Component struct ArchetypeComponentInfo { storage_type: StorageType, - fragmenting_value: Option>, + fragmenting_value: Option, } bitflags::bitflags! { @@ -434,7 +436,7 @@ impl Archetype { table_id: TableId, table_components: impl Iterator, sparse_set_components: impl Iterator, - value_components: impl Iterator, + value_components: &FragmentingValuesShared, ) -> Self { let (min_table, _) = table_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint(); @@ -479,9 +481,9 @@ impl Archetype { .insert(id, ArchetypeRecord { column: None }); } - for (component_id, archetype_component_id) in value_components { + for (component_id, archetype_component_id) in value_components.iter_ids_and_values() { if let Some(info) = archetype_components.get_mut(component_id) { - info.fragmenting_value = Some(archetype_component_id.clone_boxed()); + info.fragmenting_value = Some(archetype_component_id.clone()); } flags.insert(ArchetypeFlags::HAS_VALUE_COMPONENTS); } @@ -585,10 +587,10 @@ impl Archetype { pub(crate) fn components_with_fragmenting_values( &self, - ) -> impl Iterator { + ) -> impl Iterator { self.components .iter() - .filter_map(|(id, info)| Some((*id, info.fragmenting_value.as_ref()?.as_ref()))) + .filter_map(|(id, info)| Some((*id, info.fragmenting_value.as_ref()?))) } /// Fetches an immutable reference to the archetype's [`Edges`], a cache of @@ -712,11 +714,13 @@ impl Archetype { /// Returns [`FragmentingValue`] for this archetype of the requested `component_id`. /// /// This will return `None` if requested component isn't a part of this archetype or isn't fragmenting. - pub fn get_value_component(&self, component_id: ComponentId) -> Option<&dyn FragmentingValue> { + pub fn get_value_component( + &self, + component_id: ComponentId, + ) -> Option<&SharedFragmentingValue> { self.components .get(component_id) .and_then(|info| info.fragmenting_value.as_ref()) - .map(AsRef::as_ref) } /// Returns `true` if this archetype contains any components that fragment by value. @@ -956,7 +960,7 @@ impl Archetypes { table_id: TableId, table_components: Vec, sparse_set_components: Vec, - value_components: FragmentingValuesOwned, + value_components: FragmentingValuesShared, ) -> ArchetypeId { let archetype_identity = ArchetypeComponents { sparse_set_components: sparse_set_components.into_boxed_slice(), @@ -984,7 +988,7 @@ impl Archetypes { table_id, table_components.iter().copied(), sparse_set_components.iter().copied(), - value_components.iter_ids_and_values(), + value_components, )); id }) diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 0c9a5d4cfae70..cfc67726eb592 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,7 +15,7 @@ use crate::{ RequiredComponentConstructor, RequiredComponents, StorageType, Tick, }, entity::{Entities, Entity, EntityLocation}, - fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed}, + fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed, FragmentingValuesShared}, observer::Observers, prelude::World, query::DebugCheckedUnwrap, @@ -795,6 +795,7 @@ impl BundleInfo { observers: &Observers, archetype_id: ArchetypeId, value_components: &FragmentingValuesBorrowed, + current_tick: Tick, ) -> ArchetypeId { if let Some(archetype_after_insert_id) = archetypes[archetype_id] .edges() @@ -852,11 +853,18 @@ impl BundleInfo { 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 != *component_value { + if old_value.as_ref() != component_value { replaced_value_components.insert(component_id, component_value); } } else { - new_value_components.push((component_id, component_value)); + new_value_components.push(( + component_id, + storages + .shared + .get_or_insert(current_tick, component_id, component_value) + .value() + .clone(), + )); } } @@ -952,8 +960,14 @@ impl BundleInfo { component_id, replaced_value_components .get(&component_id) - .copied() - .unwrap_or(value), + .map(|value| { + storages + .shared + .get_or_insert(current_tick, component_id, *value) + .value() + }) + .unwrap_or(value) + .clone(), ) }) .chain(new_value_components) @@ -970,7 +984,7 @@ impl BundleInfo { .edges_mut() .cache_archetype_value_components_after_bundle_insert( self.id, - value_components.to_owned(), + value_components.to_shared(current_tick, &mut storages.shared), value_archetype_id, ); value_archetype_id @@ -1072,6 +1086,7 @@ impl BundleInfo { removed_sparse_set_components.binary_search(id).is_err() && removed_table_components.binary_search(id).is_err() }) + .map(|(id, value)| (id, value.clone())) .collect(); } @@ -1176,6 +1191,7 @@ impl<'w> BundleInserter<'w> { &world.observers, archetype_id, value_components, + change_tick, ); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; @@ -1833,6 +1849,7 @@ impl<'w> BundleSpawner<'w> { &world.observers, ArchetypeId::EMPTY, value_components, + change_tick, ); let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; diff --git a/crates/bevy_ecs/src/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs index 78306145d5129..53b44d4589cb6 100644 --- a/crates/bevy_ecs/src/fragmenting_value.rs +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -10,7 +10,6 @@ use bevy_platform::hash::FixedHasher; use bevy_ptr::Ptr; use core::{ any::{Any, TypeId}, - borrow::Borrow, hash::{BuildHasher, Hash, Hasher}, ptr::NonNull, }; @@ -18,7 +17,8 @@ use indexmap::Equivalent; use crate::{ bundle::Bundle, - component::{ComponentId, ComponentKey, Components, ComponentsRegistrator, Immutable}, + component::{ComponentId, ComponentKey, Components, ComponentsRegistrator, Immutable, Tick}, + storage::{Shared, SharedFragmentingValue}, }; /// Trait used to define values that can fragment archetypes. @@ -106,12 +106,12 @@ impl dyn FragmentingValue { /// 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)] -pub struct FragmentingValuesOwned { - values: Box<[(ComponentId, Box)]>, +#[derive(Hash, PartialEq, Eq, Default, Clone)] +pub struct FragmentingValuesShared { + values: Box<[(ComponentId, SharedFragmentingValue)]>, } -impl FragmentingValuesOwned { +impl FragmentingValuesShared { /// Returns `true` if there are no fragmenting values. pub fn is_empty(&self) -> bool { self.values.is_empty() @@ -120,20 +120,15 @@ impl FragmentingValuesOwned { /// 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.as_ref())) + ) -> impl Iterator { + self.values.iter().map(|(id, v)| (*id, v)) } } -impl> FromIterator<(ComponentId, T)> for FragmentingValuesOwned { - fn from_iter>(iter: I) -> Self { - let mut values = Vec::new(); - for (id, value) in iter { - values.push((id, value.borrow().clone_boxed())); - } - values.sort_unstable_by_key(|(id, _)| *id); - FragmentingValuesOwned { - values: values.into_boxed_slice(), +impl FromIterator<(ComponentId, SharedFragmentingValue)> for FragmentingValuesShared { + fn from_iter>(iter: I) -> Self { + FragmentingValuesShared { + values: iter.into_iter().collect(), } } } @@ -141,7 +136,7 @@ impl> FromIterator<(ComponentId, T)> for Fragmen /// 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 [`FragmentingValuesOwned`] keys. +/// Borrowed version is used to query maps with [`FragmentingValuesShared`] keys. #[derive(Hash, PartialEq, Eq, Default)] pub struct FragmentingValuesBorrowed<'a> { values: Vec<(ComponentId, &'a dyn FragmentingValue)>, @@ -162,15 +157,24 @@ impl<'a> FragmentingValuesBorrowed<'a> { self.values.is_empty() } - /// Creates [`FragmentingValuesOwned`] by cloning all fragmenting values. - pub fn to_owned(&self) -> FragmentingValuesOwned { - FragmentingValuesOwned { - values: self - .values - .iter() - .map(|(id, v)| (*id, v.clone_boxed())) - .collect(), - } + /// Creates [`FragmentingValuesShared`] by cloning or retrieving from [`Shared`] all fragmenting values. + pub fn to_shared( + &self, + current_tick: Tick, + shared_component_storage: &mut Shared, + ) -> FragmentingValuesShared { + self.values + .iter() + .map(|(id, v)| { + ( + *id, + shared_component_storage + .get_or_insert(current_tick, *id, *v) + .value() + .clone(), + ) + }) + .collect() } /// Returns fragmenting component values with their corresponding [`ComponentId`]s. @@ -192,15 +196,15 @@ impl<'a> FromIterator<(ComponentId, &'a dyn FragmentingValue)> for FragmentingVa } } -impl<'a> Equivalent for FragmentingValuesBorrowed<'a> { - fn equivalent(&self, key: &FragmentingValuesOwned) -> bool { +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 FragmentingValuesOwned. - // Because FragmentingValuesOwned is created by calling clone_boxed, it always creates Box of a proper type that DynamicFragmentingValues abstracts. + // 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)) } diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 2a5a5f184e649..6d4acef1eb1ac 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,6 +47,8 @@ pub struct Storages { pub resources: Resources, /// Backing storage for `!Send` resources. pub non_send_resources: Resources, + + pub shared: Shared, } impl Storages { diff --git a/crates/bevy_ecs/src/storage/shared.rs b/crates/bevy_ecs/src/storage/shared.rs new file mode 100644 index 0000000000000..a274c4f3350fe --- /dev/null +++ b/crates/bevy_ecs/src/storage/shared.rs @@ -0,0 +1,125 @@ +use bevy_platform::{collections::HashSet, sync::Arc}; +use core::{ + cell::Cell, + hash::{Hash, Hasher}, + ops::Deref, +}; +use indexmap::Equivalent; + +use crate::{ + component::{ComponentId, Tick}, + fragmenting_value::FragmentingValue, +}; + +pub struct SharedComponent { + component_id: ComponentId, + added: Cell, + value: SharedFragmentingValue, +} + +impl SharedComponent { + pub fn component_id(&self) -> ComponentId { + self.component_id + } + + pub fn added(&self) -> Tick { + self.added.get() + } + + pub fn check_tick(&self, change_tick: Tick) -> bool { + let mut tick = self.added.get(); + let wrapped = tick.check_tick(change_tick); + self.added.set(tick); + return wrapped; + } + + pub fn value(&self) -> &SharedFragmentingValue { + &self.value + } +} + +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); + } +} + +#[derive(Default)] +pub struct Shared { + values_set: HashSet, +} + +impl Shared { + pub fn get_or_insert( + &mut self, + current_tick: Tick, + component_id: ComponentId, + value: &dyn FragmentingValue, + ) -> &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())), + }) + } + + pub fn get(&self, value: &dyn FragmentingValue) -> Option<&SharedComponent> { + self.values_set.get(value) + } + + 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() { + component.check_tick(change_tick); + } + } +} + +impl Equivalent for dyn FragmentingValue { + fn equivalent(&self, key: &SharedComponent) -> bool { + *self == *key.value.as_ref() + } +} + +#[derive(Clone)] +pub struct SharedFragmentingValue(Arc); + +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); + } +} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 1eadc50605645..ae082aef98f7b 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1836,8 +1836,14 @@ impl<'w> EntityWorldMut<'w> { ) -> &mut Self { let location = self.location(); let change_tick = self.world.change_tick(); - let mut registrator = self.world.components_registrator(); - let value_components = FragmentingValuesBorrowed::from_bundle(&mut registrator, &bundle); + + 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, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index f2b801add8696..b712f42e6bafa 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2973,6 +2973,7 @@ impl World { ref mut sparse_sets, ref mut resources, ref mut non_send_resources, + ref mut shared, } = self.storages; #[cfg(feature = "trace")] @@ -2981,6 +2982,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::() { @@ -3000,6 +3002,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(); From 8c57aec286e943306fc94ad83abdf791194a2053 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sat, 31 May 2025 17:19:03 +0000 Subject: [PATCH 12/20] add `StorageType::Shared` --- crates/bevy_ecs/macros/src/component.rs | 6 +- crates/bevy_ecs/src/archetype.rs | 28 ++++-- crates/bevy_ecs/src/bundle.rs | 85 ++++++++++++++--- crates/bevy_ecs/src/component.rs | 2 + crates/bevy_ecs/src/fragmenting_value.rs | 15 ++- crates/bevy_ecs/src/query/fetch.rs | 93 ++++++++++++++++--- crates/bevy_ecs/src/query/filter.rs | 52 +++++++++-- crates/bevy_ecs/src/storage/mod.rs | 6 +- crates/bevy_ecs/src/storage/shared.rs | 74 +++++++++++++-- crates/bevy_ecs/src/world/entity_ref.rs | 75 ++++++++++----- crates/bevy_ecs/src/world/mod.rs | 6 +- crates/bevy_ecs/src/world/spawn_batch.rs | 4 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 46 ++++++++- 13 files changed, 407 insertions(+), 85 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index bcc72842f6873..112390f91d6ba 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -477,6 +477,7 @@ struct Attrs { enum StorageTy { Table, SparseSet, + Shared, } struct Require { @@ -496,6 +497,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 { @@ -521,9 +523,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}'.", ))); } }; @@ -659,6 +662,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/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index db8ddc817004f..c1047d5e27075 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -23,13 +23,13 @@ use crate::{ bundle::BundleId, component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, entity::{Entity, EntityLocation}, - fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed, FragmentingValuesShared}, + fragmenting_value::{FragmentingValuesBorrowed, FragmentingValuesShared}, observer::Observers, storage::{ ImmutableSparseSet, SharedFragmentingValue, SparseArray, SparseSet, TableId, TableRow, }, }; -use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use alloc::{boxed::Box, vec::Vec}; use bevy_platform::collections::HashMap; use core::{ hash::Hash, @@ -428,7 +428,7 @@ pub struct Archetype { impl Archetype { /// `table_components` and `sparse_set_components` must be sorted - pub(crate) fn new<'a>( + pub(crate) fn new( components: &Components, component_index: &mut ComponentIndex, observers: &Observers, @@ -481,9 +481,17 @@ impl Archetype { .insert(id, ArchetypeRecord { column: None }); } - for (component_id, archetype_component_id) in value_components.iter_ids_and_values() { + 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(archetype_component_id.clone()); + info.fragmenting_value = Some(value.clone()); + } else { + archetype_components.insert( + component_id, + ArchetypeComponentInfo { + storage_type: StorageType::Shared, + fragmenting_value: Some(value.clone()), + }, + ); } flags.insert(ArchetypeFlags::HAS_VALUE_COMPONENTS); } @@ -571,6 +579,14 @@ impl Archetype { .map(|(id, _)| *id) } + #[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. @@ -823,7 +839,7 @@ impl ArchetypeGeneration { struct ArchetypeComponents { table_components: Box<[ComponentId]>, sparse_set_components: Box<[ComponentId]>, - value_components: FragmentingValuesOwned, + value_components: FragmentingValuesShared, } /// Maps a [`ComponentId`] to the list of [`Archetypes`]([`Archetype`]) that contain the [`Component`](crate::component::Component), diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index cfc67726eb592..2ac2bb0c6710a 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -15,7 +15,7 @@ use crate::{ RequiredComponentConstructor, RequiredComponents, StorageType, Tick, }, entity::{Entities, Entity, EntityLocation}, - fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed, FragmentingValuesShared}, + fragmenting_value::{FragmentingValue, FragmentingValuesBorrowed}, observer::Observers, prelude::World, query::DebugCheckedUnwrap, @@ -666,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, @@ -718,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; }); @@ -775,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 + } } } } @@ -796,6 +810,7 @@ impl BundleInfo { archetype_id: ArchetypeId, value_components: &FragmentingValuesBorrowed, current_tick: Tick, + caller: MaybeLocation, ) -> ArchetypeId { if let Some(archetype_after_insert_id) = archetypes[archetype_id] .edges() @@ -830,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 => (), } } } @@ -847,6 +864,8 @@ 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 => (), } } } @@ -861,7 +880,7 @@ impl BundleInfo { component_id, storages .shared - .get_or_insert(current_tick, component_id, component_value) + .get_or_insert(current_tick, component_id, component_value, caller) .value() .clone(), )); @@ -963,7 +982,7 @@ impl BundleInfo { .map(|value| { storages .shared - .get_or_insert(current_tick, component_id, *value) + .get_or_insert(current_tick, component_id, *value, caller) .value() }) .unwrap_or(value) @@ -984,7 +1003,7 @@ impl BundleInfo { .edges_mut() .cache_archetype_value_components_after_bundle_insert( self.id, - value_components.to_shared(current_tick, &mut storages.shared), + value_components.to_shared(current_tick, &mut storages.shared, caller), value_archetype_id, ); value_archetype_id @@ -1037,6 +1056,7 @@ impl BundleInfo { 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 @@ -1046,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 @@ -1085,6 +1108,7 @@ impl BundleInfo { .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(); @@ -1150,6 +1174,7 @@ impl<'w> BundleInserter<'w> { 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 = @@ -1165,6 +1190,7 @@ impl<'w> BundleInserter<'w> { bundle_id, change_tick, value_components, + caller, ) } } @@ -1180,6 +1206,7 @@ impl<'w> BundleInserter<'w> { 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); @@ -1192,6 +1219,7 @@ impl<'w> BundleInserter<'w> { archetype_id, value_components, change_tick, + caller, ); if new_archetype_id == archetype_id { let archetype = &mut world.archetypes[archetype_id]; @@ -1307,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, @@ -1331,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); @@ -1356,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, @@ -1376,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); @@ -1438,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, @@ -1608,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, @@ -1629,6 +1666,7 @@ impl<'w> BundleRemover<'w> { location: EntityLocation, caller: MaybeLocation, pre_remove: impl FnOnce( + &Archetype, &mut SparseSets, Option<&mut Table>, &Components, @@ -1681,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() @@ -1803,7 +1842,12 @@ pub(crate) struct BundleSpawner<'w> { impl<'w> BundleSpawner<'w> { #[inline] - pub fn new(world: &'w mut World, change_tick: Tick, bundle: &T) -> 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) }; @@ -1812,13 +1856,17 @@ impl<'w> BundleSpawner<'w> { .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) } + 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) -> Self { + 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) }; @@ -1827,7 +1875,7 @@ impl<'w> BundleSpawner<'w> { .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, &value_components) } + unsafe { Self::new_with_id(world, bundle_id, change_tick, &value_components, caller) } } /// Creates a new [`BundleSpawner`]. @@ -1840,6 +1888,7 @@ impl<'w> BundleSpawner<'w> { 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( @@ -1850,6 +1899,7 @@ impl<'w> BundleSpawner<'w> { ArchetypeId::EMPTY, value_components, change_tick, + caller, ); let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; @@ -1887,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 9d28b18390b30..f3530a843ba50 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -702,6 +702,8 @@ pub enum StorageType { Table, /// Provides fast addition and removal of components, but slower iteration. SparseSet, + + Shared, } /// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`. diff --git a/crates/bevy_ecs/src/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs index 53b44d4589cb6..a9f2bcb5d9a6c 100644 --- a/crates/bevy_ecs/src/fragmenting_value.rs +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -17,6 +17,7 @@ use indexmap::Equivalent; use crate::{ bundle::Bundle, + change_detection::MaybeLocation, component::{ComponentId, ComponentKey, Components, ComponentsRegistrator, Immutable, Tick}, storage::{Shared, SharedFragmentingValue}, }; @@ -139,7 +140,7 @@ impl FromIterator<(ComponentId, SharedFragmentingValue)> for FragmentingValuesSh /// Borrowed version is used to query maps with [`FragmentingValuesShared`] keys. #[derive(Hash, PartialEq, Eq, Default)] pub struct FragmentingValuesBorrowed<'a> { - values: Vec<(ComponentId, &'a dyn FragmentingValue)>, + values: Box<[(ComponentId, &'a dyn FragmentingValue)]>, } impl<'a> FragmentingValuesBorrowed<'a> { @@ -149,7 +150,9 @@ impl<'a> FragmentingValuesBorrowed<'a> { 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 } + FragmentingValuesBorrowed { + values: values.into_boxed_slice(), + } } /// Returns `true` if there are no fragmenting values. @@ -162,6 +165,7 @@ impl<'a> FragmentingValuesBorrowed<'a> { &self, current_tick: Tick, shared_component_storage: &mut Shared, + caller: MaybeLocation, ) -> FragmentingValuesShared { self.values .iter() @@ -169,7 +173,7 @@ impl<'a> FragmentingValuesBorrowed<'a> { ( *id, shared_component_storage - .get_or_insert(current_tick, *id, *v) + .get_or_insert(current_tick, *id, *v, caller) .value() .clone(), ) @@ -192,7 +196,9 @@ impl<'a> FromIterator<(ComponentId, &'a dyn FragmentingValue)> for FragmentingVa values.push((id, value)); } values.sort_unstable_by_key(|(id, _)| *id); - FragmentingValuesBorrowed { values } + FragmentingValuesBorrowed { + values: values.into_boxed_slice(), + } } } @@ -374,6 +380,7 @@ mod tests { #[component( key=Self, immutable, + storage="Shared", )] struct Fragmenting(u32); diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index cffba8cda1689..b35cf46d1df41 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,7 @@ 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) } }, + || unsafe { (&world.storages().shared, None) }, ), last_run, this_run, @@ -1590,7 +1604,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 +1612,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 +1620,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 +1738,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 +1772,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 +1817,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 +1827,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 +1954,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 +2290,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 +2751,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 +2800,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 +2829,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..8facb1b7c2e4f 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,7 @@ 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) } }, + || unsafe { (&world.storages().shared, None) }, ), last_run, this_run, @@ -743,7 +746,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 +754,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 +762,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 +847,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 +934,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 +981,7 @@ 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) } }, + || unsafe { (&world.storages().shared, None) }, ), last_run, this_run, @@ -969,7 +991,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 +999,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 +1007,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 +1093,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/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 6d4acef1eb1ac..04f451160ad36 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -55,12 +55,12 @@ 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 index a274c4f3350fe..9de28b06fb71e 100644 --- a/crates/bevy_ecs/src/storage/shared.rs +++ b/crates/bevy_ecs/src/storage/shared.rs @@ -1,5 +1,6 @@ use bevy_platform::{collections::HashSet, sync::Arc}; use core::{ + any::Any, cell::Cell, hash::{Hash, Hasher}, ops::Deref, @@ -7,6 +8,7 @@ use core::{ use indexmap::Equivalent; use crate::{ + change_detection::MaybeLocation, component::{ComponentId, Tick}, fragmenting_value::FragmentingValue, }; @@ -14,6 +16,7 @@ use crate::{ pub struct SharedComponent { component_id: ComponentId, added: Cell, + location: MaybeLocation, value: SharedFragmentingValue, } @@ -26,16 +29,22 @@ impl SharedComponent { self.added.get() } - pub fn check_tick(&self, change_tick: Tick) -> bool { - let mut tick = self.added.get(); - let wrapped = tick.check_tick(change_tick); - self.added.set(tick); - return wrapped; - } - pub fn value(&self) -> &SharedFragmentingValue { &self.value } + + pub fn location(&self) -> &MaybeLocation { + &self.location + } + + 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 { @@ -63,12 +72,14 @@ impl Shared { 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, }) } @@ -76,13 +87,19 @@ impl Shared { self.values_set.get(value) } + pub fn get_shared(&self, value: &SharedFragmentingValue) -> Option<&SharedComponent> { + self.values_set.get(value) + } + pub fn clear(&mut self) { - self.values_set.clear() + self.values_set.clear(); } pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { for component in self.values_set.iter() { - component.check_tick(change_tick); + let mut tick = component.added.get(); + tick.check_tick(change_tick); + component.added.set(tick); } } } @@ -93,9 +110,21 @@ impl Equivalent for dyn FragmentingValue { } } +impl Equivalent for SharedFragmentingValue { + fn equivalent(&self, key: &SharedComponent) -> bool { + *self == key.value + } +} + #[derive(Clone)] pub struct SharedFragmentingValue(Arc); +impl SharedFragmentingValue { + 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() @@ -123,3 +152,30 @@ impl Hash for SharedFragmentingValue { self.0.hash(state); } } + +#[cfg(test)] +mod tests { + use alloc::{vec, vec::Vec}; + + use crate::{component::Component, world::World}; + + use super::*; + + #[derive(Component, Clone, Hash, PartialEq, Eq, Debug)] + #[component( + key=Self, + immutable, + 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::()); + } +} diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index ae082aef98f7b..11a1f9c5df811 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -25,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::{ @@ -34,6 +34,7 @@ use core::{ hash::{Hash, Hasher}, marker::PhantomData, mem::MaybeUninit, + ptr::NonNull, }; use thiserror::Error; @@ -1849,6 +1850,7 @@ impl<'w> EntityWorldMut<'w> { location.archetype_id, change_tick, &value_components, + caller, ); // SAFETY: location matches current entity. `T` matches `bundle_info` let (location, after_effect) = unsafe { @@ -1932,6 +1934,7 @@ impl<'w> EntityWorldMut<'w> { bundle_id, change_tick, &value_components, + caller, ); self.location = Some(insert_dynamic_bundle( @@ -2023,6 +2026,7 @@ impl<'w> EntityWorldMut<'w> { bundle_id, change_tick, &value_components, + MaybeLocation::caller(), ); self.location = Some(insert_dynamic_bundle( @@ -2064,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 b712f42e6bafa..618bf0736d5b3 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1153,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, &bundle); + 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) }; @@ -2301,6 +2301,7 @@ impl World { bundle_id, change_tick, &value_components, + caller, ) }, archetype_id: first_location.archetype_id, @@ -2335,6 +2336,7 @@ impl World { bundle_id, change_tick, &value_components, + caller, ) }, archetype_id: location.archetype_id, @@ -2463,6 +2465,7 @@ impl World { bundle_id, change_tick, &value_components, + caller, ) }, archetype_id: first_location.archetype_id, @@ -2504,6 +2507,7 @@ impl World { bundle_id, change_tick, &value_components, + caller, ) }, archetype_id: location.archetype_id, diff --git a/crates/bevy_ecs/src/world/spawn_batch.rs b/crates/bevy_ecs/src/world/spawn_batch.rs index 9d94c4200f284..b4d6ec5078eb4 100644 --- a/crates/bevy_ecs/src/world/spawn_batch.rs +++ b/crates/bevy_ecs/src/world/spawn_batch.rs @@ -47,7 +47,7 @@ where BundleSpawnerOrWorld::World(world) } else { let change_tick = world.change_tick(); - let mut spawner = BundleSpawner::new_uniform::(world, change_tick); + let mut spawner = BundleSpawner::new_uniform::(world, change_tick, caller); spawner.reserve_storage(length); BundleSpawnerOrWorld::Spawner(spawner) }; @@ -90,7 +90,7 @@ where match &mut self.spawner_or_world { BundleSpawnerOrWorld::World(world) => { let change_tick = world.change_tick(); - let mut spawner = BundleSpawner::new(world, change_tick, &bundle); + let mut spawner = BundleSpawner::new(world, change_tick, &bundle, self.caller); // SAFETY: bundle matches spawner type unsafe { Some(spawner.spawn(bundle, self.caller).0) } } 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(), + }), } } From 1cb6635e291b94c3eb2282c8c2f1a1eb85438830 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sun, 1 Jun 2025 09:44:26 +0000 Subject: [PATCH 13/20] doc pass --- crates/bevy_ecs/src/archetype.rs | 5 +++++ crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/component.rs | 5 ++++- crates/bevy_ecs/src/query/fetch.rs | 1 + crates/bevy_ecs/src/query/filter.rs | 2 ++ crates/bevy_ecs/src/storage/mod.rs | 2 +- crates/bevy_ecs/src/storage/shared.rs | 28 +++++++++++++++++++++++++++ 7 files changed, 42 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index c1047d5e27075..e70d9ec9ec805 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -579,6 +579,11 @@ 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 diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 2ac2bb0c6710a..63936cd814113 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -726,7 +726,7 @@ impl BundleInfo { .get_info(component_id) .and_then(|info| info.drop()) { - drop_fn(component_ptr) + drop_fn(component_ptr); } } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index f3530a843ba50..580ab9ee4dc11 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -702,7 +702,10 @@ 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, } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index b35cf46d1df41..044283619aaa0 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1594,6 +1594,7 @@ 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, diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 8facb1b7c2e4f..603a85c61eeee 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -736,6 +736,7 @@ 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, @@ -981,6 +982,7 @@ 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, diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 04f451160ad36..5da01b8291cf6 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -47,7 +47,7 @@ pub struct Storages { pub resources: Resources, /// Backing storage for `!Send` resources. pub non_send_resources: Resources, - + /// Backing storage for [`Shared`] components. pub shared: Shared, } diff --git a/crates/bevy_ecs/src/storage/shared.rs b/crates/bevy_ecs/src/storage/shared.rs index 9de28b06fb71e..c938e92c99145 100644 --- a/crates/bevy_ecs/src/storage/shared.rs +++ b/crates/bevy_ecs/src/storage/shared.rs @@ -13,6 +13,7 @@ use crate::{ fragmenting_value::FragmentingValue, }; +/// Stores data associated with a shared component. pub struct SharedComponent { component_id: ComponentId, added: Cell, @@ -21,22 +22,27 @@ pub struct SharedComponent { } 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. @@ -61,12 +67,20 @@ impl Hash for SharedComponent { } } +/// 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, @@ -83,14 +97,24 @@ impl Shared { }) } + /// 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(); } @@ -116,10 +140,14 @@ impl Equivalent for SharedFragmentingValue { } } +/// 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() } From 7689c1f4fcf0b536dca3edfd11375bce449d1b6f Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sun, 1 Jun 2025 10:01:50 +0000 Subject: [PATCH 14/20] Enforce `Shared` storage invariants automatically --- crates/bevy_ecs/macros/src/component.rs | 9 ++++++--- crates/bevy_ecs/src/component.rs | 14 ++++++++++++-- crates/bevy_ecs/src/fragmenting_value.rs | 6 +----- crates/bevy_ecs/src/storage/shared.rs | 6 +----- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 112390f91d6ba..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::)) @@ -262,6 +263,8 @@ 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} }; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 580ab9ee4dc11..75e411a59637b 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -665,7 +665,11 @@ impl private::Seal for NoKey {} impl ComponentKey for NoKey { type KeyType = (); type ValueType = C; - const INVARIANT_ASSERT: () = (); + 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 @@ -706,6 +710,8 @@ pub enum StorageType { /// 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, } @@ -1199,7 +1205,8 @@ impl ComponentDescriptor { /// - if `fragmenting_value_vtable` is not `None`, it must be usable on a pointer with a value of the layout `layout` /// /// # Panics - /// This will panic if `fragmenting_value_vtable` is not `None` and `mutable` is `true`. Fragmenting value components must be immutable. + /// - 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, @@ -1209,6 +1216,9 @@ impl ComponentDescriptor { 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"); } diff --git a/crates/bevy_ecs/src/fragmenting_value.rs b/crates/bevy_ecs/src/fragmenting_value.rs index a9f2bcb5d9a6c..b3442a31b5bee 100644 --- a/crates/bevy_ecs/src/fragmenting_value.rs +++ b/crates/bevy_ecs/src/fragmenting_value.rs @@ -377,11 +377,7 @@ mod tests { use core::hash::Hash; #[derive(Component, Clone, Eq, PartialEq, Hash)] - #[component( - key=Self, - immutable, - storage="Shared", - )] + #[component(storage = "Shared")] struct Fragmenting(u32); #[derive(Component)] diff --git a/crates/bevy_ecs/src/storage/shared.rs b/crates/bevy_ecs/src/storage/shared.rs index c938e92c99145..9df01a316a83c 100644 --- a/crates/bevy_ecs/src/storage/shared.rs +++ b/crates/bevy_ecs/src/storage/shared.rs @@ -190,11 +190,7 @@ mod tests { use super::*; #[derive(Component, Clone, Hash, PartialEq, Eq, Debug)] - #[component( - key=Self, - immutable, - storage="Shared" - )] + #[component(storage = "Shared")] struct SharedComponent(Vec); #[test] From 49c0111c696a7d2631c6ac64248d4593fe38dc65 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sun, 1 Jun 2025 10:19:54 +0000 Subject: [PATCH 15/20] add shared components to component index --- crates/bevy_ecs/src/archetype.rs | 4 ++++ crates/bevy_ecs/src/storage/shared.rs | 28 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index e70d9ec9ec805..33d265b15468a 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -492,6 +492,10 @@ impl Archetype { fragmenting_value: Some(value.clone()), }, ); + component_index + .entry(component_id) + .or_default() + .insert(id, ArchetypeRecord { column: None }); } flags.insert(ArchetypeFlags::HAS_VALUE_COMPONENTS); } diff --git a/crates/bevy_ecs/src/storage/shared.rs b/crates/bevy_ecs/src/storage/shared.rs index 9df01a316a83c..482ea6d94e1fc 100644 --- a/crates/bevy_ecs/src/storage/shared.rs +++ b/crates/bevy_ecs/src/storage/shared.rs @@ -185,7 +185,13 @@ impl Hash for SharedFragmentingValue { mod tests { use alloc::{vec, vec::Vec}; - use crate::{component::Component, world::World}; + use crate::{ + change_detection::DetectChanges, + component::Component, + entity::Entity, + query::With, + world::{Ref, World}, + }; use super::*; @@ -202,4 +208,24 @@ mod tests { 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)); + } } From 2bf058bfaf187a4301573d15f068667559943a17 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sun, 1 Jun 2025 10:39:14 +0000 Subject: [PATCH 16/20] use Shared in fragmenting value benches --- benches/benches/bevy_ecs/components/fragmenting_values.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/benches/bevy_ecs/components/fragmenting_values.rs b/benches/benches/bevy_ecs/components/fragmenting_values.rs index d53a91bf652f4..70c9d652a6db3 100644 --- a/benches/benches/bevy_ecs/components/fragmenting_values.rs +++ b/benches/benches/bevy_ecs/components/fragmenting_values.rs @@ -3,7 +3,7 @@ use criterion::Criterion; use glam::*; #[derive(Component, PartialEq, Eq, Hash, Clone)] -#[component(immutable, key=Self)] +#[component(storage = "Shared")] struct Fragmenting(u32); #[derive(Component)] From 9ced4fa342528ec59b3c758c69e13a9f04488e3d Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sun, 1 Jun 2025 11:13:10 +0000 Subject: [PATCH 17/20] add simple iteration benchmark --- .../bevy_ecs/iteration/iter_simple_shared.rs | 51 +++++++++++++++++++ benches/benches/bevy_ecs/iteration/mod.rs | 5 ++ 2 files changed, 56 insertions(+) create mode 100644 benches/benches/bevy_ecs/iteration/iter_simple_shared.rs 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()); From 41cfc3b8147c5f10acbefc4a8a299c931e3c33a2 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sun, 1 Jun 2025 12:04:40 +0000 Subject: [PATCH 18/20] fix broken doc links --- crates/bevy_ecs/src/archetype.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 33d265b15468a..ff1a5050936fa 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -145,7 +145,7 @@ 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`]s of components. + /// 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. @@ -736,7 +736,7 @@ impl Archetype { .map(|info| info.storage_type) } - /// Returns [`FragmentingValue`] for this archetype of the requested `component_id`. + /// 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( From a71bdcd443b29cb9bb49dd480b5200ec39307994 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sun, 1 Jun 2025 12:16:17 +0000 Subject: [PATCH 19/20] update migration guide --- .../migration-guides/fragmenting_value_components.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/release-content/migration-guides/fragmenting_value_components.md b/release-content/migration-guides/fragmenting_value_components.md index 83bad2ff194ab..428c1f4b12221 100644 --- a/release-content/migration-guides/fragmenting_value_components.md +++ b/release-content/migration-guides/fragmenting_value_components.md @@ -1,6 +1,6 @@ --- title: Added support for fragmenting value components -pull_requests: [19153] +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. @@ -11,3 +11,5 @@ Manual impl of `Component` trait now has a new associated type, `Key`, that shou `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`. From 64ebc24c8799b795df88edb810e6eef8f51270c2 Mon Sep 17 00:00:00 2001 From: eugineerd Date: Sun, 1 Jun 2025 12:39:59 +0000 Subject: [PATCH 20/20] add release note --- .../release-notes/shared_component_storage.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 release-content/release-notes/shared_component_storage.md 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.