Fix entity does not exist message on index reuse (#17264)

# Objective

With the `track_location` feature, the error message of trying to
acquire an entity that was despawned pointed to the wrong line if the
entity index has been reused.

## Showcase

```rust
use bevy_ecs::prelude::*;

fn main() {
    let mut world = World::new();
    let e = world.spawn_empty().id();
    world.despawn(e);
    world.flush();
    let _ = world.spawn_empty();
    world.entity(e);
}
```
Old message:
```
Entity 0v1 was despawned by src/main.rs:8:19
```
New message:
```
Entity 0v1 does not exist (its index has been reused)
```
This commit is contained in:
SpecificProtagonist 2025-01-12 06:39:54 +01:00 committed by GitHub
parent 02bb151889
commit f5d38f30cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 58 additions and 30 deletions

View File

@ -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!(

View File

@ -1018,9 +1018,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
.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

View File

@ -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}>`");

View File

@ -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)
);
}

View File

@ -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),
))
}
};

View File

@ -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<Self::DeferredMut<'_>, 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<const N: usize> 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) });

View File

@ -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)
);
}

View File

@ -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::<B>(), 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::<B>(), 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::<B>(), 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::<B>(), 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
);
}
}