Skip to content

Commit ced100f

Browse files
james7132cart
authored andcommitted
Immutable sparse sets for metadata storage (bevyengine#4928)
# Objective Make core types in ECS smaller. The column sparse set in Tables is never updated after creation. ## Solution Create `ImmutableSparseSet` which removes the capacity fields in the backing vec's and the APIs for inserting or removing elements. Drops the size of the sparse set by 3 usizes (24 bytes on 64-bit systems) ## Followup ~~After bevyengine#4809, Archetype's component SparseSet should be replaced with it.~~ This has been done. --- ## Changelog Removed: `Table::component_capacity` ## Migration Guide `Table::component_capacity()` has been removed as Tables do not support adding/removing columns after construction. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
1 parent 59da1ca commit ced100f

File tree

3 files changed

+167
-83
lines changed

3 files changed

+167
-83
lines changed

crates/bevy_ecs/src/archetype.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
bundle::BundleId,
66
component::{ComponentId, StorageType},
77
entity::{Entity, EntityLocation},
8-
storage::{SparseArray, SparseSet, SparseSetIndex, TableId},
8+
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId},
99
};
1010
use std::{
1111
collections::HashMap,
@@ -182,7 +182,7 @@ pub struct Archetype {
182182
table_id: TableId,
183183
edges: Edges,
184184
entities: Vec<ArchetypeEntity>,
185-
components: SparseSet<ComponentId, ArchetypeComponentInfo>,
185+
components: ImmutableSparseSet<ComponentId, ArchetypeComponentInfo>,
186186
}
187187

188188
impl Archetype {
@@ -217,8 +217,8 @@ impl Archetype {
217217
Self {
218218
id,
219219
table_id,
220-
components,
221-
entities: Default::default(),
220+
entities: Vec::new(),
221+
components: components.into_immutable(),
222222
edges: Default::default(),
223223
}
224224
}

crates/bevy_ecs/src/storage/sparse_set.rs

+106-55
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ pub(crate) struct SparseArray<I, V = I> {
1414
marker: PhantomData<I>,
1515
}
1616

17+
/// A space-optimized version of [`SparseArray`] that cannot be changed
18+
/// after construction.
19+
#[derive(Debug)]
20+
pub(crate) struct ImmutableSparseArray<I, V = I> {
21+
values: Box<[Option<V>]>,
22+
marker: PhantomData<I>,
23+
}
24+
1725
impl<I: SparseSetIndex, V> Default for SparseArray<I, V> {
1826
fn default() -> Self {
1927
Self::new()
@@ -30,6 +38,27 @@ impl<I, V> SparseArray<I, V> {
3038
}
3139
}
3240

41+
macro_rules! impl_sparse_array {
42+
($ty:ident) => {
43+
impl<I: SparseSetIndex, V> $ty<I, V> {
44+
#[inline]
45+
pub fn contains(&self, index: I) -> bool {
46+
let index = index.sparse_set_index();
47+
self.values.get(index).map(|v| v.is_some()).unwrap_or(false)
48+
}
49+
50+
#[inline]
51+
pub fn get(&self, index: I) -> Option<&V> {
52+
let index = index.sparse_set_index();
53+
self.values.get(index).map(|v| v.as_ref()).unwrap_or(None)
54+
}
55+
}
56+
};
57+
}
58+
59+
impl_sparse_array!(SparseArray);
60+
impl_sparse_array!(ImmutableSparseArray);
61+
3362
impl<I: SparseSetIndex, V> SparseArray<I, V> {
3463
#[inline]
3564
pub fn insert(&mut self, index: I, value: V) {
@@ -40,18 +69,6 @@ impl<I: SparseSetIndex, V> SparseArray<I, V> {
4069
self.values[index] = Some(value);
4170
}
4271

43-
#[inline]
44-
pub fn contains(&self, index: I) -> bool {
45-
let index = index.sparse_set_index();
46-
self.values.get(index).map(|v| v.is_some()).unwrap_or(false)
47-
}
48-
49-
#[inline]
50-
pub fn get(&self, index: I) -> Option<&V> {
51-
let index = index.sparse_set_index();
52-
self.values.get(index).map(|v| v.as_ref()).unwrap_or(None)
53-
}
54-
5572
#[inline]
5673
pub fn get_mut(&mut self, index: I) -> Option<&mut V> {
5774
let index = index.sparse_set_index();
@@ -70,6 +87,13 @@ impl<I: SparseSetIndex, V> SparseArray<I, V> {
7087
pub fn clear(&mut self) {
7188
self.values.clear();
7289
}
90+
91+
pub(crate) fn into_immutable(self) -> ImmutableSparseArray<I, V> {
92+
ImmutableSparseArray {
93+
values: self.values.into_boxed_slice(),
94+
marker: PhantomData,
95+
}
96+
}
7397
}
7498

7599
/// A sparse data structure of [Components](crate::component::Component)
@@ -249,11 +273,75 @@ pub struct SparseSet<I, V: 'static> {
249273
sparse: SparseArray<I, usize>,
250274
}
251275

276+
/// A space-optimized version of [`SparseSet`] that cannot be changed
277+
/// after construction.
278+
#[derive(Debug)]
279+
pub(crate) struct ImmutableSparseSet<I, V: 'static> {
280+
dense: Box<[V]>,
281+
indices: Box<[I]>,
282+
sparse: ImmutableSparseArray<I, usize>,
283+
}
284+
285+
macro_rules! impl_sparse_set {
286+
($ty:ident) => {
287+
impl<I: SparseSetIndex, V> $ty<I, V> {
288+
#[inline]
289+
pub fn len(&self) -> usize {
290+
self.dense.len()
291+
}
292+
293+
#[inline]
294+
pub fn contains(&self, index: I) -> bool {
295+
self.sparse.contains(index)
296+
}
297+
298+
pub fn get(&self, index: I) -> Option<&V> {
299+
self.sparse.get(index).map(|dense_index| {
300+
// SAFETY: if the sparse index points to something in the dense vec, it exists
301+
unsafe { self.dense.get_unchecked(*dense_index) }
302+
})
303+
}
304+
305+
pub fn get_mut(&mut self, index: I) -> Option<&mut V> {
306+
let dense = &mut self.dense;
307+
self.sparse.get(index).map(move |dense_index| {
308+
// SAFETY: if the sparse index points to something in the dense vec, it exists
309+
unsafe { dense.get_unchecked_mut(*dense_index) }
310+
})
311+
}
312+
313+
pub fn indices(&self) -> impl Iterator<Item = I> + '_ {
314+
self.indices.iter().cloned()
315+
}
316+
317+
pub fn values(&self) -> impl Iterator<Item = &V> {
318+
self.dense.iter()
319+
}
320+
321+
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
322+
self.dense.iter_mut()
323+
}
324+
325+
pub fn iter(&self) -> impl Iterator<Item = (&I, &V)> {
326+
self.indices.iter().zip(self.dense.iter())
327+
}
328+
329+
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&I, &mut V)> {
330+
self.indices.iter().zip(self.dense.iter_mut())
331+
}
332+
}
333+
};
334+
}
335+
336+
impl_sparse_set!(SparseSet);
337+
impl_sparse_set!(ImmutableSparseSet);
338+
252339
impl<I: SparseSetIndex, V> Default for SparseSet<I, V> {
253340
fn default() -> Self {
254341
Self::new()
255342
}
256343
}
344+
257345
impl<I, V> SparseSet<I, V> {
258346
pub const fn new() -> Self {
259347
Self {
@@ -306,36 +394,11 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
306394
}
307395
}
308396

309-
#[inline]
310-
pub fn len(&self) -> usize {
311-
self.dense.len()
312-
}
313-
314397
#[inline]
315398
pub fn is_empty(&self) -> bool {
316399
self.dense.len() == 0
317400
}
318401

319-
#[inline]
320-
pub fn contains(&self, index: I) -> bool {
321-
self.sparse.contains(index)
322-
}
323-
324-
pub fn get(&self, index: I) -> Option<&V> {
325-
self.sparse.get(index).map(|dense_index| {
326-
// SAFETY: if the sparse index points to something in the dense vec, it exists
327-
unsafe { self.dense.get_unchecked(*dense_index) }
328-
})
329-
}
330-
331-
pub fn get_mut(&mut self, index: I) -> Option<&mut V> {
332-
let dense = &mut self.dense;
333-
self.sparse.get(index).map(move |dense_index| {
334-
// SAFETY: if the sparse index points to something in the dense vec, it exists
335-
unsafe { dense.get_unchecked_mut(*dense_index) }
336-
})
337-
}
338-
339402
pub fn remove(&mut self, index: I) -> Option<V> {
340403
self.sparse.remove(index).map(|dense_index| {
341404
let is_last = dense_index == self.dense.len() - 1;
@@ -349,24 +412,12 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
349412
})
350413
}
351414

352-
pub fn indices(&self) -> impl Iterator<Item = I> + '_ {
353-
self.indices.iter().cloned()
354-
}
355-
356-
pub fn values(&self) -> impl Iterator<Item = &V> {
357-
self.dense.iter()
358-
}
359-
360-
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
361-
self.dense.iter_mut()
362-
}
363-
364-
pub fn iter(&self) -> impl Iterator<Item = (&I, &V)> {
365-
self.indices.iter().zip(self.dense.iter())
366-
}
367-
368-
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&I, &mut V)> {
369-
self.indices.iter().zip(self.dense.iter_mut())
415+
pub(crate) fn into_immutable(self) -> ImmutableSparseSet<I, V> {
416+
ImmutableSparseSet {
417+
dense: self.dense.into_boxed_slice(),
418+
indices: self.indices.into_boxed_slice(),
419+
sparse: self.sparse.into_immutable(),
420+
}
370421
}
371422
}
372423

