From 1c358b2c3633fa4ea033f7fd35133708b167e933 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Fri, 2 May 2025 22:45:08 +0200 Subject: [PATCH 1/2] changed Vec> into a HashMap --- crates/bevy_ecs/src/component.rs | 59 ++++++++++---------------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index bfa55804b616a..2874ce4c80090 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1778,9 +1778,7 @@ impl<'w> ComponentsRegistrator<'w> { &mut self .components .components - .get_mut(id.0) - .debug_checked_unwrap() - .as_mut() + .get_mut(&id) .debug_checked_unwrap() }; @@ -1955,7 +1953,7 @@ impl<'w> ComponentsRegistrator<'w> { /// Stores metadata associated with each kind of [`Component`] in a given [`World`]. #[derive(Debug, Default)] pub struct Components { - components: Vec>, + components: HashMap, indices: TypeIdMap, resource_indices: TypeIdMap, // This is kept internal and local to verify that no deadlocks can occor. @@ -1974,16 +1972,10 @@ impl Components { id: ComponentId, descriptor: ComponentDescriptor, ) { + // Caller ensure id is unique + debug_assert!(!self.components.contains_key(&id)); let info = ComponentInfo::new(id, descriptor); - let least_len = id.0 + 1; - if self.components.len() < least_len { - self.components.resize_with(least_len, || None); - } - // SAFETY: We just extended the vec to make this index valid. - let slot = unsafe { self.components.get_mut(id.0).debug_checked_unwrap() }; - // Caller ensures id is unique - debug_assert!(slot.is_none()); - *slot = Some(info); + unsafe { self.components.insert_unique_unchecked(id, info) }; } /// Returns the number of components registered or queued with this instance. @@ -2045,7 +2037,7 @@ impl Components { /// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value. #[inline] pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> { - self.components.get(id.0).and_then(|info| info.as_ref()) + self.components.get(&id) } /// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present. @@ -2057,8 +2049,8 @@ impl Components { #[inline] pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option> { self.components - .get(id.0) - .and_then(|info| info.as_ref().map(|info| Cow::Borrowed(&info.descriptor))) + .get(&id) + .map(|info| Cow::Borrowed(&info.descriptor)) .or_else(|| { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); // first check components, then resources, then dynamic @@ -2079,11 +2071,8 @@ impl Components { #[inline] pub fn get_name<'a>(&'a self, id: ComponentId) -> Option> { self.components - .get(id.0) - .and_then(|info| { - info.as_ref() - .map(|info| Cow::Borrowed(info.descriptor.name())) - }) + .get(&id) + .map(|info| Cow::Borrowed(info.descriptor.name())) .or_else(|| { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); // first check components, then resources, then dynamic @@ -2104,20 +2093,12 @@ impl Components { #[inline] pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo { // SAFETY: The caller ensures `id` is valid. - unsafe { - self.components - .get(id.0) - .debug_checked_unwrap() - .as_ref() - .debug_checked_unwrap() - } + unsafe { self.components.get(&id).debug_checked_unwrap() } } #[inline] pub(crate) fn get_hooks_mut(&mut self, id: ComponentId) -> Option<&mut ComponentHooks> { - self.components - .get_mut(id.0) - .and_then(|info| info.as_mut().map(|info| &mut info.hooks)) + self.components.get_mut(&id).map(|info| &mut info.hooks) } #[inline] @@ -2126,8 +2107,8 @@ impl Components { id: ComponentId, ) -> Option<&mut RequiredComponents> { self.components - .get_mut(id.0) - .and_then(|info| info.as_mut().map(|info| &mut info.required_components)) + .get_mut(&id) + .map(|info| &mut info.required_components) } /// Registers the given component `R` and [required components] inherited from it as required by `T`. @@ -2336,9 +2317,7 @@ impl Components { #[inline] pub(crate) fn get_required_by(&self, id: ComponentId) -> Option<&HashSet> { - self.components - .get(id.0) - .and_then(|info| info.as_ref().map(|info| &info.required_by)) + self.components.get(&id).map(|info| &info.required_by) } #[inline] @@ -2347,8 +2326,8 @@ impl Components { id: ComponentId, ) -> Option<&mut HashSet> { self.components - .get_mut(id.0) - .and_then(|info| info.as_mut().map(|info| &mut info.required_by)) + .get_mut(&id) + .map(|info| &mut info.required_by) } /// Returns true if the [`ComponentId`] is fully registered and valid. @@ -2356,7 +2335,7 @@ impl Components { /// Those ids are still correct, but they are not usable in every context yet. #[inline] pub fn is_id_valid(&self, id: ComponentId) -> bool { - self.components.get(id.0).is_some_and(Option::is_some) + self.components.contains_key(&id) } /// Type-erased equivalent of [`Components::valid_component_id()`]. @@ -2533,7 +2512,7 @@ impl Components { /// Gets an iterator over all components fully registered with this instance. pub fn iter_registered(&self) -> impl Iterator + '_ { - self.components.iter().filter_map(Option::as_ref) + self.components.values() } } From 99a32c72bae6e4dbf5eca6f7eef0d434d42580af Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 4 May 2025 00:56:42 +0200 Subject: [PATCH 2/2] added hybrid storage --- crates/bevy_ecs/src/component.rs | 4 +- crates/bevy_ecs/src/storage/hybrid.rs | 95 +++++++++++++++++++++++++++ crates/bevy_ecs/src/storage/mod.rs | 2 + 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 crates/bevy_ecs/src/storage/hybrid.rs diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 2874ce4c80090..0ede8f679b66b 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -8,7 +8,7 @@ use crate::{ query::DebugCheckedUnwrap, relationship::RelationshipHookMode, resource::Resource, - storage::{SparseSetIndex, SparseSets, Table, TableRow}, + storage::{HybridMap, SparseSetIndex, SparseSets, Table, TableRow}, system::{Local, SystemParam}, world::{DeferredWorld, FromWorld, World}, }; @@ -1953,7 +1953,7 @@ impl<'w> ComponentsRegistrator<'w> { /// Stores metadata associated with each kind of [`Component`] in a given [`World`]. #[derive(Debug, Default)] pub struct Components { - components: HashMap, + components: HybridMap, indices: TypeIdMap, resource_indices: TypeIdMap, // This is kept internal and local to verify that no deadlocks can occor. diff --git a/crates/bevy_ecs/src/storage/hybrid.rs b/crates/bevy_ecs/src/storage/hybrid.rs new file mode 100644 index 0000000000000..99e30ef5cde7c --- /dev/null +++ b/crates/bevy_ecs/src/storage/hybrid.rs @@ -0,0 +1,95 @@ +use crate::query::DebugCheckedUnwrap; +use bevy_platform::collections::HashMap; +use bevy_platform::prelude::Vec; + +use super::SparseSetIndex; + +// Hybrid datastructures that can store both sparse and dense indices efficiently. +// +// This is done quite simply by having the first N indices be stored in a `Vec` or array, and for any indices larger than N, a sparse datastructure is used (like a HashMap). + +#[derive(Debug)] +pub struct HybridMap +where + K: SparseSetIndex, +{ + cutoff: usize, + vec: Vec>, + hashmap: HashMap, +} + +impl Default for HybridMap +where + K: SparseSetIndex, +{ + fn default() -> Self { + HybridMap { + cutoff: 128, + vec: Vec::new(), + hashmap: HashMap::new(), + } + } +} + +impl HybridMap +where + K: SparseSetIndex, +{ + pub fn contains_key(&self, key: &K) -> bool { + let index = key.sparse_set_index(); + if index < self.cutoff { + self.vec.get(index).is_some_and(Option::is_some) + } else { + self.contains_key(key) + } + } + + pub fn get(&self, key: &K) -> Option<&V> { + let index = key.sparse_set_index(); + if index < self.cutoff { + self.vec.get(index).and_then(|val| val.as_ref()) + } else { + self.hashmap.get(key) + } + } + + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + let index = key.sparse_set_index(); + if index < self.cutoff { + self.vec.get_mut(index).and_then(|val| val.as_mut()) + } else { + self.hashmap.get_mut(key) + } + } + + pub unsafe fn insert_unique_unchecked(&mut self, key: K, value: V) { + let index = key.sparse_set_index(); + if index < self.cutoff { + let least_len = index + 1; + if self.vec.len() < least_len { + self.vec.resize_with(least_len, || None); + } + // SAFETY: We just extended the vec to make this index valid + let slot = unsafe { self.vec.get_mut(index).debug_checked_unwrap() }; + // Caller ensures id is unique + debug_assert!(slot.is_none()); + *slot = Some(value); + } else { + // SAFETY: Caller ensures id is unique + unsafe { + self.hashmap.insert_unique_unchecked(key, value); + } + } + } + + pub fn len(&self) -> usize { + self.vec.len() + self.hashmap.len() + } + + pub fn values(&self) -> impl Iterator + '_ { + self.vec + .iter() + .filter_map(Option::as_ref) + .chain(self.hashmap.values()) + } +} diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 2a5a5f184e649..19ca21cb951e2 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -22,11 +22,13 @@ mod blob_array; mod blob_vec; +mod hybrid; mod resource; mod sparse_set; mod table; mod thin_array_ptr; +pub use hybrid::*; pub use resource::*; pub use sparse_set::*; pub use table::*;