diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 41835784ce..b04f470484 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -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() { diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 007e13e310..ea44052b81 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -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 { 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 { - 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*: diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index f284aa0f9f..ddef365f3b 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -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::(e1).is_none()); + assert!(world.get::(e1).is_none()); + assert_eq!(world.get::(e4).unwrap().0, "def"); + assert_eq!(world.get::(e4).unwrap().0, 456); + } + #[test] fn despawn_table_storage() { let mut world = World::new(); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index f764c2788e..4ead2ded85 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -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 diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index 8581c96015..3c63193256 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -215,12 +215,12 @@ unsafe impl WorldEntityFetch for Entity { ) -> Result, 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( diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index e21c432216..d6ffbf04c3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -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::>() .len(), diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 96cf13f6db..7a7e74c773 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -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, ); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index affca7bf36..fc384a1bc1 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -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, 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, 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::()) @@ -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) } } diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 18c85d3eec..159ec47744 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -1125,7 +1125,11 @@ pub fn process_remote_list_request(In(params): In>, 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"))?; diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index c9e594107e..057f0afd34 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -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