crates/bevy_ecs/src/storage/table.rs

+57-24
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
component::{ComponentId, ComponentInfo, ComponentTicks, Components},
33
entity::Entity,
44
query::DebugCheckedUnwrap,
5-
storage::{blob_vec::BlobVec, SparseSet},
5+
storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet},
66
};
77
use bevy_ptr::{OwningPtr, Ptr, PtrMut};
88
use bevy_utils::HashMap;
@@ -262,31 +262,68 @@ impl Column {
262262
}
263263
}
264264

265-
pub struct Table {
265+
/// A builder type for constructing [`Table`]s.
266+
///
267+
/// - Use [`with_capacity`] to initialize the builder.
268+
/// - Repeatedly call [`add_column`] to add columns for components.
269+
/// - Finalize with [`build`] to get the constructed [`Table`].
270+
///
271+
/// [`with_capacity`]: Self::with_capacity
272+
/// [`add_column`]: Self::add_column
273+
/// [`build`]: Self::build
274+
pub(crate) struct TableBuilder {
266275
columns: SparseSet<ComponentId, Column>,
267-
entities: Vec<Entity>,
276+
capacity: usize,
268277
}
269278

270-
impl Table {
271-
pub(crate) fn with_capacity(capacity: usize, column_capacity: usize) -> Table {
279+
impl TableBuilder {
280+
/// Creates a blank [`Table`], allocating space for `column_capacity` columns
281+
/// with the capacity to hold `capacity` entities worth of components each.
282+
pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self {
272283
Self {
273284
columns: SparseSet::with_capacity(column_capacity),
274-
entities: Vec::with_capacity(capacity),
285+
capacity,
275286
}
276287
}
277288

278-
#[inline]
279-
pub fn entities(&self) -> &[Entity] {
280-
&self.entities
281-
}
282-
283-
pub(crate) fn add_column(&mut self, component_info: &ComponentInfo) {
289+
pub fn add_column(&mut self, component_info: &ComponentInfo) {
284290
self.columns.insert(
285291
component_info.id(),
286-
Column::with_capacity(component_info, self.entities.capacity()),
292+
Column::with_capacity(component_info, self.capacity),
287293
);
288294
}
289295

296+
pub fn build(self) -> Table {
297+
Table {
298+
columns: self.columns.into_immutable(),
299+
entities: Vec::with_capacity(self.capacity),
300+
}
301+
}
302+
}
303+
304+
/// A column-oriented [structure-of-arrays] based storage for [`Component`]s of entities
305+
/// in a [`World`].
306+
///
307+
/// Conceptually, a `Table` can be thought of as an `HashMap<ComponentId, Column>`, where
308+
/// each `Column` is a type-erased `Vec<T: Component>`. Each row corresponds to a single entity
309+
/// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same
310+
/// entity). Fetching components from a table involves fetching the associated column for a
311+
/// component type (via it's [`ComponentId`]), then fetching the entity's row within that column.
312+
///
313+
/// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays
314+
/// [`Component`]: crate::component::Component
315+
/// [`World`]: crate::world::World
316+
pub struct Table {
317+
columns: ImmutableSparseSet<ComponentId, Column>,
318+
entities: Vec<Entity>,
319+
}
320+
321+
impl Table {
322+
#[inline]
323+
pub fn entities(&self) -> &[Entity] {
324+
&self.entities
325+
}
326+
290327
/// Removes the entity at the given row and returns the entity swapped in to replace it (if an
291328
/// entity was swapped in)
292329
///
@@ -457,11 +494,6 @@ impl Table {
457494
self.entities.capacity()
458495
}
459496

460-
#[inline]
461-
pub fn component_capacity(&self) -> usize {
462-
self.columns.capacity()
463-
}
464-
465497
#[inline]
466498
pub fn is_empty(&self) -> bool {
467499
self.entities.is_empty()
@@ -495,7 +527,7 @@ pub struct Tables {
495527

496528
impl Default for Tables {
497529
fn default() -> Self {
498-
let empty_table = Table::with_capacity(0, 0);
530+
let empty_table = TableBuilder::with_capacity(0, 0).build();
499531
Tables {
500532
tables: vec![empty_table],
501533
table_ids: HashMap::default(),
@@ -548,11 +580,11 @@ impl Tables {
548580
.raw_entry_mut()
549581
.from_key(component_ids)
550582
.or_insert_with(|| {
551-
let mut table = Table::with_capacity(0, component_ids.len());
583+
let mut table = TableBuilder::with_capacity(0, component_ids.len());
552584
for component_id in component_ids {
553585
table.add_column(components.get_info_unchecked(*component_id));
554586
}
555-
tables.push(table);
587+
tables.push(table.build());
556588
(component_ids.to_vec(), TableId(tables.len() - 1))
557589
});
558590

@@ -601,7 +633,7 @@ mod tests {
601633
use crate::{
602634
component::{ComponentTicks, Components},
603635
entity::Entity,
604-
storage::Table,
636+
storage::TableBuilder,
605637
};
606638
#[derive(Component)]
607639
struct W<T>(T);
@@ -612,8 +644,9 @@ mod tests {
612644
let mut storages = Storages::default();
613645
let component_id = components.init_component::<W<usize>>(&mut storages);
614646
let columns = &[component_id];
615-
let mut table = Table::with_capacity(0, columns.len());
616-
table.add_column(components.get_info(component_id).unwrap());
647+
let mut builder = TableBuilder::with_capacity(0, columns.len());
648+
builder.add_column(components.get_info(component_id).unwrap());
649+
let mut table = builder.build();
617650
let entities = (0..200).map(Entity::from_raw).collect::<Vec<_>>();
618651
for entity in &entities {
619652
// SAFETY: we allocate and immediately set data afterwards

0 commit comments

Comments
 (0)