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:
parent
02bb151889
commit
f5d38f30cc
@ -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!(
|
||||
|
@ -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
|
||||
|
@ -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}>`");
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
@ -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) });
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user