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"))]
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());
for component in archetype.components() {

View File

@ -782,20 +782,19 @@ impl Entities {
}
/// 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]
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
self.get_id_location(entity).flatten()
}
/// Returns the [`EntityIdLocation`] of an [`Entity`].
/// Note: for pending entities, returns `None`.
#[inline]
pub fn get_id_location(&self, entity: Entity) -> Option<EntityIdLocation> {
self.meta
.get(entity.index() as usize)
.filter(|meta| meta.generation == entity.generation)
.map(|meta| meta.location)
match self.meta.get(entity.index() as usize) {
Some(meta) => (meta.generation == entity.generation).then_some(meta.location),
None => (entity.generation() == EntityGeneration::FIRST).then_some(None),
}
}
/// 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]
fn despawn_table_storage() {
let mut world = World::new();

View File

@ -8,7 +8,7 @@ use crate::{
event::{Event, EventId, Events, SendBatchIds},
observer::{Observers, TriggerTargets},
prelude::{Component, QueryState},
query::{QueryData, QueryFilter},
query::{DebugCheckedUnwrap, QueryData, QueryFilter},
relationship::RelationshipHookMode,
resource::Resource,
system::{Commands, Query},
@ -144,7 +144,8 @@ impl<'w> DeferredWorld<'w> {
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:
// - DeferredWorld ensures archetype pointer will remain valid as no

View File

@ -215,12 +215,12 @@ unsafe impl WorldEntityFetch for Entity {
) -> Result<Self::Mut<'_>, EntityMutableFetchError> {
let location = cell
.entities()
.get(self)
.get_id_location(self)
.ok_or(EntityDoesNotExistError::new(self, cell.entities()))?;
// SAFETY: caller ensures that the world cell has mutable access to the entity.
let world = unsafe { cell.world_mut() };
// 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(

View File

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

View File

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

View File

@ -8,7 +8,7 @@ use crate::{
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
entity::{
ContainsEntity, Entities, EntitiesAllocator, Entity, EntityDoesNotExistError,
EntityLocation,
EntityIdLocation, EntityLocation,
},
error::{DefaultErrorHandler, ErrorHandler},
observer::Observers,
@ -375,7 +375,7 @@ impl<'w> UnsafeWorldCell<'w> {
) -> Result<UnsafeEntityCell<'w>, EntityDoesNotExistError> {
let location = self
.entities()
.get(entity)
.get_id_location(entity)
.ok_or(EntityDoesNotExistError::new(entity, self.entities()))?;
Ok(UnsafeEntityCell::new(
self,
@ -397,7 +397,7 @@ impl<'w> UnsafeWorldCell<'w> {
) -> Result<UnsafeEntityCell<'w>, EntityDoesNotExistError> {
let location = self
.entities()
.get(entity)
.get_id_location(entity)
.ok_or(EntityDoesNotExistError::new(entity, self.entities()))?;
Ok(UnsafeEntityCell::new(
self, entity, location, last_run, this_run,
@ -743,7 +743,7 @@ impl Debug for UnsafeWorldCell<'_> {
pub struct UnsafeEntityCell<'w> {
world: UnsafeWorldCell<'w>,
entity: Entity,
location: EntityLocation,
location: EntityIdLocation,
last_run: Tick,
this_run: Tick,
}
@ -753,7 +753,7 @@ impl<'w> UnsafeEntityCell<'w> {
pub(crate) fn new(
world: UnsafeWorldCell<'w>,
entity: Entity,
location: EntityLocation,
location: EntityIdLocation,
last_run: Tick,
this_run: Tick,
) -> Self {
@ -775,14 +775,15 @@ impl<'w> UnsafeEntityCell<'w> {
/// Gets metadata indicating the location where the current entity is stored.
#[inline]
pub fn location(self) -> EntityLocation {
pub fn location(self) -> EntityIdLocation {
self.location
}
/// Returns the archetype that the current entity belongs to.
#[inline]
pub fn archetype(self) -> &'w Archetype {
&self.world.archetypes()[self.location.archetype_id]
pub fn archetype(self) -> Option<&'w Archetype> {
self.location
.map(|loc| &self.world.archetypes()[loc.archetype_id])
}
/// Gets the world that the current entity belongs to.
@ -813,7 +814,8 @@ impl<'w> UnsafeEntityCell<'w> {
/// [`Self::contains_type_id`].
#[inline]
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`.
@ -848,7 +850,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id,
T::STORAGE_TYPE,
self.entity,
self.location,
self.location?,
)
// SAFETY: returned component is of type T
.map(|value| value.deref::<T>())
@ -875,7 +877,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id,
T::STORAGE_TYPE,
self.entity,
self.location,
self.location?,
)
.map(|(value, cells, caller)| Ref {
// SAFETY: returned component is of type T
@ -906,7 +908,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id,
T::STORAGE_TYPE,
self.entity,
self.location,
self.location?,
)
}
}
@ -938,7 +940,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id,
info.storage_type(),
self.entity,
self.location,
self.location?,
)
}
}
@ -991,7 +993,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id,
T::STORAGE_TYPE,
self.entity,
self.location,
self.location?,
)
.map(|(value, cells, caller)| Mut {
// SAFETY: returned component is of type T
@ -1015,7 +1017,7 @@ impl<'w> UnsafeEntityCell<'w> {
let world = self.world().world();
Q::get_state(world.components())?
};
let location = self.location();
let location = self.location()?;
// SAFETY: Location is guaranteed to exist
let archetype = unsafe {
self.world
@ -1068,7 +1070,7 @@ impl<'w> UnsafeEntityCell<'w> {
component_id,
info.storage_type(),
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
unsafe {
get_component_and_ticks(
self.world,
component_id,
info.storage_type(),
self.entity,
self.location,
)
.map(|(value, cells, caller)| MutUntyped {
// 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),
changed_by: caller.map(|caller| caller.deref_mut()),
})
.ok_or(GetEntityMutByIdError::ComponentNotFound)
self.location
.and_then(|location| {
get_component_and_ticks(
self.world,
component_id,
info.storage_type(),
self.entity,
location,
)
})
.map(|(value, cells, caller)| MutUntyped {
// 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),
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
unsafe {
get_component_and_ticks(
self.world,
component_id,
info.storage_type(),
self.entity,
self.location,
)
.map(|(value, cells, caller)| MutUntyped {
// 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),
changed_by: caller.map(|caller| caller.deref_mut()),
})
.ok_or(GetEntityMutByIdError::ComponentNotFound)
self.location
.and_then(|location| {
get_component_and_ticks(
self.world,
component_id,
info.storage_type(),
self.entity,
location,
)
})
.map(|(value, cells, caller)| MutUntyped {
// 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),
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 let Some(BrpListParams { entity }) = params.map(parse).transpose()? {
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 {
continue;
};
@ -1179,7 +1183,11 @@ pub fn process_remote_list_watching_request(
let entity_ref = get_entity(world, entity)?;
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
.get_change_ticks_by_id(component_id)
.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);
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 type_id = self
.original_world