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
|
/// 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")]
|
#[cfg(feature = "track_location")]
|
||||||
pub fn entity_get_spawned_or_despawned_by(
|
pub fn entity_get_spawned_or_despawned_by(
|
||||||
&self,
|
&self,
|
||||||
@ -987,11 +988,16 @@ impl Entities {
|
|||||||
) -> Option<&'static Location<'static>> {
|
) -> Option<&'static Location<'static>> {
|
||||||
self.meta
|
self.meta
|
||||||
.get(entity.index() as usize)
|
.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)
|
.and_then(|meta| meta.spawned_or_despawned_by)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a message explaining why an entity does not exists, if known.
|
/// 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,
|
&self,
|
||||||
_entity: Entity,
|
_entity: Entity,
|
||||||
) -> EntityDoesNotExistDetails {
|
) -> EntityDoesNotExistDetails {
|
||||||
@ -1014,9 +1020,12 @@ impl fmt::Display for EntityDoesNotExistDetails {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
#[cfg(feature = "track_location")]
|
#[cfg(feature = "track_location")]
|
||||||
if let Some(location) = self.location {
|
if let Some(location) = self.location {
|
||||||
write!(f, "was despawned by {}", location)
|
write!(f, "was despawned by {location}")
|
||||||
} else {
|
} else {
|
||||||
write!(f, "was never spawned")
|
write!(
|
||||||
|
f,
|
||||||
|
"does not exist (index has been reused or was never spawned)"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "track_location"))]
|
#[cfg(not(feature = "track_location"))]
|
||||||
write!(
|
write!(
|
||||||
|
@ -1018,9 +1018,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||||||
.get(entity)
|
.get(entity)
|
||||||
.ok_or(QueryEntityError::NoSuchEntity(
|
.ok_or(QueryEntityError::NoSuchEntity(
|
||||||
entity,
|
entity,
|
||||||
world
|
world.entities().entity_does_not_exist_error_details(entity),
|
||||||
.entities()
|
|
||||||
.entity_does_not_exist_error_details_message(entity),
|
|
||||||
))?;
|
))?;
|
||||||
if !self
|
if !self
|
||||||
.matched_archetypes
|
.matched_archetypes
|
||||||
|
@ -349,7 +349,7 @@ fn insert_reflect_with_registry_ref(
|
|||||||
let type_path = type_info.type_path();
|
let type_path = type_info.type_path();
|
||||||
let Ok(mut entity) = world.get_entity_mut(entity) else {
|
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",
|
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 {
|
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}>`");
|
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) -> ! {
|
fn panic_no_entity(entities: &Entities, entity: Entity) -> ! {
|
||||||
panic!(
|
panic!(
|
||||||
"Attempting to create an EntityCommands for entity {entity}, which {}",
|
"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(..)) => {
|
Err(EntityFetchError::NoSuchEntity(..)) => {
|
||||||
return Err(EntityFetchError::NoSuchEntity(
|
return Err(EntityFetchError::NoSuchEntity(
|
||||||
entity,
|
entity,
|
||||||
self.entities()
|
self.entities().entity_does_not_exist_error_details(entity),
|
||||||
.entity_does_not_exist_error_details_message(entity),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -124,8 +124,7 @@ unsafe impl WorldEntityFetch for Entity {
|
|||||||
.get(self)
|
.get(self)
|
||||||
.ok_or(EntityFetchError::NoSuchEntity(
|
.ok_or(EntityFetchError::NoSuchEntity(
|
||||||
self,
|
self,
|
||||||
cell.entities()
|
cell.entities().entity_does_not_exist_error_details(self),
|
||||||
.entity_does_not_exist_error_details_message(self),
|
|
||||||
))?;
|
))?;
|
||||||
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
||||||
let world = unsafe { cell.world_mut() };
|
let world = unsafe { cell.world_mut() };
|
||||||
@ -139,8 +138,7 @@ unsafe impl WorldEntityFetch for Entity {
|
|||||||
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
|
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
|
||||||
let ecell = cell.get_entity(self).ok_or(EntityFetchError::NoSuchEntity(
|
let ecell = cell.get_entity(self).ok_or(EntityFetchError::NoSuchEntity(
|
||||||
self,
|
self,
|
||||||
cell.entities()
|
cell.entities().entity_does_not_exist_error_details(self),
|
||||||
.entity_does_not_exist_error_details_message(self),
|
|
||||||
))?;
|
))?;
|
||||||
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
||||||
Ok(unsafe { EntityMut::new(ecell) })
|
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) {
|
for (r, &id) in core::iter::zip(&mut refs, self) {
|
||||||
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
||||||
id,
|
id,
|
||||||
cell.entities()
|
cell.entities().entity_does_not_exist_error_details(id),
|
||||||
.entity_does_not_exist_error_details_message(id),
|
|
||||||
))?;
|
))?;
|
||||||
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
||||||
*r = MaybeUninit::new(unsafe { EntityMut::new(ecell) });
|
*r = MaybeUninit::new(unsafe { EntityMut::new(ecell) });
|
||||||
@ -275,8 +272,7 @@ unsafe impl WorldEntityFetch for &'_ [Entity] {
|
|||||||
for &id in self {
|
for &id in self {
|
||||||
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
||||||
id,
|
id,
|
||||||
cell.entities()
|
cell.entities().entity_does_not_exist_error_details(id),
|
||||||
.entity_does_not_exist_error_details_message(id),
|
|
||||||
))?;
|
))?;
|
||||||
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
||||||
refs.push(unsafe { EntityMut::new(ecell) });
|
refs.push(unsafe { EntityMut::new(ecell) });
|
||||||
@ -322,8 +318,7 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet {
|
|||||||
for &id in self {
|
for &id in self {
|
||||||
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
||||||
id,
|
id,
|
||||||
cell.entities()
|
cell.entities().entity_does_not_exist_error_details(id),
|
||||||
.entity_does_not_exist_error_details_message(id),
|
|
||||||
))?;
|
))?;
|
||||||
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
// SAFETY: caller ensures that the world cell has mutable access to the entity.
|
||||||
refs.insert(id, unsafe { EntityMut::new(ecell) });
|
refs.insert(id, unsafe { EntityMut::new(ecell) });
|
||||||
|
@ -1001,7 +1001,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
self.entity,
|
self.entity,
|
||||||
self.world
|
self.world
|
||||||
.entities()
|
.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) -> ! {
|
fn panic_no_entity(world: &World, entity: Entity) -> ! {
|
||||||
panic!(
|
panic!(
|
||||||
"Entity {entity} {}",
|
"Entity {entity} {}",
|
||||||
world
|
world.entities.entity_does_not_exist_error_details(entity)
|
||||||
.entities
|
|
||||||
.entity_does_not_exist_error_details_message(entity)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1245,8 +1243,7 @@ impl World {
|
|||||||
Err(EntityFetchError::NoSuchEntity(..)) => {
|
Err(EntityFetchError::NoSuchEntity(..)) => {
|
||||||
return Err(EntityFetchError::NoSuchEntity(
|
return Err(EntityFetchError::NoSuchEntity(
|
||||||
entity,
|
entity,
|
||||||
self.entities()
|
self.entities().entity_does_not_exist_error_details(entity),
|
||||||
.entity_does_not_exist_error_details_message(entity),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1309,7 +1306,7 @@ impl World {
|
|||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
if log_warning {
|
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
|
false
|
||||||
}
|
}
|
||||||
@ -2468,11 +2465,11 @@ impl World {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
} else {
|
} 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 {
|
} 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(|_| {}),
|
.map(|_| {}),
|
||||||
Err(EntityFetchError::NoSuchEntity(e, ..)) if e == e1));
|
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