diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 44e500a15bb36..07df26a09a78d 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -979,7 +979,8 @@ impl Entities { } /// Returns the source code location from which this entity has last been spawned - /// or despawned. Returns `None` if this entity has never existed. + /// or despawned. Returns `None` if its index has been reused by another entity + /// or if this entity has never existed. #[cfg(feature = "track_location")] pub fn entity_get_spawned_or_despawned_by( &self, @@ -987,11 +988,16 @@ impl Entities { ) -> Option<&'static Location<'static>> { self.meta .get(entity.index() as usize) + .filter(|meta| + // Generation is incremented immediately upon despawn + (meta.generation == entity.generation) + || (meta.location.archetype_id == ArchetypeId::INVALID) + && (meta.generation == IdentifierMask::inc_masked_high_by(entity.generation, 1))) .and_then(|meta| meta.spawned_or_despawned_by) } /// Constructs a message explaining why an entity does not exists, if known. - pub(crate) fn entity_does_not_exist_error_details_message( + pub(crate) fn entity_does_not_exist_error_details( &self, _entity: Entity, ) -> EntityDoesNotExistDetails { @@ -1014,9 +1020,12 @@ impl fmt::Display for EntityDoesNotExistDetails { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { #[cfg(feature = "track_location")] if let Some(location) = self.location { - write!(f, "was despawned by {}", location) + write!(f, "was despawned by {location}") } else { - write!(f, "was never spawned") + write!( + f, + "does not exist (index has been reused or was never spawned)" + ) } #[cfg(not(feature = "track_location"))] write!( diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 29f0829f44549..2785ea44a049f 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1018,9 +1018,7 @@ impl QueryState { .get(entity) .ok_or(QueryEntityError::NoSuchEntity( entity, - world - .entities() - .entity_does_not_exist_error_details_message(entity), + world.entities().entity_does_not_exist_error_details(entity), ))?; if !self .matched_archetypes diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index 8586596c72368..15dedd8574cb4 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -349,7 +349,7 @@ fn insert_reflect_with_registry_ref( let type_path = type_info.type_path(); let Ok(mut entity) = world.get_entity_mut(entity) else { panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", - world.entities().entity_does_not_exist_error_details_message(entity)); + world.entities().entity_does_not_exist_error_details(entity)); }; let Some(type_registration) = type_registry.get(type_info.type_id()) else { panic!("`{type_path}` should be registered in type registry via `App::register_type<{type_path}>`"); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index ffff2cb0944c2..95a8711520335 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -446,7 +446,7 @@ impl<'w, 's> Commands<'w, 's> { fn panic_no_entity(entities: &Entities, entity: Entity) -> ! { panic!( "Attempting to create an EntityCommands for entity {entity}, which {}", - entities.entity_does_not_exist_error_details_message(entity) + entities.entity_does_not_exist_error_details(entity) ); } diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 05df7dc762380..1d28051fc516e 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -107,8 +107,7 @@ impl<'w> DeferredWorld<'w> { Err(EntityFetchError::NoSuchEntity(..)) => { return Err(EntityFetchError::NoSuchEntity( entity, - self.entities() - .entity_does_not_exist_error_details_message(entity), + self.entities().entity_does_not_exist_error_details(entity), )) } }; diff --git a/crates/bevy_ecs/src/world/entity_fetch.rs b/crates/bevy_ecs/src/world/entity_fetch.rs index 8fbf96777de36..05a5c51aff67e 100644 --- a/crates/bevy_ecs/src/world/entity_fetch.rs +++ b/crates/bevy_ecs/src/world/entity_fetch.rs @@ -124,8 +124,7 @@ unsafe impl WorldEntityFetch for Entity { .get(self) .ok_or(EntityFetchError::NoSuchEntity( self, - cell.entities() - .entity_does_not_exist_error_details_message(self), + cell.entities().entity_does_not_exist_error_details(self), ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. let world = unsafe { cell.world_mut() }; @@ -139,8 +138,7 @@ unsafe impl WorldEntityFetch for Entity { ) -> Result, EntityFetchError> { let ecell = cell.get_entity(self).ok_or(EntityFetchError::NoSuchEntity( self, - cell.entities() - .entity_does_not_exist_error_details_message(self), + cell.entities().entity_does_not_exist_error_details(self), ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. Ok(unsafe { EntityMut::new(ecell) }) @@ -215,8 +213,7 @@ unsafe impl WorldEntityFetch for &'_ [Entity; N] { for (r, &id) in core::iter::zip(&mut refs, self) { let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( id, - cell.entities() - .entity_does_not_exist_error_details_message(id), + cell.entities().entity_does_not_exist_error_details(id), ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. *r = MaybeUninit::new(unsafe { EntityMut::new(ecell) }); @@ -275,8 +272,7 @@ unsafe impl WorldEntityFetch for &'_ [Entity] { for &id in self { let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( id, - cell.entities() - .entity_does_not_exist_error_details_message(id), + cell.entities().entity_does_not_exist_error_details(id), ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. refs.push(unsafe { EntityMut::new(ecell) }); @@ -322,8 +318,7 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet { for &id in self { let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity( id, - cell.entities() - .entity_does_not_exist_error_details_message(id), + cell.entities().entity_does_not_exist_error_details(id), ))?; // SAFETY: caller ensures that the world cell has mutable access to the entity. refs.insert(id, unsafe { EntityMut::new(ecell) }); diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 54ef11875fa64..6b3491e877457 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -1001,7 +1001,7 @@ impl<'w> EntityWorldMut<'w> { self.entity, self.world .entities() - .entity_does_not_exist_error_details_message(self.entity) + .entity_does_not_exist_error_details(self.entity) ); } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 1037969dedca9..92cd9bb4b2649 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -674,9 +674,7 @@ impl World { fn panic_no_entity(world: &World, entity: Entity) -> ! { panic!( "Entity {entity} {}", - world - .entities - .entity_does_not_exist_error_details_message(entity) + world.entities.entity_does_not_exist_error_details(entity) ); } @@ -1245,8 +1243,7 @@ impl World { Err(EntityFetchError::NoSuchEntity(..)) => { return Err(EntityFetchError::NoSuchEntity( entity, - self.entities() - .entity_does_not_exist_error_details_message(entity), + self.entities().entity_does_not_exist_error_details(entity), )) } }; @@ -1309,7 +1306,7 @@ impl World { true } else { if log_warning { - warn!("error[B0003]: {caller}: Could not despawn entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", self.entities.entity_does_not_exist_error_details_message(entity)); + warn!("error[B0003]: {caller}: Could not despawn entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", self.entities.entity_does_not_exist_error_details(entity)); } false } @@ -2468,11 +2465,11 @@ impl World { ) }; } else { - panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), self.entities.entity_does_not_exist_error_details_message(entity)); + panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), self.entities.entity_does_not_exist_error_details(entity)); } } } else { - panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {first_entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), self.entities.entity_does_not_exist_error_details_message(first_entity)); + panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {first_entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::(), self.entities.entity_does_not_exist_error_details(first_entity)); } } } @@ -4321,4 +4318,34 @@ mod tests { .map(|_| {}), Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1)); } + + #[cfg(feature = "track_location")] + #[test] + #[track_caller] + fn entity_spawn_despawn_tracking() { + use core::panic::Location; + + let mut world = World::new(); + let entity = world.spawn_empty().id(); + assert_eq!( + world.entities.entity_get_spawned_or_despawned_by(entity), + Some(Location::caller()) + ); + world.despawn(entity); + assert_eq!( + world.entities.entity_get_spawned_or_despawned_by(entity), + Some(Location::caller()) + ); + let new = world.spawn_empty().id(); + assert_eq!(entity.index(), new.index()); + assert_eq!( + world.entities.entity_get_spawned_or_despawned_by(entity), + None + ); + world.despawn(new); + assert_eq!( + world.entities.entity_get_spawned_or_despawned_by(entity), + None + ); + } }