Skip to content

Commit a5b1c46

Browse files
committed
Extend EntityLocation with TableId and TableRow (#6681)
# Objective `Query::get` and other random access methods require looking up `EntityLocation` for every provided entity, then always looking up the `Archetype` to get the table ID and table row. This requires 4 total random fetches from memory: the `Entities` lookup, the `Archetype` lookup, the table row lookup, and the final fetch from table/sparse sets. If `EntityLocation` contains the table ID and table row, only the `Entities` lookup and the final storage fetch are required. ## Solution Add `TableId` and table row to `EntityLocation`. Ensure it's updated whenever entities are moved around. To ensure `EntityMeta` does not grow bigger, both `TableId` and `ArchetypeId` have been shrunk to u32, and the archetype index and table row are stored as u32s instead of as usizes. This should shrink `EntityMeta` by 4 bytes, from 24 to 20 bytes, as there is no padding anymore due to the change in alignment. This idea was partially concocted by @BoxyUwU. ## Performance This should restore the `Query::get` "gains" lost to #6625 that were introduced in #4800 without being unsound, and also incorporates some of the memory usage reductions seen in #3678. This also removes the same lookups during add/remove/spawn commands, so there may be a bit of a speedup in commands and `Entity{Ref,Mut}`. --- ## Changelog Added: `EntityLocation::table_id` Added: `EntityLocation::table_row`. Changed: `World`s can now only hold a maximum of 2<sup>32</sup>- 1 archetypes. Changed: `World`s can now only hold a maximum of 2<sup>32</sup> - 1 tables. ## Migration Guide A `World` can only hold a maximum of 2<sup>32</sup> - 1 archetypes and tables now. If your use case requires more than this, please file an issue explaining your use case.
1 parent f8a229b commit a5b1c46

File tree

8 files changed

+114
-76
lines changed

8 files changed

+114
-76
lines changed

crates/bevy_ecs/src/archetype.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,20 @@ use std::{
4141
/// [`Entities::get`]: crate::entity::Entities
4242
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
4343
#[repr(transparent)]
44-
pub struct ArchetypeRow(usize);
44+
pub struct ArchetypeRow(u32);
4545

4646
impl ArchetypeRow {
47-
pub const INVALID: ArchetypeRow = ArchetypeRow(usize::MAX);
47+
pub const INVALID: ArchetypeRow = ArchetypeRow(u32::MAX);
4848

4949
/// Creates a `ArchetypeRow`.
5050
pub const fn new(index: usize) -> Self {
51-
Self(index)
51+
Self(index as u32)
5252
}
5353

5454
/// Gets the index of the row.
5555
#[inline]
5656
pub const fn index(self) -> usize {
57-
self.0
57+
self.0 as usize
5858
}
5959
}
6060

@@ -69,24 +69,24 @@ impl ArchetypeRow {
6969
/// [`EMPTY`]: crate::archetype::ArchetypeId::EMPTY
7070
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
7171
#[repr(transparent)]
72-
pub struct ArchetypeId(usize);
72+
pub struct ArchetypeId(u32);
7373

7474
impl ArchetypeId {
7575
/// The ID for the [`Archetype`] without any components.
7676
pub const EMPTY: ArchetypeId = ArchetypeId(0);
7777
/// # Safety:
7878
///
7979
/// This must always have an all-1s bit pattern to ensure soundness in fast entity id space allocation.
80-
pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX);
80+
pub const INVALID: ArchetypeId = ArchetypeId(u32::MAX);
8181

8282
#[inline]
8383
pub(crate) const fn new(index: usize) -> Self {
84-
ArchetypeId(index)
84+
ArchetypeId(index as u32)
8585
}
8686

8787
#[inline]
8888
pub(crate) fn index(self) -> usize {
89-
self.0
89+
self.0 as usize
9090
}
9191
}
9292

@@ -407,18 +407,18 @@ impl Archetype {
407407
/// Fetches the row in the [`Table`] where the components for the entity at `index`
408408
/// is stored.
409409
///
410-
/// An entity's archetype index can be fetched from [`EntityLocation::archetype_row`], which
410+
/// An entity's archetype row can be fetched from [`EntityLocation::archetype_row`], which
411411
/// can be retrieved from [`Entities::get`].
412412
///
413413
/// # Panics
414414
/// This function will panic if `index >= self.len()`.
415415
///
416416
/// [`Table`]: crate::storage::Table
417-
/// [`EntityLocation`]: crate::entity::EntityLocation::archetype_row
417+
/// [`EntityLocation::archetype_row`]: crate::entity::EntityLocation::archetype_row
418418
/// [`Entities::get`]: crate::entity::Entities::get
419419
#[inline]
420-
pub fn entity_table_row(&self, index: ArchetypeRow) -> TableRow {
421-
self.entities[index.0].table_row
420+
pub fn entity_table_row(&self, row: ArchetypeRow) -> TableRow {
421+
self.entities[row.index()].table_row
422422
}
423423

424424
/// Updates if the components for the entity at `index` can be found
@@ -427,8 +427,8 @@ impl Archetype {
427427
/// # Panics
428428
/// This function will panic if `index >= self.len()`.
429429
#[inline]
430-
pub(crate) fn set_entity_table_row(&mut self, index: ArchetypeRow, table_row: TableRow) {
431-
self.entities[index.0].table_row = table_row;
430+
pub(crate) fn set_entity_table_row(&mut self, row: ArchetypeRow, table_row: TableRow) {
431+
self.entities[row.index()].table_row = table_row;
432432
}
433433

434434
/// Allocates an entity to the archetype.
@@ -441,11 +441,14 @@ impl Archetype {
441441
entity: Entity,
442442
table_row: TableRow,
443443
) -> EntityLocation {
444+
let archetype_row = ArchetypeRow::new(self.entities.len());
444445
self.entities.push(ArchetypeEntity { entity, table_row });
445446

446447
EntityLocation {
447448
archetype_id: self.id,
448-
archetype_row: ArchetypeRow(self.entities.len() - 1),
449+
archetype_row,
450+
table_id: self.table_id,
451+
table_row,
449452
}
450453
}
451454

@@ -458,14 +461,14 @@ impl Archetype {
458461
///
459462
/// # Panics
460463
/// This function will panic if `index >= self.len()`
461-
pub(crate) fn swap_remove(&mut self, index: ArchetypeRow) -> ArchetypeSwapRemoveResult {
462-
let is_last = index.0 == self.entities.len() - 1;
463-
let entity = self.entities.swap_remove(index.0);
464+
pub(crate) fn swap_remove(&mut self, row: ArchetypeRow) -> ArchetypeSwapRemoveResult {
465+
let is_last = row.index() == self.entities.len() - 1;
466+
let entity = self.entities.swap_remove(row.index());
464467
ArchetypeSwapRemoveResult {
465468
swapped_entity: if is_last {
466469
None
467470
} else {
468-
Some(self.entities[index.0].entity)
471+
Some(self.entities[row.index()].entity)
469472
},
470473
table_row: entity.table_row,
471474
}
@@ -691,7 +694,7 @@ impl Archetypes {
691694
.archetype_ids
692695
.entry(archetype_identity)
693696
.or_insert_with(move || {
694-
let id = ArchetypeId(archetypes.len());
697+
let id = ArchetypeId::new(archetypes.len());
695698
let table_start = *archetype_component_count;
696699
*archetype_component_count += table_components.len();
697700
let table_archetype_components =

crates/bevy_ecs/src/bundle.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use bevy_ecs_macros::Bundle;
66

77
use crate::{
88
archetype::{
9-
Archetype, ArchetypeId, ArchetypeRow, Archetypes, BundleComponentStatus, ComponentStatus,
9+
Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus,
1010
SpawnBundleStatus,
1111
},
1212
component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick},
@@ -528,13 +528,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
528528
pub unsafe fn insert<T: Bundle>(
529529
&mut self,
530530
entity: Entity,
531-
archetype_row: ArchetypeRow,
531+
location: EntityLocation,
532532
bundle: T,
533533
) -> EntityLocation {
534-
let location = EntityLocation {
535-
archetype_row,
536-
archetype_id: self.archetype.id(),
537-
};
538534
match &mut self.result {
539535
InsertBundleResult::SameArchetype => {
540536
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
@@ -548,7 +544,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
548544
self.sparse_sets,
549545
add_bundle,
550546
entity,
551-
self.archetype.entity_table_row(archetype_row),
547+
location.table_row,
552548
self.change_tick,
553549
bundle,
554550
);

crates/bevy_ecs/src/entity/mod.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ pub use map_entities::*;
3636

3737
use crate::{
3838
archetype::{ArchetypeId, ArchetypeRow},
39-
storage::SparseSetIndex,
39+
storage::{SparseSetIndex, TableId, TableRow},
4040
};
4141
use serde::{Deserialize, Serialize};
4242
use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering};
@@ -716,12 +716,17 @@ impl Entities {
716716
}
717717
}
718718

719+
// This type is repr(C) to ensure that the layout and values within it can be safe to fully fill
720+
// with u8::MAX, as required by [`Entities::flush_and_reserve_invalid_assuming_no_entities`].
719721
// Safety:
720722
// This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX.
723+
/// Metadata for an [`Entity`].
721724
#[derive(Copy, Clone, Debug)]
722725
#[repr(C)]
723726
struct EntityMeta {
727+
/// The current generation of the [`Entity`].
724728
pub generation: u32,
729+
/// The current location of the [`Entity`]
725730
pub location: EntityLocation,
726731
}
727732

@@ -731,19 +736,39 @@ impl EntityMeta {
731736
location: EntityLocation {
732737
archetype_id: ArchetypeId::INVALID,
733738
archetype_row: ArchetypeRow::INVALID, // dummy value, to be filled in
739+
table_id: TableId::INVALID,
740+
table_row: TableRow::INVALID, // dummy value, to be filled in
734741
},
735742
};
736743
}
737744

745+
// This type is repr(C) to ensure that the layout and values within it can be safe to fully fill
746+
// with u8::MAX, as required by [`Entities::flush_and_reserve_invalid_assuming_no_entities`].
747+
// SAFETY:
748+
// This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX.
738749
/// A location of an entity in an archetype.
739750
#[derive(Copy, Clone, Debug)]
740751
#[repr(C)]
741752
pub struct EntityLocation {
742-
/// The archetype index
753+
/// The ID of the [`Archetype`] the [`Entity`] belongs to.
754+
///
755+
/// [`Archetype`]: crate::archetype::Archetype
743756
pub archetype_id: ArchetypeId,
744757

745-
/// The index of the entity in the archetype
758+
/// The index of the [`Entity`] within its [`Archetype`].
759+
///
760+
/// [`Archetype`]: crate::archetype::Archetype
746761
pub archetype_row: ArchetypeRow,
762+
763+
/// The ID of the [`Table`] the [`Entity`] belongs to.
764+
///
765+
/// [`Table`]: crate::storage::Table
766+
pub table_id: TableId,
767+
768+
/// The index of the [`Entity`] within its [`Table`].
769+
///
770+
/// [`Table`]: crate::storage::Table
771+
pub table_row: TableRow,
747772
}
748773

749774
#[cfg(test)]

crates/bevy_ecs/src/query/iter.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ where
151151
.archetypes
152152
.get(location.archetype_id)
153153
.debug_checked_unwrap();
154-
let table = self.tables.get(archetype.table_id()).debug_checked_unwrap();
154+
let table = self.tables.get(location.table_id).debug_checked_unwrap();
155155

156156
// SAFETY: `archetype` is from the world that `fetch/filter` were created for,
157157
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
@@ -170,12 +170,11 @@ where
170170
table,
171171
);
172172

173-
let table_row = archetype.entity_table_row(location.archetype_row);
174173
// SAFETY: set_archetype was called prior.
175174
// `location.archetype_row` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d
176-
if F::filter_fetch(&mut self.filter, entity, table_row) {
175+
if F::filter_fetch(&mut self.filter, entity, location.table_row) {
177176
// SAFETY: set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype
178-
return Some(Q::fetch(&mut self.fetch, entity, table_row));
177+
return Some(Q::fetch(&mut self.fetch, entity, location.table_row));
179178
}
180179
}
181180
None

crates/bevy_ecs/src/query/state.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,17 +408,16 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
408408
let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick);
409409
let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick);
410410

411-
let table_row = archetype.entity_table_row(location.archetype_row);
412411
let table = world
413412
.storages()
414413
.tables
415-
.get(archetype.table_id())
414+
.get(location.table_id)
416415
.debug_checked_unwrap();
417416
Q::set_archetype(&mut fetch, &self.fetch_state, archetype, table);
418417
F::set_archetype(&mut filter, &self.filter_state, archetype, table);
419418

420-
if F::filter_fetch(&mut filter, entity, table_row) {
421-
Ok(Q::fetch(&mut fetch, entity, table_row))
419+
if F::filter_fetch(&mut filter, entity, location.table_row) {
420+
Ok(Q::fetch(&mut fetch, entity, location.table_row))
422421
} else {
423422
Err(QueryEntityError::QueryDoesNotMatch(entity))
424423
}

crates/bevy_ecs/src/storage/table.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,33 @@ use std::{
1212
ops::{Index, IndexMut},
1313
};
1414

15+
/// An opaque unique ID for a [`Table`] within a [`World`].
16+
///
17+
/// Can be used with [`Tables::get`] to fetch the corresponding
18+
/// table.
19+
///
20+
/// Each [`Archetype`] always points to a table via [`Archetype::table_id`].
21+
/// Multiple archetypes can point to the same table so long as the components
22+
/// stored in the table are identical, but do not share the same sparse set
23+
/// components.
24+
///
25+
/// [`World`]: crate::world::World
26+
/// [`Archetype`]: crate::archetype::Archetype
27+
/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id
1528
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16-
pub struct TableId(usize);
29+
pub struct TableId(u32);
1730

1831
impl TableId {
32+
pub(crate) const INVALID: TableId = TableId(u32::MAX);
33+
1934
#[inline]
2035
pub fn new(index: usize) -> Self {
21-
TableId(index)
36+
TableId(index as u32)
2237
}
2338

2439
#[inline]
2540
pub fn index(self) -> usize {
26-
self.0
41+
self.0 as usize
2742
}
2843

2944
#[inline]
@@ -49,19 +64,21 @@ impl TableId {
4964
/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id
5065
/// [`Entity`]: crate::entity::Entity
5166
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52-
pub struct TableRow(usize);
67+
pub struct TableRow(u32);
5368

5469
impl TableRow {
70+
pub const INVALID: TableRow = TableRow(u32::MAX);
71+
5572
/// Creates a `TableRow`.
5673
#[inline]
5774
pub const fn new(index: usize) -> Self {
58-
Self(index)
75+
Self(index as u32)
5976
}
6077

6178
/// Gets the index of the row.
6279
#[inline]
6380
pub const fn index(self) -> usize {
64-
self.0
81+
self.0 as usize
6582
}
6683
}
6784

@@ -568,7 +585,7 @@ impl Table {
568585
column.added_ticks.push(UnsafeCell::new(Tick::new(0)));
569586
column.changed_ticks.push(UnsafeCell::new(Tick::new(0)));
570587
}
571-
TableRow(index)
588+
TableRow::new(index)
572589
}
573590

574591
#[inline]
@@ -677,7 +694,7 @@ impl Tables {
677694
table.add_column(components.get_info_unchecked(*component_id));
678695
}
679696
tables.push(table.build());
680-
(component_ids.to_vec(), TableId(tables.len() - 1))
697+
(component_ids.to_vec(), TableId::new(tables.len() - 1))
681698
});
682699

683700
*value

0 commit comments

Comments
 (0)