Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 20 additions & 41 deletions crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -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()
};

Expand Down Expand Up @@ -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<Option<ComponentInfo>>,
components: HybridMap<ComponentId, ComponentInfo>,
indices: TypeIdMap<ComponentId>,
resource_indices: TypeIdMap<ComponentId>,
// This is kept internal and local to verify that no deadlocks can occor.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -2057,8 +2049,8 @@ impl Components {
#[inline]
pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, ComponentDescriptor>> {
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
Expand All @@ -2079,11 +2071,8 @@ impl Components {
#[inline]
pub fn get_name<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, str>> {
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
Expand All @@ -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]
Expand All @@ -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`.
Expand Down Expand Up @@ -2336,9 +2317,7 @@ impl Components {

#[inline]
pub(crate) fn get_required_by(&self, id: ComponentId) -> Option<&HashSet<ComponentId>> {
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]
Expand All @@ -2347,16 +2326,16 @@ impl Components {
id: ComponentId,
) -> Option<&mut HashSet<ComponentId>> {
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.
/// Ids may be invalid if they are still queued to be registered.
/// 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()`].
Expand Down Expand Up @@ -2533,7 +2512,7 @@ impl Components {

/// Gets an iterator over all components fully registered with this instance.
pub fn iter_registered(&self) -> impl Iterator<Item = &ComponentInfo> + '_ {
self.components.iter().filter_map(Option::as_ref)
self.components.values()
}
}

Expand Down
95 changes: 95 additions & 0 deletions crates/bevy_ecs/src/storage/hybrid.rs
Original file line number Diff line number Diff line change
@@ -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<K, V>
where
K: SparseSetIndex,
{
cutoff: usize,
vec: Vec<Option<V>>,
hashmap: HashMap<K, V>,
}

impl<K, V> Default for HybridMap<K, V>
where
K: SparseSetIndex,
{
fn default() -> Self {
HybridMap {
cutoff: 128,
vec: Vec::new(),
hashmap: HashMap::new(),
}
}
}

impl<K, V> HybridMap<K, V>
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<Item = &V> + '_ {
self.vec
.iter()
.filter_map(Option::as_ref)
.chain(self.hashmap.values())
}
}
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down
Loading