Skip to content

Commit

Permalink
Remove redundant table and sparse set component IDs from Archetype (b…
Browse files Browse the repository at this point in the history
…evyengine#4927)

# Objective
Archetype is a deceptively large type in memory. It stores metadata about which components are in which storage in multiple locations, which is only used when creating new Archetypes while moving entities.

## Solution
Remove the redundant `Box<[ComponentId]>`s and iterate over the sparse set of component metadata instead. Reduces Archetype's size by 4 usizes (32 bytes on 64-bit systems), as well as the additional allocations for holding these slices.

It'd seem like there's a downside that the origin archetype has it's component metadata iterated over twice when creating a new archetype, but this change also removes the extra `Vec<ArchetypeComponentId>` allocations when creating a new archetype which may amortize out to a net gain here. This change likely negatively impacts creating new archetypes with a large number of components, but that's a cost mitigated by the fact that these archetypal relationships are cached in Edges and is incurred only once for each edge created.

## Additional Context
There are several other in-flight PRs that shrink Archetype:

 - bevyengine#4800 merges the entities and rows Vecs together (shaves off 24 bytes per archetype) 
 - bevyengine#4809 removes unique_components and moves it to it's own dedicated storage (shaves off 72 bytes per archetype)

---

## Changelog
Changed: `Archetype::table_components` and `Archetype::sparse_set_components` return iterators instead of slices. `Archetype::new` requires iterators instead of parallel slices/vecs.

## Migration Guide
Do I still need to do this? I really hope people were not relying on the public facing APIs changed here.
  • Loading branch information
james7132 authored and alradish committed Jan 22, 2023
1 parent 875ec5e commit f22be30
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 48 deletions.
77 changes: 34 additions & 43 deletions crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,40 +182,32 @@ pub struct Archetype {
table_id: TableId,
edges: Edges,
entities: Vec<ArchetypeEntity>,
table_components: Box<[ComponentId]>,
sparse_set_components: Box<[ComponentId]>,
components: SparseSet<ComponentId, ArchetypeComponentInfo>,
}

impl Archetype {
pub fn new(
id: ArchetypeId,
table_id: TableId,
table_components: Box<[ComponentId]>,
sparse_set_components: Box<[ComponentId]>,
table_archetype_components: Vec<ArchetypeComponentId>,
sparse_set_archetype_components: Vec<ArchetypeComponentId>,
table_components: impl Iterator<Item = (ComponentId, ArchetypeComponentId)>,
sparse_set_components: impl Iterator<Item = (ComponentId, ArchetypeComponentId)>,
) -> Self {
let mut components =
SparseSet::with_capacity(table_components.len() + sparse_set_components.len());
for (component_id, archetype_component_id) in
table_components.iter().zip(table_archetype_components)
{
let (min_table, _) = table_components.size_hint();
let (min_sparse, _) = sparse_set_components.size_hint();
let mut components = SparseSet::with_capacity(min_table + min_sparse);
for (component_id, archetype_component_id) in table_components {
components.insert(
*component_id,
component_id,
ArchetypeComponentInfo {
storage_type: StorageType::Table,
archetype_component_id,
},
);
}

for (component_id, archetype_component_id) in sparse_set_components
.iter()
.zip(sparse_set_archetype_components)
{
for (component_id, archetype_component_id) in sparse_set_components {
components.insert(
*component_id,
component_id,
ArchetypeComponentInfo {
storage_type: StorageType::SparseSet,
archetype_component_id,
Expand All @@ -225,10 +217,8 @@ impl Archetype {
Self {
id,
table_id,
entities: Vec::new(),
components,
table_components,
sparse_set_components,
entities: Default::default(),
edges: Default::default(),
}
}
Expand All @@ -249,13 +239,19 @@ impl Archetype {
}

#[inline]
pub fn table_components(&self) -> &[ComponentId] {
&self.table_components
pub fn table_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.components
.iter()
.filter(|(_, component)| component.storage_type == StorageType::Table)
.map(|(id, _)| *id)
}

#[inline]
pub fn sparse_set_components(&self) -> &[ComponentId] {
&self.sparse_set_components
pub fn sparse_set_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
self.components
.iter()
.filter(|(_, component)| component.storage_type == StorageType::SparseSet)
.map(|(id, _)| *id)
}

#[inline]
Expand Down Expand Up @@ -484,38 +480,33 @@ impl Archetypes {
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
) -> ArchetypeId {
let table_components = table_components.into_boxed_slice();
let sparse_set_components = sparse_set_components.into_boxed_slice();
let archetype_identity = ArchetypeIdentity {
sparse_set_components: sparse_set_components.clone(),
table_components: table_components.clone(),
sparse_set_components: sparse_set_components.clone().into_boxed_slice(),
table_components: table_components.clone().into_boxed_slice(),
};

let archetypes = &mut self.archetypes;
let archetype_component_count = &mut self.archetype_component_count;
let mut next_archetype_component_id = move || {
let id = ArchetypeComponentId(*archetype_component_count);
*archetype_component_count += 1;
id
};
*self
.archetype_ids
.entry(archetype_identity)
.or_insert_with(move || {
let id = ArchetypeId(archetypes.len());
let table_archetype_components = (0..table_components.len())
.map(|_| next_archetype_component_id())
.collect();
let sparse_set_archetype_components = (0..sparse_set_components.len())
.map(|_| next_archetype_component_id())
.collect();
let table_start = *archetype_component_count;
*archetype_component_count += table_components.len();
let table_archetype_components =
(table_start..*archetype_component_count).map(ArchetypeComponentId);
let sparse_start = *archetype_component_count;
*archetype_component_count += sparse_set_components.len();
let sparse_set_archetype_components =
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
archetypes.push(Archetype::new(
id,
table_id,
table_components,
sparse_set_components,
table_archetype_components,
sparse_set_archetype_components,
table_components.into_iter().zip(table_archetype_components),
sparse_set_components
.into_iter()
.zip(sparse_set_archetype_components),
));
id
})
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ impl BundleInfo {
table_components = if new_table_components.is_empty() {
// if there are no new table components, we can keep using this table
table_id = current_archetype.table_id();
current_archetype.table_components().to_vec()
current_archetype.table_components().collect()
} else {
new_table_components.extend(current_archetype.table_components());
// sort to ignore order while hashing
Expand All @@ -477,7 +477,7 @@ impl BundleInfo {
};

sparse_set_components = if new_sparse_set_components.is_empty() {
current_archetype.sparse_set_components().to_vec()
current_archetype.sparse_set_components().collect()
} else {
new_sparse_set_components.extend(current_archetype.sparse_set_components());
// sort to ignore order while hashing
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ecs/src/world/entity_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ impl<'w> EntityMut<'w> {
table_row = remove_result.table_row;

for component_id in archetype.sparse_set_components() {
let sparse_set = world.storages.sparse_sets.get_mut(*component_id).unwrap();
let sparse_set = world.storages.sparse_sets.get_mut(component_id).unwrap();
sparse_set.remove(self.entity);
}
// SAFETY: table rows stored in archetypes always exist
Expand Down Expand Up @@ -843,8 +843,8 @@ unsafe fn remove_bundle_from_archetype(
// components are already sorted
removed_table_components.sort();
removed_sparse_set_components.sort();
next_table_components = current_archetype.table_components().to_vec();
next_sparse_set_components = current_archetype.sparse_set_components().to_vec();
next_table_components = current_archetype.table_components().collect();
next_sparse_set_components = current_archetype.sparse_set_components().collect();
sorted_remove(&mut next_table_components, &removed_table_components);
sorted_remove(
&mut next_sparse_set_components,
Expand Down

0 comments on commit f22be30

Please sign in to comment.