entity refs can have no location

This commit is contained in:
Elliott Pierce 2025-05-31 13:30:33 -04:00
parent 58ee663ece
commit 85b0d03dec
10 changed files with 129 additions and 75 deletions

View File

@ -487,7 +487,10 @@ impl EntityCloner {
#[cfg(not(feature = "bevy_reflect"))] #[cfg(not(feature = "bevy_reflect"))]
let app_registry = Option::<()>::None; let app_registry = Option::<()>::None;
let archetype = source_entity.archetype(); let Some(archetype) = source_entity.archetype() else {
// If the source has no archetype, there is nothing to clone.
return target;
};
bundle_scratch = BundleScratch::with_capacity(archetype.component_count()); bundle_scratch = BundleScratch::with_capacity(archetype.component_count());
for component in archetype.components() { for component in archetype.components() {

View File

@ -782,20 +782,19 @@ impl Entities {
} }
/// Returns the [`EntityLocation`] of an [`Entity`]. /// Returns the [`EntityLocation`] of an [`Entity`].
/// Note: for pending entities and entities not participating in the ECS (entities with a [`EntityIdLocation`] of `None`), returns `None`. /// Note: for non-constructed entities, returns `None`.
#[inline] #[inline]
pub fn get(&self, entity: Entity) -> Option<EntityLocation> { pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
self.get_id_location(entity).flatten() self.get_id_location(entity).flatten()
} }
/// Returns the [`EntityIdLocation`] of an [`Entity`]. /// Returns the [`EntityIdLocation`] of an [`Entity`].
/// Note: for pending entities, returns `None`.
#[inline] #[inline]
pub fn get_id_location(&self, entity: Entity) -> Option<EntityIdLocation> { pub fn get_id_location(&self, entity: Entity) -> Option<EntityIdLocation> {
self.meta match self.meta.get(entity.index() as usize) {
.get(entity.index() as usize) Some(meta) => (meta.generation == entity.generation).then_some(meta.location),
.filter(|meta| meta.generation == entity.generation) None => (entity.generation() == EntityGeneration::FIRST).then_some(None),
.map(|meta| meta.location) }
} }
/// Returns true if the entity exists in the world *now*: /// Returns true if the entity exists in the world *now*:

View File

@ -353,6 +353,36 @@ mod tests {
); );
} }
#[test]
fn construct_and_destruct() {
let mut world = World::new();
let e1 = world.spawn_null();
world.construct(e1, (TableStored("abc"), A(123))).unwrap();
let e2 = world.spawn_null();
assert!(world.destruct(e2).is_some());
assert!(world.despawn(e2));
let e3 = world.spawn_null();
let mut e3 = world.entity_mut(e3);
e3.destruct();
e3.despawn();
let e4 = world.spawn_null();
world
.entity_mut(e4)
.construct((TableStored("junk"), A(0)))
.unwrap()
.destruct()
.construct((TableStored("def"), A(456)))
.unwrap();
assert_eq!(world.entities.count_active(), 2);
assert!(world.despawn(e1));
assert_eq!(world.entities.count_active(), 1);
assert!(world.get::<TableStored>(e1).is_none());
assert!(world.get::<A>(e1).is_none());
assert_eq!(world.get::<TableStored>(e4).unwrap().0, "def");
assert_eq!(world.get::<A>(e4).unwrap().0, 456);
}
#[test] #[test]
fn despawn_table_storage() { fn despawn_table_storage() {
let mut world = World::new(); let mut world = World::new();

View File

@ -8,7 +8,7 @@ use crate::{
event::{Event, EventId, Events, SendBatchIds}, event::{Event, EventId, Events, SendBatchIds},
observer::{Observers, TriggerTargets}, observer::{Observers, TriggerTargets},
prelude::{Component, QueryState}, prelude::{Component, QueryState},
query::{QueryData, QueryFilter}, query::{DebugCheckedUnwrap, QueryData, QueryFilter},
relationship::RelationshipHookMode, relationship::RelationshipHookMode,
resource::Resource, resource::Resource,
system::{Commands, Query}, system::{Commands, Query},
@ -144,7 +144,8 @@ impl<'w> DeferredWorld<'w> {
return Ok(None); return Ok(None);
} }
let archetype = &raw const *entity_cell.archetype(); // SAFETY: If the archetype was none, it would not have the component on it.
let archetype = unsafe { &raw const *entity_cell.archetype().debug_checked_unwrap() };
// SAFETY: // SAFETY:
// - DeferredWorld ensures archetype pointer will remain valid as no // - DeferredWorld ensures archetype pointer will remain valid as no

View File

@ -215,12 +215,12 @@ unsafe impl WorldEntityFetch for Entity {
) -> Result<Self::Mut<'_>, EntityMutableFetchError> { ) -> Result<Self::Mut<'_>, EntityMutableFetchError> {
let location = cell let location = cell
.entities() .entities()
.get(self) .get_id_location(self)
.ok_or(EntityDoesNotExistError::new(self, cell.entities()))?; .ok_or(EntityDoesNotExistError::new(self, cell.entities()))?;
// 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, Some(location)) }) Ok(unsafe { EntityWorldMut::new(world, self, location) })
} }
unsafe fn fetch_deferred_mut( unsafe fn fetch_deferred_mut(

View File

@ -78,13 +78,13 @@ impl<'w> EntityRef<'w> {
/// Gets metadata indicating the location where the current entity is stored. /// Gets metadata indicating the location where the current entity is stored.
#[inline] #[inline]
pub fn location(&self) -> EntityLocation { pub fn location(&self) -> EntityIdLocation {
self.cell.location() self.cell.location()
} }
/// Returns the archetype that the current entity belongs to. /// Returns the archetype that the current entity belongs to.
#[inline] #[inline]
pub fn archetype(&self) -> &Archetype { pub fn archetype(&self) -> Option<&Archetype> {
self.cell.archetype() self.cell.archetype()
} }
@ -488,13 +488,13 @@ impl<'w> EntityMut<'w> {
/// Gets metadata indicating the location where the current entity is stored. /// Gets metadata indicating the location where the current entity is stored.
#[inline] #[inline]
pub fn location(&self) -> EntityLocation { pub fn location(&self) -> EntityIdLocation {
self.cell.location() self.cell.location()
} }
/// Returns the archetype that the current entity belongs to. /// Returns the archetype that the current entity belongs to.
#[inline] #[inline]
pub fn archetype(&self) -> &Archetype { pub fn archetype(&self) -> Option<&Archetype> {
self.cell.archetype() self.cell.archetype()
} }
@ -1123,37 +1123,34 @@ impl<'w> EntityWorldMut<'w> {
} }
fn as_unsafe_entity_cell_readonly(&self) -> UnsafeEntityCell<'_> { fn as_unsafe_entity_cell_readonly(&self) -> UnsafeEntityCell<'_> {
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,
location, self.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<'_> {
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,
location, self.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> {
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,
location, self.location,
last_change_tick, last_change_tick,
change_tick, change_tick,
) )
@ -3275,13 +3272,13 @@ impl<'w> FilteredEntityRef<'w> {
/// Gets metadata indicating the location where the current entity is stored. /// Gets metadata indicating the location where the current entity is stored.
#[inline] #[inline]
pub fn location(&self) -> EntityLocation { pub fn location(&self) -> EntityIdLocation {
self.entity.location() self.entity.location()
} }
/// Returns the archetype that the current entity belongs to. /// Returns the archetype that the current entity belongs to.
#[inline] #[inline]
pub fn archetype(&self) -> &Archetype { pub fn archetype(&self) -> Option<&Archetype> {
self.entity.archetype() self.entity.archetype()
} }
@ -3617,13 +3614,13 @@ impl<'w> FilteredEntityMut<'w> {
/// Gets metadata indicating the location where the current entity is stored. /// Gets metadata indicating the location where the current entity is stored.
#[inline] #[inline]
pub fn location(&self) -> EntityLocation { pub fn location(&self) -> EntityIdLocation {
self.entity.location() self.entity.location()
} }
/// Returns the archetype that the current entity belongs to. /// Returns the archetype that the current entity belongs to.
#[inline] #[inline]
pub fn archetype(&self) -> &Archetype { pub fn archetype(&self) -> Option<&Archetype> {
self.entity.archetype() self.entity.archetype()
} }
@ -4928,7 +4925,10 @@ mod tests {
let ent = world.spawn((Marker::<1>, Marker::<2>, Marker::<3>)).id(); let ent = world.spawn((Marker::<1>, Marker::<2>, Marker::<3>)).id();
world.entity_mut(ent).retain::<()>(); world.entity_mut(ent).retain::<()>();
assert_eq!(world.entity(ent).archetype().components().next(), None); assert_eq!(
world.entity(ent).archetype().unwrap().components().next(),
None
);
} }
// Test removing some components with `retain`, including components not on the entity. // Test removing some components with `retain`, including components not on the entity.
@ -4948,6 +4948,7 @@ mod tests {
world world
.entity(ent) .entity(ent)
.archetype() .archetype()
.unwrap()
.components() .components()
.collect::<Vec<_>>() .collect::<Vec<_>>()
.len(), .len(),

View File

@ -977,7 +977,7 @@ impl World {
let cell = UnsafeEntityCell::new( let cell = UnsafeEntityCell::new(
self.as_unsafe_world_cell_readonly(), self.as_unsafe_world_cell_readonly(),
entity, entity,
location, Some(location),
self.last_change_tick, self.last_change_tick,
self.read_change_tick(), self.read_change_tick(),
); );
@ -1000,7 +1000,7 @@ impl World {
let cell = UnsafeEntityCell::new( let cell = UnsafeEntityCell::new(
world_cell, world_cell,
entity, entity,
location, Some(location),
last_change_tick, last_change_tick,
change_tick, change_tick,
); );

View File

@ -8,7 +8,7 @@ use crate::{
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells}, component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
entity::{ entity::{
ContainsEntity, Entities, EntitiesAllocator, Entity, EntityDoesNotExistError, ContainsEntity, Entities, EntitiesAllocator, Entity, EntityDoesNotExistError,
EntityLocation, EntityIdLocation, EntityLocation,
}, },
error::{DefaultErrorHandler, ErrorHandler}, error::{DefaultErrorHandler, ErrorHandler},
observer::Observers, observer::Observers,
@ -375,7 +375,7 @@ impl<'w> UnsafeWorldCell<'w> {
) -> Result<UnsafeEntityCell<'w>, EntityDoesNotExistError> { ) -> Result<UnsafeEntityCell<'w>, EntityDoesNotExistError> {
let location = self let location = self
.entities() .entities()
.get(entity) .get_id_location(entity)
.ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?;
Ok(UnsafeEntityCell::new( Ok(UnsafeEntityCell::new(
self, self,
@ -397,7 +397,7 @@ impl<'w> UnsafeWorldCell<'w> {
) -> Result<UnsafeEntityCell<'w>, EntityDoesNotExistError> { ) -> Result<UnsafeEntityCell<'w>, EntityDoesNotExistError> {
let location = self let location = self
.entities() .entities()
.get(entity) .get_id_location(entity)
.ok_or(EntityDoesNotExistError::new(entity, self.entities()))?; .ok_or(EntityDoesNotExistError::new(entity, self.entities()))?;
Ok(UnsafeEntityCell::new( Ok(UnsafeEntityCell::new(
self, entity, location, last_run, this_run, self, entity, location, last_run, this_run,
@ -743,7 +743,7 @@ impl Debug for UnsafeWorldCell<'_> {
pub struct UnsafeEntityCell<'w> { pub struct UnsafeEntityCell<'w> {
world: UnsafeWorldCell<'w>, world: UnsafeWorldCell<'w>,
entity: Entity, entity: Entity,
location: EntityLocation, location: EntityIdLocation,
last_run: Tick, last_run: Tick,
this_run: Tick, this_run: Tick,
} }
@ -753,7 +753,7 @@ impl<'w> UnsafeEntityCell<'w> {
pub(crate) fn new( pub(crate) fn new(
world: UnsafeWorldCell<'w>, world: UnsafeWorldCell<'w>,
entity: Entity, entity: Entity,
location: EntityLocation, location: EntityIdLocation,
last_run: Tick, last_run: Tick,
this_run: Tick, this_run: Tick,
) -> Self { ) -> Self {
@ -775,14 +775,15 @@ impl<'w> UnsafeEntityCell<'w> {
/// Gets metadata indicating the location where the current entity is stored. /// Gets metadata indicating the location where the current entity is stored.
#[inline] #[inline]
pub fn location(self) -> EntityLocation { pub fn location(self) -> EntityIdLocation {
self.location self.location
} }
/// Returns the archetype that the current entity belongs to. /// Returns the archetype that the current entity belongs to.
#[inline] #[inline]
pub fn archetype(self) -> &'w Archetype { pub fn archetype(self) -> Option<&'w Archetype> {
&self.world.archetypes()[self.location.archetype_id] self.location
.map(|loc| &self.world.archetypes()[loc.archetype_id])
} }
/// Gets the world that the current entity belongs to. /// Gets the world that the current entity belongs to.
@ -813,7 +814,8 @@ impl<'w> UnsafeEntityCell<'w> {
/// [`Self::contains_type_id`]. /// [`Self::contains_type_id`].
#[inline] #[inline]
pub fn contains_id(self, component_id: ComponentId) -> bool { pub fn contains_id(self, component_id: ComponentId) -> bool {
self.archetype().contains(component_id) self.archetype()
.is_some_and(|archetype| archetype.contains(component_id))
} }
/// Returns `true` if the current entity has a component with the type identified by `type_id`. /// Returns `true` if the current entity has a component with the type identified by `type_id`.
@ -848,7 +850,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id, component_id,
T::STORAGE_TYPE, T::STORAGE_TYPE,
self.entity, self.entity,
self.location, self.location?,
) )
// SAFETY: returned component is of type T // SAFETY: returned component is of type T
.map(|value| value.deref::<T>()) .map(|value| value.deref::<T>())
@ -875,7 +877,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id, component_id,
T::STORAGE_TYPE, T::STORAGE_TYPE,
self.entity, self.entity,
self.location, self.location?,
) )
.map(|(value, cells, caller)| Ref { .map(|(value, cells, caller)| Ref {
// SAFETY: returned component is of type T // SAFETY: returned component is of type T
@ -906,7 +908,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id, component_id,
T::STORAGE_TYPE, T::STORAGE_TYPE,
self.entity, self.entity,
self.location, self.location?,
) )
} }
} }
@ -938,7 +940,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id, component_id,
info.storage_type(), info.storage_type(),
self.entity, self.entity,
self.location, self.location?,
) )
} }
} }
@ -991,7 +993,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id, component_id,
T::STORAGE_TYPE, T::STORAGE_TYPE,
self.entity, self.entity,
self.location, self.location?,
) )
.map(|(value, cells, caller)| Mut { .map(|(value, cells, caller)| Mut {
// SAFETY: returned component is of type T // SAFETY: returned component is of type T
@ -1015,7 +1017,7 @@ impl<'w> UnsafeEntityCell<'w> {
let world = self.world().world(); let world = self.world().world();
Q::get_state(world.components())? Q::get_state(world.components())?
}; };
let location = self.location(); let location = self.location()?;
// SAFETY: Location is guaranteed to exist // SAFETY: Location is guaranteed to exist
let archetype = unsafe { let archetype = unsafe {
self.world self.world
@ -1068,7 +1070,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id, component_id,
info.storage_type(), info.storage_type(),
self.entity, self.entity,
self.location, self.location?,
) )
} }
} }
@ -1103,20 +1105,23 @@ impl<'w> UnsafeEntityCell<'w> {
// SAFETY: entity_location is valid, component_id is valid as checked by the line above // SAFETY: entity_location is valid, component_id is valid as checked by the line above
unsafe { unsafe {
get_component_and_ticks( self.location
self.world, .and_then(|location| {
component_id, get_component_and_ticks(
info.storage_type(), self.world,
self.entity, component_id,
self.location, info.storage_type(),
) self.entity,
.map(|(value, cells, caller)| MutUntyped { location,
// SAFETY: world access validated by caller and ties world lifetime to `MutUntyped` lifetime )
value: value.assert_unique(), })
ticks: TicksMut::from_tick_cells(cells, self.last_run, self.this_run), .map(|(value, cells, caller)| MutUntyped {
changed_by: caller.map(|caller| caller.deref_mut()), // SAFETY: world access validated by caller and ties world lifetime to `MutUntyped` lifetime
}) value: value.assert_unique(),
.ok_or(GetEntityMutByIdError::ComponentNotFound) ticks: TicksMut::from_tick_cells(cells, self.last_run, self.this_run),
changed_by: caller.map(|caller| caller.deref_mut()),
})
.ok_or(GetEntityMutByIdError::ComponentNotFound)
} }
} }
@ -1147,20 +1152,23 @@ impl<'w> UnsafeEntityCell<'w> {
// SAFETY: entity_location is valid, component_id is valid as checked by the line above // SAFETY: entity_location is valid, component_id is valid as checked by the line above
unsafe { unsafe {
get_component_and_ticks( self.location
self.world, .and_then(|location| {
component_id, get_component_and_ticks(
info.storage_type(), self.world,
self.entity, component_id,
self.location, info.storage_type(),
) self.entity,
.map(|(value, cells, caller)| MutUntyped { location,
// SAFETY: world access validated by caller and ties world lifetime to `MutUntyped` lifetime )
value: value.assert_unique(), })
ticks: TicksMut::from_tick_cells(cells, self.last_run, self.this_run), .map(|(value, cells, caller)| MutUntyped {
changed_by: caller.map(|caller| caller.deref_mut()), // SAFETY: world access validated by caller and ties world lifetime to `MutUntyped` lifetime
}) value: value.assert_unique(),
.ok_or(GetEntityMutByIdError::ComponentNotFound) ticks: TicksMut::from_tick_cells(cells, self.last_run, self.this_run),
changed_by: caller.map(|caller| caller.deref_mut()),
})
.ok_or(GetEntityMutByIdError::ComponentNotFound)
} }
} }

View File

@ -1125,7 +1125,11 @@ pub fn process_remote_list_request(In(params): In<Option<Value>>, world: &World)
// If `Some`, return all components of the provided entity. // If `Some`, return all components of the provided entity.
if let Some(BrpListParams { entity }) = params.map(parse).transpose()? { if let Some(BrpListParams { entity }) = params.map(parse).transpose()? {
let entity = get_entity(world, entity)?; let entity = get_entity(world, entity)?;
for component_id in entity.archetype().components() { for component_id in entity
.archetype()
.iter()
.flat_map(|archetype| archetype.components())
{
let Some(component_info) = world.components().get_info(component_id) else { let Some(component_info) = world.components().get_info(component_id) else {
continue; continue;
}; };
@ -1179,7 +1183,11 @@ pub fn process_remote_list_watching_request(
let entity_ref = get_entity(world, entity)?; let entity_ref = get_entity(world, entity)?;
let mut response = BrpListWatchingResponse::default(); let mut response = BrpListWatchingResponse::default();
for component_id in entity_ref.archetype().components() { for component_id in entity_ref
.archetype()
.iter()
.flat_map(|archetype| archetype.components())
{
let ticks = entity_ref let ticks = entity_ref
.get_change_ticks_by_id(component_id) .get_change_ticks_by_id(component_id)
.ok_or(BrpError::internal("Failed to get ticks"))?; .ok_or(BrpError::internal("Failed to get ticks"))?;

View File

@ -283,7 +283,11 @@ impl<'w> DynamicSceneBuilder<'w> {
}; };
let original_entity = self.original_world.entity(entity); let original_entity = self.original_world.entity(entity);
for component_id in original_entity.archetype().components() { for component_id in original_entity
.archetype()
.iter()
.flat_map(|archetype| archetype.components())
{
let mut extract_and_push = || { let mut extract_and_push = || {
let type_id = self let type_id = self
.original_world .original_world