Skip to content

Commit 61bd3af

Browse files
ElliottjPiercechescockmockersfalice-i-cecile
authored
Remove invalid entity locations (#19433)
# Objective This is the first step of #19430 and is a follow up for #19132. Now that `ArchetypeRow` has a niche, we can use `Option` instead of needing `INVALID` everywhere. This was especially concerning since `INVALID` *really was valid!* Using options here made the code clearer and more data-driven. ## Solution Replace all uses of `INVALID` entity locations (and archetype/table rows) with `None`. ## Testing CI --------- Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> Co-authored-by: François Mockers <francois.mockers@vleue.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent 43b8fbd commit 61bd3af

File tree

8 files changed

+149
-156
lines changed

8 files changed

+149
-156
lines changed

crates/bevy_ecs/src/bundle.rs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,16 +1193,16 @@ impl<'w> BundleInserter<'w> {
11931193
unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
11941194
entities.set(
11951195
swapped_entity.index(),
1196-
EntityLocation {
1196+
Some(EntityLocation {
11971197
archetype_id: swapped_location.archetype_id,
11981198
archetype_row: location.archetype_row,
11991199
table_id: swapped_location.table_id,
12001200
table_row: swapped_location.table_row,
1201-
},
1201+
}),
12021202
);
12031203
}
12041204
let new_location = new_archetype.allocate(entity, result.table_row);
1205-
entities.set(entity.index(), new_location);
1205+
entities.set(entity.index(), Some(new_location));
12061206
let after_effect = bundle_info.write_components(
12071207
table,
12081208
sparse_sets,
@@ -1242,19 +1242,19 @@ impl<'w> BundleInserter<'w> {
12421242
unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
12431243
entities.set(
12441244
swapped_entity.index(),
1245-
EntityLocation {
1245+
Some(EntityLocation {
12461246
archetype_id: swapped_location.archetype_id,
12471247
archetype_row: location.archetype_row,
12481248
table_id: swapped_location.table_id,
12491249
table_row: swapped_location.table_row,
1250-
},
1250+
}),
12511251
);
12521252
}
12531253
// PERF: store "non bundle" components in edge, then just move those to avoid
12541254
// redundant copies
12551255
let move_result = table.move_to_superset_unchecked(result.table_row, new_table);
12561256
let new_location = new_archetype.allocate(entity, move_result.new_row);
1257-
entities.set(entity.index(), new_location);
1257+
entities.set(entity.index(), Some(new_location));
12581258

12591259
// If an entity was moved into this entity's table spot, update its table row.
12601260
if let Some(swapped_entity) = move_result.swapped_entity {
@@ -1264,12 +1264,12 @@ impl<'w> BundleInserter<'w> {
12641264

12651265
entities.set(
12661266
swapped_entity.index(),
1267-
EntityLocation {
1267+
Some(EntityLocation {
12681268
archetype_id: swapped_location.archetype_id,
12691269
archetype_row: swapped_location.archetype_row,
12701270
table_id: swapped_location.table_id,
12711271
table_row: result.table_row,
1272-
},
1272+
}),
12731273
);
12741274

12751275
if archetype.id() == swapped_location.archetype_id {
@@ -1573,12 +1573,12 @@ impl<'w> BundleRemover<'w> {
15731573

15741574
world.entities.set(
15751575
swapped_entity.index(),
1576-
EntityLocation {
1576+
Some(EntityLocation {
15771577
archetype_id: swapped_location.archetype_id,
15781578
archetype_row: location.archetype_row,
15791579
table_id: swapped_location.table_id,
15801580
table_row: swapped_location.table_row,
1581-
},
1581+
}),
15821582
);
15831583
}
15841584

@@ -1614,12 +1614,12 @@ impl<'w> BundleRemover<'w> {
16141614

16151615
world.entities.set(
16161616
swapped_entity.index(),
1617-
EntityLocation {
1617+
Some(EntityLocation {
16181618
archetype_id: swapped_location.archetype_id,
16191619
archetype_row: swapped_location.archetype_row,
16201620
table_id: swapped_location.table_id,
16211621
table_row: location.table_row,
1622-
},
1622+
}),
16231623
);
16241624
world.archetypes[swapped_location.archetype_id]
16251625
.set_entity_table_row(swapped_location.archetype_row, location.table_row);
@@ -1635,7 +1635,7 @@ impl<'w> BundleRemover<'w> {
16351635

16361636
// SAFETY: The entity is valid and has been moved to the new location already.
16371637
unsafe {
1638-
world.entities.set(entity.index(), new_location);
1638+
world.entities.set(entity.index(), Some(new_location));
16391639
}
16401640

16411641
(new_location, pre_remove_result)
@@ -1736,7 +1736,7 @@ impl<'w> BundleSpawner<'w> {
17361736
InsertMode::Replace,
17371737
caller,
17381738
);
1739-
entities.set(entity.index(), location);
1739+
entities.set(entity.index(), Some(location));
17401740
entities.mark_spawn_despawn(entity.index(), caller, self.change_tick);
17411741
(location, after_effect)
17421742
};

crates/bevy_ecs/src/entity/mod.rs

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -886,8 +886,10 @@ impl Entities {
886886

887887
/// Destroy an entity, allowing it to be reused.
888888
///
889+
/// Returns the `Option<EntityLocation>` of the entity or `None` if the `entity` was not present.
890+
///
889891
/// Must not be called while reserved entities are awaiting `flush()`.
890-
pub fn free(&mut self, entity: Entity) -> Option<EntityLocation> {
892+
pub fn free(&mut self, entity: Entity) -> Option<EntityIdLocation> {
891893
self.verify_flushed();
892894

893895
let meta = &mut self.meta[entity.index() as usize];
@@ -949,20 +951,21 @@ impl Entities {
949951
*self.free_cursor.get_mut() = 0;
950952
}
951953

952-
/// Returns the location of an [`Entity`].
953-
/// Note: for pending entities, returns `None`.
954+
/// Returns the [`EntityLocation`] of an [`Entity`].
955+
/// Note: for pending entities and entities not participating in the ECS (entities with a [`EntityIdLocation`] of `None`), returns `None`.
954956
#[inline]
955957
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
956-
if let Some(meta) = self.meta.get(entity.index() as usize) {
957-
if meta.generation != entity.generation
958-
|| meta.location.archetype_id == ArchetypeId::INVALID
959-
{
960-
return None;
961-
}
962-
Some(meta.location)
963-
} else {
964-
None
965-
}
958+
self.get_id_location(entity).flatten()
959+
}
960+
961+
/// Returns the [`EntityIdLocation`] of an [`Entity`].
962+
/// Note: for pending entities, returns `None`.
963+
#[inline]
964+
pub fn get_id_location(&self, entity: Entity) -> Option<EntityIdLocation> {
965+
self.meta
966+
.get(entity.index() as usize)
967+
.filter(|meta| meta.generation == entity.generation)
968+
.map(|meta| meta.location)
966969
}
967970

968971
/// Updates the location of an [`Entity`].
@@ -973,7 +976,7 @@ impl Entities {
973976
/// - `location` must be valid for the entity at `index` or immediately made valid afterwards
974977
/// before handing control to unknown code.
975978
#[inline]
976-
pub(crate) unsafe fn set(&mut self, index: u32, location: EntityLocation) {
979+
pub(crate) unsafe fn set(&mut self, index: u32, location: EntityIdLocation) {
977980
// SAFETY: Caller guarantees that `index` a valid entity index
978981
let meta = unsafe { self.meta.get_unchecked_mut(index as usize) };
979982
meta.location = location;
@@ -1001,7 +1004,7 @@ impl Entities {
10011004
}
10021005

10031006
let meta = &mut self.meta[index as usize];
1004-
if meta.location.archetype_id == ArchetypeId::INVALID {
1007+
if meta.location.is_none() {
10051008
meta.generation = meta.generation.after_versions(generations);
10061009
true
10071010
} else {
@@ -1036,6 +1039,8 @@ impl Entities {
10361039
/// Allocates space for entities previously reserved with [`reserve_entity`](Entities::reserve_entity) or
10371040
/// [`reserve_entities`](Entities::reserve_entities), then initializes each one using the supplied function.
10381041
///
1042+
/// See [`EntityLocation`] for details on its meaning and how to set it.
1043+
///
10391044
/// # Safety
10401045
/// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`]
10411046
/// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`]
@@ -1045,7 +1050,7 @@ impl Entities {
10451050
/// to be initialized with the invalid archetype.
10461051
pub unsafe fn flush(
10471052
&mut self,
1048-
mut init: impl FnMut(Entity, &mut EntityLocation),
1053+
mut init: impl FnMut(Entity, &mut EntityIdLocation),
10491054
by: MaybeLocation,
10501055
at: Tick,
10511056
) {
@@ -1090,7 +1095,7 @@ impl Entities {
10901095
unsafe {
10911096
self.flush(
10921097
|_entity, location| {
1093-
location.archetype_id = ArchetypeId::INVALID;
1098+
*location = None;
10941099
},
10951100
by,
10961101
at,
@@ -1178,7 +1183,7 @@ impl Entities {
11781183
.filter(|meta|
11791184
// Generation is incremented immediately upon despawn
11801185
(meta.generation == entity.generation)
1181-
|| (meta.location.archetype_id == ArchetypeId::INVALID)
1186+
|| meta.location.is_none()
11821187
&& (meta.generation == entity.generation.after_versions(1)))
11831188
.map(|meta| meta.spawned_or_despawned)
11841189
}
@@ -1203,11 +1208,7 @@ impl Entities {
12031208
#[inline]
12041209
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
12051210
for meta in &mut self.meta {
1206-
if meta.generation != EntityGeneration::FIRST
1207-
|| meta.location.archetype_id != ArchetypeId::INVALID
1208-
{
1209-
meta.spawned_or_despawned.at.check_tick(change_tick);
1210-
}
1211+
meta.spawned_or_despawned.at.check_tick(change_tick);
12111212
}
12121213
}
12131214

@@ -1267,9 +1268,9 @@ impl fmt::Display for EntityDoesNotExistDetails {
12671268
#[derive(Copy, Clone, Debug)]
12681269
struct EntityMeta {
12691270
/// The current [`EntityGeneration`] of the [`EntityRow`].
1270-
pub generation: EntityGeneration,
1271+
generation: EntityGeneration,
12711272
/// The current location of the [`EntityRow`].
1272-
pub location: EntityLocation,
1273+
location: EntityIdLocation,
12731274
/// Location and tick of the last spawn, despawn or flush of this entity.
12741275
spawned_or_despawned: SpawnedOrDespawned,
12751276
}
@@ -1284,7 +1285,7 @@ impl EntityMeta {
12841285
/// meta for **pending entity**
12851286
const EMPTY: EntityMeta = EntityMeta {
12861287
generation: EntityGeneration::FIRST,
1287-
location: EntityLocation::INVALID,
1288+
location: None,
12881289
spawned_or_despawned: SpawnedOrDespawned {
12891290
by: MaybeLocation::caller(),
12901291
at: Tick::new(0),
@@ -1316,15 +1317,15 @@ pub struct EntityLocation {
13161317
pub table_row: TableRow,
13171318
}
13181319

1319-
impl EntityLocation {
1320-
/// location for **pending entity** and **invalid entity**
1321-
pub(crate) const INVALID: EntityLocation = EntityLocation {
1322-
archetype_id: ArchetypeId::INVALID,
1323-
archetype_row: ArchetypeRow::INVALID,
1324-
table_id: TableId::INVALID,
1325-
table_row: TableRow::INVALID,
1326-
};
1327-
}
1320+
/// An [`Entity`] id may or may not correspond to a valid conceptual entity.
1321+
/// If it does, the conceptual entity may or may not have a location.
1322+
/// If it has no location, the [`EntityLocation`] will be `None`.
1323+
/// An location of `None` means the entity effectively does not exist; it has an id, but is not participating in the ECS.
1324+
/// This is different from a location in the empty archetype, which is participating (queryable, etc) but just happens to have no components.
1325+
///
1326+
/// Setting a location to `None` is often helpful when you want to destruct an entity or yank it from the ECS without allowing another system to reuse the id for something else.
1327+
/// It is also useful for reserving an id; commands will often allocate an `Entity` but not provide it a location until the command is applied.
1328+
pub type EntityIdLocation = Option<EntityLocation>;
13281329

13291330
#[cfg(test)]
13301331
mod tests {

crates/bevy_ecs/src/storage/table/mod.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ mod column;
3636
pub struct TableId(u32);
3737

3838
impl TableId {
39-
pub(crate) const INVALID: TableId = TableId(u32::MAX);
40-
4139
/// Creates a new [`TableId`].
4240
///
4341
/// `index` *must* be retrieved from calling [`TableId::as_u32`] on a `TableId` you got
@@ -105,9 +103,6 @@ impl TableId {
105103
pub struct TableRow(NonMaxU32);
106104

107105
impl TableRow {
108-
// TODO: Deprecate in favor of options, since `INVALID` is, technically, valid.
109-
pub(crate) const INVALID: TableRow = TableRow(NonMaxU32::MAX);
110-
111106
/// Creates a [`TableRow`].
112107
#[inline]
113108
pub const fn new(index: NonMaxU32) -> Self {

crates/bevy_ecs/src/world/entity_fetch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ unsafe impl WorldEntityFetch for Entity {
220220
// SAFETY: caller ensures that the world cell has mutable access to the entity.
221221
let world = unsafe { cell.world_mut() };
222222
// SAFETY: location was fetched from the same world's `Entities`.
223-
Ok(unsafe { EntityWorldMut::new(world, self, location) })
223+
Ok(unsafe { EntityWorldMut::new(world, self, Some(location)) })
224224
}
225225

226226
unsafe fn fetch_deferred_mut(

0 commit comments

Comments
 (0)