Use u32 over usize for ComponentSparseSet indicies (#4723)
# Objective Use less memory to store SparseSet components. ## Solution Change `ComponentSparseSet` to only use `Entity::id` in it's key internally, and change the usize value in it's SparseArray to use u32 instead, as it cannot have more than u32::MAX live entities stored at once. This should reduce the overhead of storing components in sparse set storage by 50%.
This commit is contained in:
parent
c5e89894f4
commit
8e4e5a5634
@ -6,6 +6,8 @@ use crate::{
|
|||||||
use bevy_ptr::{OwningPtr, Ptr};
|
use bevy_ptr::{OwningPtr, Ptr};
|
||||||
use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData};
|
use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData};
|
||||||
|
|
||||||
|
type EntityId = u32;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SparseArray<I, V = I> {
|
pub struct SparseArray<I, V = I> {
|
||||||
values: Vec<Option<V>>,
|
values: Vec<Option<V>>,
|
||||||
@ -96,8 +98,14 @@ impl<I: SparseSetIndex, V> SparseArray<I, V> {
|
|||||||
pub struct ComponentSparseSet {
|
pub struct ComponentSparseSet {
|
||||||
dense: BlobVec,
|
dense: BlobVec,
|
||||||
ticks: Vec<UnsafeCell<ComponentTicks>>,
|
ticks: Vec<UnsafeCell<ComponentTicks>>,
|
||||||
|
// Internally this only relies on the Entity ID to keep track of where the component data is
|
||||||
|
// stored for entities that are alive. The generation is not required, but is stored
|
||||||
|
// in debug builds to validate that access is correct.
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
entities: Vec<EntityId>,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
entities: Vec<Entity>,
|
entities: Vec<Entity>,
|
||||||
sparse: SparseArray<Entity, usize>,
|
sparse: SparseArray<EntityId, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentSparseSet {
|
impl ComponentSparseSet {
|
||||||
@ -137,38 +145,55 @@ impl ComponentSparseSet {
|
|||||||
/// The `value` pointer must point to a valid address that matches the [`Layout`](std::alloc::Layout)
|
/// The `value` pointer must point to a valid address that matches the [`Layout`](std::alloc::Layout)
|
||||||
/// inside the [`ComponentInfo`] given when constructing this sparse set.
|
/// inside the [`ComponentInfo`] given when constructing this sparse set.
|
||||||
pub unsafe fn insert(&mut self, entity: Entity, value: OwningPtr<'_>, change_tick: u32) {
|
pub unsafe fn insert(&mut self, entity: Entity, value: OwningPtr<'_>, change_tick: u32) {
|
||||||
if let Some(&dense_index) = self.sparse.get(entity) {
|
if let Some(&dense_index) = self.sparse.get(entity.id()) {
|
||||||
self.dense.replace_unchecked(dense_index, value);
|
debug_assert_eq!(entity, self.entities[dense_index as usize]);
|
||||||
*self.ticks.get_unchecked_mut(dense_index) =
|
let _entity = self.dense.replace_unchecked(dense_index as usize, value);
|
||||||
|
*self.ticks.get_unchecked_mut(dense_index as usize) =
|
||||||
UnsafeCell::new(ComponentTicks::new(change_tick));
|
UnsafeCell::new(ComponentTicks::new(change_tick));
|
||||||
} else {
|
} else {
|
||||||
let dense_index = self.dense.len();
|
let dense_index = self.dense.len();
|
||||||
self.dense.push(value);
|
self.dense.push(value);
|
||||||
self.sparse.insert(entity, dense_index);
|
self.sparse.insert(entity.id(), dense_index as u32);
|
||||||
debug_assert_eq!(self.ticks.len(), dense_index);
|
debug_assert_eq!(self.ticks.len(), dense_index);
|
||||||
debug_assert_eq!(self.entities.len(), dense_index);
|
debug_assert_eq!(self.entities.len(), dense_index);
|
||||||
self.ticks
|
self.ticks
|
||||||
.push(UnsafeCell::new(ComponentTicks::new(change_tick)));
|
.push(UnsafeCell::new(ComponentTicks::new(change_tick)));
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
self.entities.push(entity.id());
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains(&self, entity: Entity) -> bool {
|
pub fn contains(&self, entity: Entity) -> bool {
|
||||||
self.sparse.contains(entity)
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
if let Some(&dense_index) = self.sparse.get(entity.id()) {
|
||||||
|
debug_assert_eq!(entity, self.entities[dense_index as usize]);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
self.sparse.contains(entity.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get(&self, entity: Entity) -> Option<Ptr<'_>> {
|
pub fn get(&self, entity: Entity) -> Option<Ptr<'_>> {
|
||||||
self.sparse.get(entity).map(|dense_index| {
|
self.sparse.get(entity.id()).map(|dense_index| {
|
||||||
|
let dense_index = *dense_index as usize;
|
||||||
|
debug_assert_eq!(entity, self.entities[dense_index]);
|
||||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||||
unsafe { self.dense.get_unchecked(*dense_index) }
|
unsafe { self.dense.get_unchecked(dense_index) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
|
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, &UnsafeCell<ComponentTicks>)> {
|
||||||
let dense_index = *self.sparse.get(entity)?;
|
let dense_index = *self.sparse.get(entity.id())? as usize;
|
||||||
|
debug_assert_eq!(entity, self.entities[dense_index]);
|
||||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||||
unsafe {
|
unsafe {
|
||||||
Some((
|
Some((
|
||||||
@ -180,7 +205,8 @@ impl ComponentSparseSet {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_ticks(&self, entity: Entity) -> Option<&UnsafeCell<ComponentTicks>> {
|
pub fn get_ticks(&self, entity: Entity) -> Option<&UnsafeCell<ComponentTicks>> {
|
||||||
let dense_index = *self.sparse.get(entity)?;
|
let dense_index = *self.sparse.get(entity.id())? as usize;
|
||||||
|
debug_assert_eq!(entity, self.entities[dense_index]);
|
||||||
// SAFE: if the sparse index points to something in the dense vec, it exists
|
// SAFE: if the sparse index points to something in the dense vec, it exists
|
||||||
unsafe { Some(self.ticks.get_unchecked(dense_index)) }
|
unsafe { Some(self.ticks.get_unchecked(dense_index)) }
|
||||||
}
|
}
|
||||||
@ -189,7 +215,9 @@ impl ComponentSparseSet {
|
|||||||
/// it exists).
|
/// it exists).
|
||||||
#[must_use = "The returned pointer must be used to drop the removed component."]
|
#[must_use = "The returned pointer must be used to drop the removed component."]
|
||||||
pub fn remove_and_forget(&mut self, entity: Entity) -> Option<OwningPtr<'_>> {
|
pub fn remove_and_forget(&mut self, entity: Entity) -> Option<OwningPtr<'_>> {
|
||||||
self.sparse.remove(entity).map(|dense_index| {
|
self.sparse.remove(entity.id()).map(|dense_index| {
|
||||||
|
let dense_index = dense_index as usize;
|
||||||
|
debug_assert_eq!(entity, self.entities[dense_index]);
|
||||||
self.ticks.swap_remove(dense_index);
|
self.ticks.swap_remove(dense_index);
|
||||||
self.entities.swap_remove(dense_index);
|
self.entities.swap_remove(dense_index);
|
||||||
let is_last = dense_index == self.dense.len() - 1;
|
let is_last = dense_index == self.dense.len() - 1;
|
||||||
@ -197,14 +225,20 @@ impl ComponentSparseSet {
|
|||||||
let value = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
|
let value = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
|
||||||
if !is_last {
|
if !is_last {
|
||||||
let swapped_entity = self.entities[dense_index];
|
let swapped_entity = self.entities[dense_index];
|
||||||
*self.sparse.get_mut(swapped_entity).unwrap() = dense_index;
|
#[cfg(not(debug_assertions))]
|
||||||
|
let idx = swapped_entity;
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let idx = swapped_entity.id();
|
||||||
|
*self.sparse.get_mut(idx).unwrap() = dense_index as u32;
|
||||||
}
|
}
|
||||||
value
|
value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, entity: Entity) -> bool {
|
pub fn remove(&mut self, entity: Entity) -> bool {
|
||||||
if let Some(dense_index) = self.sparse.remove(entity) {
|
if let Some(dense_index) = self.sparse.remove(entity.id()) {
|
||||||
|
let dense_index = dense_index as usize;
|
||||||
|
debug_assert_eq!(entity, self.entities[dense_index]);
|
||||||
self.ticks.swap_remove(dense_index);
|
self.ticks.swap_remove(dense_index);
|
||||||
self.entities.swap_remove(dense_index);
|
self.entities.swap_remove(dense_index);
|
||||||
let is_last = dense_index == self.dense.len() - 1;
|
let is_last = dense_index == self.dense.len() - 1;
|
||||||
@ -212,7 +246,11 @@ impl ComponentSparseSet {
|
|||||||
unsafe { self.dense.swap_remove_and_drop_unchecked(dense_index) }
|
unsafe { self.dense.swap_remove_and_drop_unchecked(dense_index) }
|
||||||
if !is_last {
|
if !is_last {
|
||||||
let swapped_entity = self.entities[dense_index];
|
let swapped_entity = self.entities[dense_index];
|
||||||
*self.sparse.get_mut(swapped_entity).unwrap() = dense_index;
|
#[cfg(not(debug_assertions))]
|
||||||
|
let idx = swapped_entity;
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let idx = swapped_entity.id();
|
||||||
|
*self.sparse.get_mut(idx).unwrap() = dense_index as u32;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user