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>
This commit is contained in:
Eagster 2025-05-31 12:34:33 -04:00 committed by GitHub
parent 43b8fbda93
commit 61bd3af7c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 149 additions and 156 deletions

View File

@ -1193,16 +1193,16 @@ impl<'w> BundleInserter<'w> {
unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
entities.set( entities.set(
swapped_entity.index(), swapped_entity.index(),
EntityLocation { Some(EntityLocation {
archetype_id: swapped_location.archetype_id, archetype_id: swapped_location.archetype_id,
archetype_row: location.archetype_row, archetype_row: location.archetype_row,
table_id: swapped_location.table_id, table_id: swapped_location.table_id,
table_row: swapped_location.table_row, table_row: swapped_location.table_row,
}, }),
); );
} }
let new_location = new_archetype.allocate(entity, result.table_row); let new_location = new_archetype.allocate(entity, result.table_row);
entities.set(entity.index(), new_location); entities.set(entity.index(), Some(new_location));
let after_effect = bundle_info.write_components( let after_effect = bundle_info.write_components(
table, table,
sparse_sets, sparse_sets,
@ -1242,19 +1242,19 @@ impl<'w> BundleInserter<'w> {
unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
entities.set( entities.set(
swapped_entity.index(), swapped_entity.index(),
EntityLocation { Some(EntityLocation {
archetype_id: swapped_location.archetype_id, archetype_id: swapped_location.archetype_id,
archetype_row: location.archetype_row, archetype_row: location.archetype_row,
table_id: swapped_location.table_id, table_id: swapped_location.table_id,
table_row: swapped_location.table_row, table_row: swapped_location.table_row,
}, }),
); );
} }
// PERF: store "non bundle" components in edge, then just move those to avoid // PERF: store "non bundle" components in edge, then just move those to avoid
// redundant copies // redundant copies
let move_result = table.move_to_superset_unchecked(result.table_row, new_table); let move_result = table.move_to_superset_unchecked(result.table_row, new_table);
let new_location = new_archetype.allocate(entity, move_result.new_row); let new_location = new_archetype.allocate(entity, move_result.new_row);
entities.set(entity.index(), new_location); entities.set(entity.index(), Some(new_location));
// If an entity was moved into this entity's table spot, update its table row. // If an entity was moved into this entity's table spot, update its table row.
if let Some(swapped_entity) = move_result.swapped_entity { if let Some(swapped_entity) = move_result.swapped_entity {
@ -1264,12 +1264,12 @@ impl<'w> BundleInserter<'w> {
entities.set( entities.set(
swapped_entity.index(), swapped_entity.index(),
EntityLocation { Some(EntityLocation {
archetype_id: swapped_location.archetype_id, archetype_id: swapped_location.archetype_id,
archetype_row: swapped_location.archetype_row, archetype_row: swapped_location.archetype_row,
table_id: swapped_location.table_id, table_id: swapped_location.table_id,
table_row: result.table_row, table_row: result.table_row,
}, }),
); );
if archetype.id() == swapped_location.archetype_id { if archetype.id() == swapped_location.archetype_id {
@ -1573,12 +1573,12 @@ impl<'w> BundleRemover<'w> {
world.entities.set( world.entities.set(
swapped_entity.index(), swapped_entity.index(),
EntityLocation { Some(EntityLocation {
archetype_id: swapped_location.archetype_id, archetype_id: swapped_location.archetype_id,
archetype_row: location.archetype_row, archetype_row: location.archetype_row,
table_id: swapped_location.table_id, table_id: swapped_location.table_id,
table_row: swapped_location.table_row, table_row: swapped_location.table_row,
}, }),
); );
} }
@ -1614,12 +1614,12 @@ impl<'w> BundleRemover<'w> {
world.entities.set( world.entities.set(
swapped_entity.index(), swapped_entity.index(),
EntityLocation { Some(EntityLocation {
archetype_id: swapped_location.archetype_id, archetype_id: swapped_location.archetype_id,
archetype_row: swapped_location.archetype_row, archetype_row: swapped_location.archetype_row,
table_id: swapped_location.table_id, table_id: swapped_location.table_id,
table_row: location.table_row, table_row: location.table_row,
}, }),
); );
world.archetypes[swapped_location.archetype_id] world.archetypes[swapped_location.archetype_id]
.set_entity_table_row(swapped_location.archetype_row, location.table_row); .set_entity_table_row(swapped_location.archetype_row, location.table_row);
@ -1635,7 +1635,7 @@ impl<'w> BundleRemover<'w> {
// SAFETY: The entity is valid and has been moved to the new location already. // SAFETY: The entity is valid and has been moved to the new location already.
unsafe { unsafe {
world.entities.set(entity.index(), new_location); world.entities.set(entity.index(), Some(new_location));
} }
(new_location, pre_remove_result) (new_location, pre_remove_result)
@ -1736,7 +1736,7 @@ impl<'w> BundleSpawner<'w> {
InsertMode::Replace, InsertMode::Replace,
caller, caller,
); );
entities.set(entity.index(), location); entities.set(entity.index(), Some(location));
entities.mark_spawn_despawn(entity.index(), caller, self.change_tick); entities.mark_spawn_despawn(entity.index(), caller, self.change_tick);
(location, after_effect) (location, after_effect)
}; };

View File

@ -886,8 +886,10 @@ impl Entities {
/// Destroy an entity, allowing it to be reused. /// Destroy an entity, allowing it to be reused.
/// ///
/// Returns the `Option<EntityLocation>` of the entity or `None` if the `entity` was not present.
///
/// Must not be called while reserved entities are awaiting `flush()`. /// Must not be called while reserved entities are awaiting `flush()`.
pub fn free(&mut self, entity: Entity) -> Option<EntityLocation> { pub fn free(&mut self, entity: Entity) -> Option<EntityIdLocation> {
self.verify_flushed(); self.verify_flushed();
let meta = &mut self.meta[entity.index() as usize]; let meta = &mut self.meta[entity.index() as usize];
@ -949,20 +951,21 @@ impl Entities {
*self.free_cursor.get_mut() = 0; *self.free_cursor.get_mut() = 0;
} }
/// Returns the location of an [`Entity`]. /// Returns the [`EntityLocation`] of an [`Entity`].
/// Note: for pending entities, returns `None`. /// Note: for pending entities and entities not participating in the ECS (entities with a [`EntityIdLocation`] of `None`), returns `None`.
#[inline] #[inline]
pub fn get(&self, entity: Entity) -> Option<EntityLocation> { pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
if let Some(meta) = self.meta.get(entity.index() as usize) { self.get_id_location(entity).flatten()
if meta.generation != entity.generation }
|| meta.location.archetype_id == ArchetypeId::INVALID
{ /// Returns the [`EntityIdLocation`] of an [`Entity`].
return None; /// Note: for pending entities, returns `None`.
} #[inline]
Some(meta.location) pub fn get_id_location(&self, entity: Entity) -> Option<EntityIdLocation> {
} else { self.meta
None .get(entity.index() as usize)
} .filter(|meta| meta.generation == entity.generation)
.map(|meta| meta.location)
} }
/// Updates the location of an [`Entity`]. /// Updates the location of an [`Entity`].
@ -973,7 +976,7 @@ impl Entities {
/// - `location` must be valid for the entity at `index` or immediately made valid afterwards /// - `location` must be valid for the entity at `index` or immediately made valid afterwards
/// before handing control to unknown code. /// before handing control to unknown code.
#[inline] #[inline]
pub(crate) unsafe fn set(&mut self, index: u32, location: EntityLocation) { pub(crate) unsafe fn set(&mut self, index: u32, location: EntityIdLocation) {
// SAFETY: Caller guarantees that `index` a valid entity index // SAFETY: Caller guarantees that `index` a valid entity index
let meta = unsafe { self.meta.get_unchecked_mut(index as usize) }; let meta = unsafe { self.meta.get_unchecked_mut(index as usize) };
meta.location = location; meta.location = location;
@ -1001,7 +1004,7 @@ impl Entities {
} }
let meta = &mut self.meta[index as usize]; let meta = &mut self.meta[index as usize];
if meta.location.archetype_id == ArchetypeId::INVALID { if meta.location.is_none() {
meta.generation = meta.generation.after_versions(generations); meta.generation = meta.generation.after_versions(generations);
true true
} else { } else {
@ -1036,6 +1039,8 @@ impl Entities {
/// Allocates space for entities previously reserved with [`reserve_entity`](Entities::reserve_entity) or /// Allocates space for entities previously reserved with [`reserve_entity`](Entities::reserve_entity) or
/// [`reserve_entities`](Entities::reserve_entities), then initializes each one using the supplied function. /// [`reserve_entities`](Entities::reserve_entities), then initializes each one using the supplied function.
/// ///
/// See [`EntityLocation`] for details on its meaning and how to set it.
///
/// # Safety /// # Safety
/// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`] /// Flush _must_ set the entity location to the correct [`ArchetypeId`] for the given [`Entity`]
/// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`] /// each time init is called. This _can_ be [`ArchetypeId::INVALID`], provided the [`Entity`]
@ -1045,7 +1050,7 @@ impl Entities {
/// to be initialized with the invalid archetype. /// to be initialized with the invalid archetype.
pub unsafe fn flush( pub unsafe fn flush(
&mut self, &mut self,
mut init: impl FnMut(Entity, &mut EntityLocation), mut init: impl FnMut(Entity, &mut EntityIdLocation),
by: MaybeLocation, by: MaybeLocation,
at: Tick, at: Tick,
) { ) {
@ -1090,7 +1095,7 @@ impl Entities {
unsafe { unsafe {
self.flush( self.flush(
|_entity, location| { |_entity, location| {
location.archetype_id = ArchetypeId::INVALID; *location = None;
}, },
by, by,
at, at,
@ -1178,7 +1183,7 @@ impl Entities {
.filter(|meta| .filter(|meta|
// Generation is incremented immediately upon despawn // Generation is incremented immediately upon despawn
(meta.generation == entity.generation) (meta.generation == entity.generation)
|| (meta.location.archetype_id == ArchetypeId::INVALID) || meta.location.is_none()
&& (meta.generation == entity.generation.after_versions(1))) && (meta.generation == entity.generation.after_versions(1)))
.map(|meta| meta.spawned_or_despawned) .map(|meta| meta.spawned_or_despawned)
} }
@ -1203,11 +1208,7 @@ impl Entities {
#[inline] #[inline]
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) { pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
for meta in &mut self.meta { for meta in &mut self.meta {
if meta.generation != EntityGeneration::FIRST meta.spawned_or_despawned.at.check_tick(change_tick);
|| meta.location.archetype_id != ArchetypeId::INVALID
{
meta.spawned_or_despawned.at.check_tick(change_tick);
}
} }
} }
@ -1267,9 +1268,9 @@ impl fmt::Display for EntityDoesNotExistDetails {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
struct EntityMeta { struct EntityMeta {
/// The current [`EntityGeneration`] of the [`EntityRow`]. /// The current [`EntityGeneration`] of the [`EntityRow`].
pub generation: EntityGeneration, generation: EntityGeneration,
/// The current location of the [`EntityRow`]. /// The current location of the [`EntityRow`].
pub location: EntityLocation, location: EntityIdLocation,
/// Location and tick of the last spawn, despawn or flush of this entity. /// Location and tick of the last spawn, despawn or flush of this entity.
spawned_or_despawned: SpawnedOrDespawned, spawned_or_despawned: SpawnedOrDespawned,
} }
@ -1284,7 +1285,7 @@ impl EntityMeta {
/// meta for **pending entity** /// meta for **pending entity**
const EMPTY: EntityMeta = EntityMeta { const EMPTY: EntityMeta = EntityMeta {
generation: EntityGeneration::FIRST, generation: EntityGeneration::FIRST,
location: EntityLocation::INVALID, location: None,
spawned_or_despawned: SpawnedOrDespawned { spawned_or_despawned: SpawnedOrDespawned {
by: MaybeLocation::caller(), by: MaybeLocation::caller(),
at: Tick::new(0), at: Tick::new(0),
@ -1316,15 +1317,15 @@ pub struct EntityLocation {
pub table_row: TableRow, pub table_row: TableRow,
} }
impl EntityLocation { /// An [`Entity`] id may or may not correspond to a valid conceptual entity.
/// location for **pending entity** and **invalid entity** /// If it does, the conceptual entity may or may not have a location.
pub(crate) const INVALID: EntityLocation = EntityLocation { /// If it has no location, the [`EntityLocation`] will be `None`.
archetype_id: ArchetypeId::INVALID, /// An location of `None` means the entity effectively does not exist; it has an id, but is not participating in the ECS.
archetype_row: ArchetypeRow::INVALID, /// This is different from a location in the empty archetype, which is participating (queryable, etc) but just happens to have no components.
table_id: TableId::INVALID, ///
table_row: TableRow::INVALID, /// 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.
}; /// 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.
} pub type EntityIdLocation = Option<EntityLocation>;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -36,8 +36,6 @@ mod column;
pub struct TableId(u32); pub struct TableId(u32);
impl TableId { impl TableId {
pub(crate) const INVALID: TableId = TableId(u32::MAX);
/// Creates a new [`TableId`]. /// Creates a new [`TableId`].
/// ///
/// `index` *must* be retrieved from calling [`TableId::as_u32`] on a `TableId` you got /// `index` *must* be retrieved from calling [`TableId::as_u32`] on a `TableId` you got
@ -105,9 +103,6 @@ impl TableId {
pub struct TableRow(NonMaxU32); pub struct TableRow(NonMaxU32);
impl TableRow { impl TableRow {
// TODO: Deprecate in favor of options, since `INVALID` is, technically, valid.
pub(crate) const INVALID: TableRow = TableRow(NonMaxU32::MAX);
/// Creates a [`TableRow`]. /// Creates a [`TableRow`].
#[inline] #[inline]
pub const fn new(index: NonMaxU32) -> Self { pub const fn new(index: NonMaxU32) -> Self {

View File

@ -220,7 +220,7 @@ unsafe impl WorldEntityFetch for Entity {
// SAFETY: caller ensures that the world cell has mutable access to the entity. // SAFETY: caller ensures that the world cell has mutable access to the entity.
let world = unsafe { cell.world_mut() }; let world = unsafe { cell.world_mut() };
// SAFETY: location was fetched from the same world's `Entities`. // SAFETY: location was fetched from the same world's `Entities`.
Ok(unsafe { EntityWorldMut::new(world, self, location) }) Ok(unsafe { EntityWorldMut::new(world, self, Some(location)) })
} }
unsafe fn fetch_deferred_mut( unsafe fn fetch_deferred_mut(

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
archetype::{Archetype, ArchetypeId}, archetype::Archetype,
bundle::{ bundle::{
Bundle, BundleEffect, BundleFromComponents, BundleInserter, BundleRemover, DynamicBundle, Bundle, BundleEffect, BundleFromComponents, BundleInserter, BundleRemover, DynamicBundle,
InsertMode, InsertMode,
@ -10,7 +10,8 @@ use crate::{
StorageType, Tick, StorageType, Tick,
}, },
entity::{ entity::{
ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent, EntityLocation, ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent,
EntityIdLocation, EntityLocation,
}, },
event::Event, event::Event,
observer::Observer, observer::Observer,
@ -1096,7 +1097,7 @@ unsafe impl EntityEquivalent for EntityMut<'_> {}
pub struct EntityWorldMut<'w> { pub struct EntityWorldMut<'w> {
world: &'w mut World, world: &'w mut World,
entity: Entity, entity: Entity,
location: EntityLocation, location: EntityIdLocation,
} }
impl<'w> EntityWorldMut<'w> { impl<'w> EntityWorldMut<'w> {
@ -1116,43 +1117,43 @@ impl<'w> EntityWorldMut<'w> {
#[inline(always)] #[inline(always)]
#[track_caller] #[track_caller]
pub(crate) fn assert_not_despawned(&self) { pub(crate) fn assert_not_despawned(&self) {
if self.location.archetype_id == ArchetypeId::INVALID { if self.location.is_none() {
self.panic_despawned(); self.panic_despawned()
} }
} }
fn as_unsafe_entity_cell_readonly(&self) -> UnsafeEntityCell<'_> { fn as_unsafe_entity_cell_readonly(&self) -> UnsafeEntityCell<'_> {
self.assert_not_despawned(); let location = self.location();
let last_change_tick = self.world.last_change_tick; let last_change_tick = self.world.last_change_tick;
let change_tick = self.world.read_change_tick(); let change_tick = self.world.read_change_tick();
UnsafeEntityCell::new( UnsafeEntityCell::new(
self.world.as_unsafe_world_cell_readonly(), self.world.as_unsafe_world_cell_readonly(),
self.entity, self.entity,
self.location, location,
last_change_tick, last_change_tick,
change_tick, change_tick,
) )
} }
fn as_unsafe_entity_cell(&mut self) -> UnsafeEntityCell<'_> { fn as_unsafe_entity_cell(&mut self) -> UnsafeEntityCell<'_> {
self.assert_not_despawned(); let location = self.location();
let last_change_tick = self.world.last_change_tick; let last_change_tick = self.world.last_change_tick;
let change_tick = self.world.change_tick(); let change_tick = self.world.change_tick();
UnsafeEntityCell::new( UnsafeEntityCell::new(
self.world.as_unsafe_world_cell(), self.world.as_unsafe_world_cell(),
self.entity, self.entity,
self.location, location,
last_change_tick, last_change_tick,
change_tick, change_tick,
) )
} }
fn into_unsafe_entity_cell(self) -> UnsafeEntityCell<'w> { fn into_unsafe_entity_cell(self) -> UnsafeEntityCell<'w> {
self.assert_not_despawned(); let location = self.location();
let last_change_tick = self.world.last_change_tick; let last_change_tick = self.world.last_change_tick;
let change_tick = self.world.change_tick(); let change_tick = self.world.change_tick();
UnsafeEntityCell::new( UnsafeEntityCell::new(
self.world.as_unsafe_world_cell(), self.world.as_unsafe_world_cell(),
self.entity, self.entity,
self.location, location,
last_change_tick, last_change_tick,
change_tick, change_tick,
) )
@ -1168,10 +1169,10 @@ impl<'w> EntityWorldMut<'w> {
pub(crate) unsafe fn new( pub(crate) unsafe fn new(
world: &'w mut World, world: &'w mut World,
entity: Entity, entity: Entity,
location: EntityLocation, location: Option<EntityLocation>,
) -> Self { ) -> Self {
debug_assert!(world.entities().contains(entity)); debug_assert!(world.entities().contains(entity));
debug_assert_eq!(world.entities().get(entity), Some(location)); debug_assert_eq!(world.entities().get(entity), location);
EntityWorldMut { EntityWorldMut {
world, world,
@ -1216,8 +1217,10 @@ impl<'w> EntityWorldMut<'w> {
/// If the entity has been despawned while this `EntityWorldMut` is still alive. /// If the entity has been despawned while this `EntityWorldMut` is still alive.
#[inline] #[inline]
pub fn location(&self) -> EntityLocation { pub fn location(&self) -> EntityLocation {
self.assert_not_despawned(); match self.location {
self.location Some(loc) => loc,
None => self.panic_despawned(),
}
} }
/// Returns the archetype that the current entity belongs to. /// Returns the archetype that the current entity belongs to.
@ -1227,8 +1230,8 @@ impl<'w> EntityWorldMut<'w> {
/// If the entity has been despawned while this `EntityWorldMut` is still alive. /// If the entity has been despawned while this `EntityWorldMut` is still alive.
#[inline] #[inline]
pub fn archetype(&self) -> &Archetype { pub fn archetype(&self) -> &Archetype {
self.assert_not_despawned(); let location = self.location();
&self.world.archetypes[self.location.archetype_id] &self.world.archetypes[location.archetype_id]
} }
/// Returns `true` if the current entity has a component of type `T`. /// Returns `true` if the current entity has a component of type `T`.
@ -1830,22 +1833,22 @@ impl<'w> EntityWorldMut<'w> {
caller: MaybeLocation, caller: MaybeLocation,
relationship_hook_mode: RelationshipHookMode, relationship_hook_mode: RelationshipHookMode,
) -> &mut Self { ) -> &mut Self {
self.assert_not_despawned(); let location = self.location();
let change_tick = self.world.change_tick(); let change_tick = self.world.change_tick();
let mut bundle_inserter = let mut bundle_inserter =
BundleInserter::new::<T>(self.world, self.location.archetype_id, change_tick); BundleInserter::new::<T>(self.world, location.archetype_id, change_tick);
// SAFETY: location matches current entity. `T` matches `bundle_info` // SAFETY: location matches current entity. `T` matches `bundle_info`
let (location, after_effect) = unsafe { let (location, after_effect) = unsafe {
bundle_inserter.insert( bundle_inserter.insert(
self.entity, self.entity,
self.location, location,
bundle, bundle,
mode, mode,
caller, caller,
relationship_hook_mode, relationship_hook_mode,
) )
}; };
self.location = location; self.location = Some(location);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
after_effect.apply(self); after_effect.apply(self);
@ -1894,7 +1897,7 @@ impl<'w> EntityWorldMut<'w> {
caller: MaybeLocation, caller: MaybeLocation,
relationship_hook_insert_mode: RelationshipHookMode, relationship_hook_insert_mode: RelationshipHookMode,
) -> &mut Self { ) -> &mut Self {
self.assert_not_despawned(); let location = self.location();
let change_tick = self.world.change_tick(); let change_tick = self.world.change_tick();
let bundle_id = self.world.bundles.init_component_info( let bundle_id = self.world.bundles.init_component_info(
&mut self.world.storages, &mut self.world.storages,
@ -1903,23 +1906,19 @@ impl<'w> EntityWorldMut<'w> {
); );
let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); let storage_type = self.world.bundles.get_storage_unchecked(bundle_id);
let bundle_inserter = BundleInserter::new_with_id( let bundle_inserter =
self.world, BundleInserter::new_with_id(self.world, location.archetype_id, bundle_id, change_tick);
self.location.archetype_id,
bundle_id,
change_tick,
);
self.location = insert_dynamic_bundle( self.location = Some(insert_dynamic_bundle(
bundle_inserter, bundle_inserter,
self.entity, self.entity,
self.location, location,
Some(component).into_iter(), Some(component).into_iter(),
Some(storage_type).iter().cloned(), Some(storage_type).iter().cloned(),
mode, mode,
caller, caller,
relationship_hook_insert_mode, relationship_hook_insert_mode,
); ));
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -1957,7 +1956,7 @@ impl<'w> EntityWorldMut<'w> {
iter_components: I, iter_components: I,
relationship_hook_insert_mode: RelationshipHookMode, relationship_hook_insert_mode: RelationshipHookMode,
) -> &mut Self { ) -> &mut Self {
self.assert_not_despawned(); let location = self.location();
let change_tick = self.world.change_tick(); let change_tick = self.world.change_tick();
let bundle_id = self.world.bundles.init_dynamic_info( let bundle_id = self.world.bundles.init_dynamic_info(
&mut self.world.storages, &mut self.world.storages,
@ -1966,23 +1965,19 @@ impl<'w> EntityWorldMut<'w> {
); );
let mut storage_types = let mut storage_types =
core::mem::take(self.world.bundles.get_storages_unchecked(bundle_id)); core::mem::take(self.world.bundles.get_storages_unchecked(bundle_id));
let bundle_inserter = BundleInserter::new_with_id( let bundle_inserter =
self.world, BundleInserter::new_with_id(self.world, location.archetype_id, bundle_id, change_tick);
self.location.archetype_id,
bundle_id,
change_tick,
);
self.location = insert_dynamic_bundle( self.location = Some(insert_dynamic_bundle(
bundle_inserter, bundle_inserter,
self.entity, self.entity,
self.location, location,
iter_components, iter_components,
(*storage_types).iter().cloned(), (*storage_types).iter().cloned(),
InsertMode::Replace, InsertMode::Replace,
MaybeLocation::caller(), MaybeLocation::caller(),
relationship_hook_insert_mode, relationship_hook_insert_mode,
); ));
*self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types); *self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
@ -2000,13 +1995,12 @@ impl<'w> EntityWorldMut<'w> {
#[must_use] #[must_use]
#[track_caller] #[track_caller]
pub fn take<T: Bundle + BundleFromComponents>(&mut self) -> Option<T> { pub fn take<T: Bundle + BundleFromComponents>(&mut self) -> Option<T> {
self.assert_not_despawned(); let location = self.location();
let entity = self.entity; let entity = self.entity;
let location = self.location;
let mut remover = let mut remover =
// SAFETY: The archetype id must be valid since this entity is in it. // SAFETY: The archetype id must be valid since this entity is in it.
unsafe { BundleRemover::new::<T>(self.world, self.location.archetype_id, true) }?; unsafe { BundleRemover::new::<T>(self.world, location.archetype_id, true) }?;
// SAFETY: The passed location has the sane archetype as the remover, since they came from the same location. // SAFETY: The passed location has the sane archetype as the remover, since they came from the same location.
let (new_location, result) = unsafe { let (new_location, result) = unsafe {
remover.remove( remover.remove(
@ -2041,7 +2035,7 @@ impl<'w> EntityWorldMut<'w> {
}, },
) )
}; };
self.location = new_location; self.location = Some(new_location);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
@ -2062,11 +2056,11 @@ impl<'w> EntityWorldMut<'w> {
#[inline] #[inline]
pub(crate) fn remove_with_caller<T: Bundle>(&mut self, caller: MaybeLocation) -> &mut Self { pub(crate) fn remove_with_caller<T: Bundle>(&mut self, caller: MaybeLocation) -> &mut Self {
self.assert_not_despawned(); let location = self.location();
let Some(mut remover) = let Some(mut remover) =
// SAFETY: The archetype id must be valid since this entity is in it. // SAFETY: The archetype id must be valid since this entity is in it.
(unsafe { BundleRemover::new::<T>(self.world, self.location.archetype_id, false) }) (unsafe { BundleRemover::new::<T>(self.world, location.archetype_id, false) })
else { else {
return self; return self;
}; };
@ -2074,14 +2068,14 @@ impl<'w> EntityWorldMut<'w> {
let new_location = unsafe { let new_location = unsafe {
remover.remove( remover.remove(
self.entity, self.entity,
self.location, location,
caller, caller,
BundleRemover::empty_pre_remove, BundleRemover::empty_pre_remove,
) )
} }
.0; .0;
self.location = new_location; self.location = Some(new_location);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2101,7 +2095,7 @@ impl<'w> EntityWorldMut<'w> {
&mut self, &mut self,
caller: MaybeLocation, caller: MaybeLocation,
) -> &mut Self { ) -> &mut Self {
self.assert_not_despawned(); let location = self.location();
let storages = &mut self.world.storages; let storages = &mut self.world.storages;
let bundles = &mut self.world.bundles; let bundles = &mut self.world.bundles;
// SAFETY: These come from the same world. // SAFETY: These come from the same world.
@ -2112,7 +2106,7 @@ impl<'w> EntityWorldMut<'w> {
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it. // SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
let Some(mut remover) = (unsafe { let Some(mut remover) = (unsafe {
BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false) BundleRemover::new_with_id(self.world, location.archetype_id, bundle_id, false)
}) else { }) else {
return self; return self;
}; };
@ -2120,14 +2114,14 @@ impl<'w> EntityWorldMut<'w> {
let new_location = unsafe { let new_location = unsafe {
remover.remove( remover.remove(
self.entity, self.entity,
self.location, location,
caller, caller,
BundleRemover::empty_pre_remove, BundleRemover::empty_pre_remove,
) )
} }
.0; .0;
self.location = new_location; self.location = Some(new_location);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2147,7 +2141,7 @@ impl<'w> EntityWorldMut<'w> {
#[inline] #[inline]
pub(crate) fn retain_with_caller<T: Bundle>(&mut self, caller: MaybeLocation) -> &mut Self { pub(crate) fn retain_with_caller<T: Bundle>(&mut self, caller: MaybeLocation) -> &mut Self {
self.assert_not_despawned(); let old_location = self.location();
let archetypes = &mut self.world.archetypes; let archetypes = &mut self.world.archetypes;
let storages = &mut self.world.storages; let storages = &mut self.world.storages;
// SAFETY: These come from the same world. // SAFETY: These come from the same world.
@ -2161,7 +2155,6 @@ impl<'w> EntityWorldMut<'w> {
.register_info::<T>(&mut registrator, storages); .register_info::<T>(&mut registrator, storages);
// SAFETY: `retained_bundle` exists as we just initialized it. // SAFETY: `retained_bundle` exists as we just initialized it.
let retained_bundle_info = unsafe { self.world.bundles.get_unchecked(retained_bundle) }; let retained_bundle_info = unsafe { self.world.bundles.get_unchecked(retained_bundle) };
let old_location = self.location;
let old_archetype = &mut archetypes[old_location.archetype_id]; let old_archetype = &mut archetypes[old_location.archetype_id];
// PERF: this could be stored in an Archetype Edge // PERF: this could be stored in an Archetype Edge
@ -2176,7 +2169,7 @@ impl<'w> EntityWorldMut<'w> {
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it. // SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
let Some(mut remover) = (unsafe { let Some(mut remover) = (unsafe {
BundleRemover::new_with_id(self.world, self.location.archetype_id, remove_bundle, false) BundleRemover::new_with_id(self.world, old_location.archetype_id, remove_bundle, false)
}) else { }) else {
return self; return self;
}; };
@ -2184,14 +2177,14 @@ impl<'w> EntityWorldMut<'w> {
let new_location = unsafe { let new_location = unsafe {
remover.remove( remover.remove(
self.entity, self.entity,
self.location, old_location,
caller, caller,
BundleRemover::empty_pre_remove, BundleRemover::empty_pre_remove,
) )
} }
.0; .0;
self.location = new_location; self.location = Some(new_location);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2216,7 +2209,7 @@ impl<'w> EntityWorldMut<'w> {
component_id: ComponentId, component_id: ComponentId,
caller: MaybeLocation, caller: MaybeLocation,
) -> &mut Self { ) -> &mut Self {
self.assert_not_despawned(); let location = self.location();
let components = &mut self.world.components; let components = &mut self.world.components;
let bundle_id = self.world.bundles.init_component_info( let bundle_id = self.world.bundles.init_component_info(
@ -2227,7 +2220,7 @@ impl<'w> EntityWorldMut<'w> {
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it. // SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
let Some(mut remover) = (unsafe { let Some(mut remover) = (unsafe {
BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false) BundleRemover::new_with_id(self.world, location.archetype_id, bundle_id, false)
}) else { }) else {
return self; return self;
}; };
@ -2235,14 +2228,14 @@ impl<'w> EntityWorldMut<'w> {
let new_location = unsafe { let new_location = unsafe {
remover.remove( remover.remove(
self.entity, self.entity,
self.location, location,
caller, caller,
BundleRemover::empty_pre_remove, BundleRemover::empty_pre_remove,
) )
} }
.0; .0;
self.location = new_location; self.location = Some(new_location);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2258,7 +2251,7 @@ impl<'w> EntityWorldMut<'w> {
/// entity has been despawned while this `EntityWorldMut` is still alive. /// entity has been despawned while this `EntityWorldMut` is still alive.
#[track_caller] #[track_caller]
pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self { pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self {
self.assert_not_despawned(); let location = self.location();
let components = &mut self.world.components; let components = &mut self.world.components;
let bundle_id = self.world.bundles.init_dynamic_info( let bundle_id = self.world.bundles.init_dynamic_info(
@ -2269,7 +2262,7 @@ impl<'w> EntityWorldMut<'w> {
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it. // SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
let Some(mut remover) = (unsafe { let Some(mut remover) = (unsafe {
BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false) BundleRemover::new_with_id(self.world, location.archetype_id, bundle_id, false)
}) else { }) else {
return self; return self;
}; };
@ -2277,14 +2270,14 @@ impl<'w> EntityWorldMut<'w> {
let new_location = unsafe { let new_location = unsafe {
remover.remove( remover.remove(
self.entity, self.entity,
self.location, location,
MaybeLocation::caller(), MaybeLocation::caller(),
BundleRemover::empty_pre_remove, BundleRemover::empty_pre_remove,
) )
} }
.0; .0;
self.location = new_location; self.location = Some(new_location);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2302,7 +2295,7 @@ impl<'w> EntityWorldMut<'w> {
#[inline] #[inline]
pub(crate) fn clear_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { pub(crate) fn clear_with_caller(&mut self, caller: MaybeLocation) -> &mut Self {
self.assert_not_despawned(); let location = self.location();
let component_ids: Vec<ComponentId> = self.archetype().components().collect(); let component_ids: Vec<ComponentId> = self.archetype().components().collect();
let components = &mut self.world.components; let components = &mut self.world.components;
@ -2314,7 +2307,7 @@ impl<'w> EntityWorldMut<'w> {
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it. // SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
let Some(mut remover) = (unsafe { let Some(mut remover) = (unsafe {
BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false) BundleRemover::new_with_id(self.world, location.archetype_id, bundle_id, false)
}) else { }) else {
return self; return self;
}; };
@ -2322,14 +2315,14 @@ impl<'w> EntityWorldMut<'w> {
let new_location = unsafe { let new_location = unsafe {
remover.remove( remover.remove(
self.entity, self.entity,
self.location, location,
caller, caller,
BundleRemover::empty_pre_remove, BundleRemover::empty_pre_remove,
) )
} }
.0; .0;
self.location = new_location; self.location = Some(new_location);
self.world.flush(); self.world.flush();
self.update_location(); self.update_location();
self self
@ -2353,9 +2346,9 @@ impl<'w> EntityWorldMut<'w> {
} }
pub(crate) fn despawn_with_caller(self, caller: MaybeLocation) { pub(crate) fn despawn_with_caller(self, caller: MaybeLocation) {
self.assert_not_despawned(); let location = self.location();
let world = self.world; let world = self.world;
let archetype = &world.archetypes[self.location.archetype_id]; let archetype = &world.archetypes[location.archetype_id];
// SAFETY: Archetype cannot be mutably aliased by DeferredWorld // SAFETY: Archetype cannot be mutably aliased by DeferredWorld
let (archetype, mut deferred_world) = unsafe { let (archetype, mut deferred_world) = unsafe {
@ -2422,13 +2415,14 @@ impl<'w> EntityWorldMut<'w> {
let location = world let location = world
.entities .entities
.free(self.entity) .free(self.entity)
.flatten()
.expect("entity should exist at this point."); .expect("entity should exist at this point.");
let table_row; let table_row;
let moved_entity; let moved_entity;
let change_tick = world.change_tick(); let change_tick = world.change_tick();
{ {
let archetype = &mut world.archetypes[self.location.archetype_id]; let archetype = &mut world.archetypes[location.archetype_id];
let remove_result = archetype.swap_remove(location.archetype_row); let remove_result = archetype.swap_remove(location.archetype_row);
if let Some(swapped_entity) = remove_result.swapped_entity { if let Some(swapped_entity) = remove_result.swapped_entity {
let swapped_location = world.entities.get(swapped_entity).unwrap(); let swapped_location = world.entities.get(swapped_entity).unwrap();
@ -2437,12 +2431,12 @@ impl<'w> EntityWorldMut<'w> {
unsafe { unsafe {
world.entities.set( world.entities.set(
swapped_entity.index(), swapped_entity.index(),
EntityLocation { Some(EntityLocation {
archetype_id: swapped_location.archetype_id, archetype_id: swapped_location.archetype_id,
archetype_row: location.archetype_row, archetype_row: location.archetype_row,
table_id: swapped_location.table_id, table_id: swapped_location.table_id,
table_row: swapped_location.table_row, table_row: swapped_location.table_row,
}, }),
); );
world world
.entities .entities
@ -2469,12 +2463,12 @@ impl<'w> EntityWorldMut<'w> {
unsafe { unsafe {
world.entities.set( world.entities.set(
moved_entity.index(), moved_entity.index(),
EntityLocation { Some(EntityLocation {
archetype_id: moved_location.archetype_id, archetype_id: moved_location.archetype_id,
archetype_row: moved_location.archetype_row, archetype_row: moved_location.archetype_row,
table_id: moved_location.table_id, table_id: moved_location.table_id,
table_row, table_row,
}, }),
); );
world world
.entities .entities
@ -2566,11 +2560,7 @@ impl<'w> EntityWorldMut<'w> {
/// This is *only* required when using the unsafe function [`EntityWorldMut::world_mut`], /// This is *only* required when using the unsafe function [`EntityWorldMut::world_mut`],
/// which enables the location to change. /// which enables the location to change.
pub fn update_location(&mut self) { pub fn update_location(&mut self) {
self.location = self self.location = self.world.entities().get(self.entity);
.world
.entities()
.get(self.entity)
.unwrap_or(EntityLocation::INVALID);
} }
/// Returns if the entity has been despawned. /// Returns if the entity has been despawned.
@ -2582,7 +2572,7 @@ impl<'w> EntityWorldMut<'w> {
/// to avoid panicking when calling further methods. /// to avoid panicking when calling further methods.
#[inline] #[inline]
pub fn is_despawned(&self) -> bool { pub fn is_despawned(&self) -> bool {
self.location.archetype_id == ArchetypeId::INVALID self.location.is_none()
} }
/// Gets an Entry into the world for this entity and component for in-place manipulation. /// Gets an Entry into the world for this entity and component for in-place manipulation.

View File

@ -43,7 +43,7 @@ use crate::{
ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable, ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable,
RequiredComponents, RequiredComponentsError, Tick, RequiredComponents, RequiredComponentsError, Tick,
}, },
entity::{Entities, Entity, EntityDoesNotExistError, EntityLocation}, entity::{Entities, Entity, EntityDoesNotExistError},
entity_disabling::DefaultQueryFilters, entity_disabling::DefaultQueryFilters,
event::{Event, EventId, Events, SendBatchIds}, event::{Event, EventId, Events, SendBatchIds},
observer::Observers, observer::Observers,
@ -1154,16 +1154,15 @@ impl World {
let entity = self.entities.alloc(); let entity = self.entities.alloc();
let mut bundle_spawner = BundleSpawner::new::<B>(self, change_tick); let mut bundle_spawner = BundleSpawner::new::<B>(self, change_tick);
// SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent // SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent
let (mut entity_location, after_effect) = let (entity_location, after_effect) =
unsafe { bundle_spawner.spawn_non_existent(entity, bundle, caller) }; unsafe { bundle_spawner.spawn_non_existent(entity, bundle, caller) };
let mut entity_location = Some(entity_location);
// SAFETY: command_queue is not referenced anywhere else // SAFETY: command_queue is not referenced anywhere else
if !unsafe { self.command_queue.is_empty() } { if !unsafe { self.command_queue.is_empty() } {
self.flush(); self.flush();
entity_location = self entity_location = self.entities().get(entity);
.entities()
.get(entity)
.unwrap_or(EntityLocation::INVALID);
} }
// SAFETY: entity and location are valid, as they were just created above // SAFETY: entity and location are valid, as they were just created above
@ -1186,11 +1185,11 @@ impl World {
// empty // empty
let location = unsafe { archetype.allocate(entity, table_row) }; let location = unsafe { archetype.allocate(entity, table_row) };
let change_tick = self.change_tick(); let change_tick = self.change_tick();
self.entities.set(entity.index(), location); self.entities.set(entity.index(), Some(location));
self.entities self.entities
.mark_spawn_despawn(entity.index(), caller, change_tick); .mark_spawn_despawn(entity.index(), caller, change_tick);
EntityWorldMut::new(self, entity, location) EntityWorldMut::new(self, entity, Some(location))
} }
/// Spawns a batch of entities with the same component [`Bundle`] type. Takes a given /// Spawns a batch of entities with the same component [`Bundle`] type. Takes a given
@ -2725,7 +2724,7 @@ impl World {
|entity, location| { |entity, location| {
// SAFETY: no components are allocated by archetype.allocate() because the archetype // SAFETY: no components are allocated by archetype.allocate() because the archetype
// is empty // is empty
*location = empty_archetype.allocate(entity, table.allocate(entity)); *location = Some(empty_archetype.allocate(entity, table.allocate(entity)));
}, },
by, by,
at, at,

View File

@ -0,0 +1,17 @@
---
title: Entities APIs
pull_requests: [19350, 19433]
---
`Entities::flush` now also asks for metadata about the flush operation
that will be stored for the flushed entities. For the source location,
`MaybeLocation::caller()` can be used; the tick should be retrieved
from the world.
Additionally, flush now gives `&mut EntityIdLocation` instead of `&mut EntityLocation` access.
`EntityIdLocation` is an alias for `Option<EntityLocation>`.
This replaces invalid locations with `None`.
It is possible for an `Entity` id to be allocated/reserved but not yet have a location.
This is used in commands for example, and this reality is more transparent with an `Option`.
This extends to other interfaces: `Entities::free` now returns `Option<EntityIdLocation>` instead of `Option<EntityLocation>`.
`Entities::get` remains unchanged, but you can access an `Entity`'s `EntityIdLocation` through the new `Entities::get_id_location`.

View File

@ -1,9 +0,0 @@
---
title: Flushing
pull_requests: [19350]
---
`Entities::flush` now also asks for metadata about the flush operation
that will be stored for the flushed entities. For the source location,
`MaybeLocation::caller()` can be used; the tick should be retrieved
from the world.