Optimize Entities::entity_does_not_exist_error_details_message
, remove UnsafeWorldCell
from error (#17115)
## Objective The error `EntityFetchError::NoSuchEntity` has an `UnsafeWorldCell` inside it, which it uses to call `Entities::entity_does_not_exist_error_details_message` when being printed. That method returns a `String` that, if the `track_location` feature is enabled, contains the location of whoever despawned the relevant entity. I initially had to modify this error while working on #17043. The `UnsafeWorldCell` was causing borrow problems when being returned from a command, so I tried replacing it with the `String` that the method returns, since that was the world cell's only purpose. Unfortunately, `String`s are slow, and it significantly impacted performance (on top of that PR's performance hit): <details> <summary>17043 benchmarks</summary> ### With `String`  ### No `String`  </details> For that PR, I just removed the error details entirely, but I figured I'd try to find a way to keep them around. ## Solution - Replace the `String` with a helper struct that holds the location, and only turn it into a string when someone actually wants to print it. - Replace the `UnsafeWorldCell` with the aforementioned struct. - Do the same for `QueryEntityError::NoSuchEntity`. ## Benchmarking This had some interesting performance impact: <details> <summary>This PR vs main</summary>    </details> ## Other work `QueryEntityError::QueryDoesNotMatch` also has an `UnsafeWorldCell` inside it. This one would be more complicated to rework while keeping the same functionality. ## Migration Guide The errors `EntityFetchError::NoSuchEntity` and `QueryEntityError::NoSuchEntity` now contain an `EntityDoesNotExistDetails` struct instead of an `UnsafeWorldCell`. If you were just printing these, they should work identically. --------- Co-authored-by: Benjamin Brienen <benjamin.brienen@outlook.com>
This commit is contained in:
parent
859c2d77f9
commit
4fde223831
@ -69,12 +69,12 @@ use crate::{
|
|||||||
},
|
},
|
||||||
storage::{SparseSetIndex, TableId, TableRow},
|
storage::{SparseSetIndex, TableId, TableRow},
|
||||||
};
|
};
|
||||||
use alloc::{borrow::ToOwned, string::String, vec::Vec};
|
use alloc::vec::Vec;
|
||||||
use core::{fmt, hash::Hash, mem, num::NonZero};
|
use core::{fmt, hash::Hash, mem, num::NonZero};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
#[cfg(feature = "track_location")]
|
#[cfg(feature = "track_location")]
|
||||||
use {alloc::format, core::panic::Location};
|
use core::panic::Location;
|
||||||
|
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -991,19 +991,38 @@ impl Entities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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(&self, _entity: Entity) -> String {
|
pub(crate) fn entity_does_not_exist_error_details_message(
|
||||||
|
&self,
|
||||||
|
_entity: Entity,
|
||||||
|
) -> EntityDoesNotExistDetails {
|
||||||
|
EntityDoesNotExistDetails {
|
||||||
|
#[cfg(feature = "track_location")]
|
||||||
|
location: self.entity_get_spawned_or_despawned_by(_entity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper struct that, when printed, will write the appropriate details
|
||||||
|
/// regarding an entity that did not exist.
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct EntityDoesNotExistDetails {
|
||||||
|
#[cfg(feature = "track_location")]
|
||||||
|
location: Option<&'static Location<'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for EntityDoesNotExistDetails {
|
||||||
|
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.entity_get_spawned_or_despawned_by(_entity) {
|
write!(f, "was despawned by {}", location)
|
||||||
format!("was despawned by {location}",)
|
} else {
|
||||||
} else {
|
write!(f, "was never spawned")
|
||||||
"was never spawned".to_owned()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "track_location"))]
|
#[cfg(not(feature = "track_location"))]
|
||||||
{
|
write!(
|
||||||
"does not exist (enable `track_location` feature for more details)".to_owned()
|
f,
|
||||||
}
|
"does not exist (enable `track_location` feature for more details)"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{entity::Entity, world::unsafe_world_cell::UnsafeWorldCell};
|
use crate::{
|
||||||
|
entity::{Entity, EntityDoesNotExistDetails},
|
||||||
|
world::unsafe_world_cell::UnsafeWorldCell,
|
||||||
|
};
|
||||||
|
|
||||||
/// An error that occurs when retrieving a specific [`Entity`]'s query result from [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState).
|
/// An error that occurs when retrieving a specific [`Entity`]'s query result from [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState).
|
||||||
// TODO: return the type_name as part of this error
|
// TODO: return the type_name as part of this error
|
||||||
@ -11,7 +14,7 @@ pub enum QueryEntityError<'w> {
|
|||||||
/// Either it does not have a requested component, or it has a component which the query filters out.
|
/// Either it does not have a requested component, or it has a component which the query filters out.
|
||||||
QueryDoesNotMatch(Entity, UnsafeWorldCell<'w>),
|
QueryDoesNotMatch(Entity, UnsafeWorldCell<'w>),
|
||||||
/// The given [`Entity`] does not exist.
|
/// The given [`Entity`] does not exist.
|
||||||
NoSuchEntity(Entity, UnsafeWorldCell<'w>),
|
NoSuchEntity(Entity, EntityDoesNotExistDetails),
|
||||||
/// The [`Entity`] was requested mutably more than once.
|
/// The [`Entity`] was requested mutably more than once.
|
||||||
///
|
///
|
||||||
/// See [`QueryState::get_many_mut`](crate::query::QueryState::get_many_mut) for an example.
|
/// See [`QueryState::get_many_mut`](crate::query::QueryState::get_many_mut) for an example.
|
||||||
@ -30,17 +33,14 @@ impl<'w> core::fmt::Display for QueryEntityError<'w> {
|
|||||||
)?;
|
)?;
|
||||||
format_archetype(f, world, entity)
|
format_archetype(f, world, entity)
|
||||||
}
|
}
|
||||||
Self::NoSuchEntity(entity, world) => {
|
Self::NoSuchEntity(entity, details) => {
|
||||||
write!(
|
write!(f, "The entity with ID {entity} {details}")
|
||||||
f,
|
|
||||||
"Entity {entity} {}",
|
|
||||||
world
|
|
||||||
.entities()
|
|
||||||
.entity_does_not_exist_error_details_message(entity)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Self::AliasedMutability(entity) => {
|
Self::AliasedMutability(entity) => {
|
||||||
write!(f, "Entity {entity} was requested mutably more than once")
|
write!(
|
||||||
|
f,
|
||||||
|
"The entity with ID {entity} was requested mutably more than once"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,14 +54,8 @@ impl<'w> core::fmt::Debug for QueryEntityError<'w> {
|
|||||||
format_archetype(f, world, entity)?;
|
format_archetype(f, world, entity)?;
|
||||||
write!(f, ")")
|
write!(f, ")")
|
||||||
}
|
}
|
||||||
Self::NoSuchEntity(entity, world) => {
|
Self::NoSuchEntity(entity, details) => {
|
||||||
write!(
|
write!(f, "NoSuchEntity({entity} {details})")
|
||||||
f,
|
|
||||||
"NoSuchEntity({entity} {})",
|
|
||||||
world
|
|
||||||
.entities()
|
|
||||||
.entity_does_not_exist_error_details_message(entity)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"),
|
Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"),
|
||||||
}
|
}
|
||||||
|
@ -1020,7 +1020,12 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||||||
let location = world
|
let location = world
|
||||||
.entities()
|
.entities()
|
||||||
.get(entity)
|
.get(entity)
|
||||||
.ok_or(QueryEntityError::NoSuchEntity(entity, world))?;
|
.ok_or(QueryEntityError::NoSuchEntity(
|
||||||
|
entity,
|
||||||
|
world
|
||||||
|
.entities()
|
||||||
|
.entity_does_not_exist_error_details_message(entity),
|
||||||
|
))?;
|
||||||
if !self
|
if !self
|
||||||
.matched_archetypes
|
.matched_archetypes
|
||||||
.contains(location.archetype_id.index())
|
.contains(location.archetype_id.index())
|
||||||
|
@ -109,7 +109,11 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
return Err(EntityFetchError::AliasedMutability(entity))
|
return Err(EntityFetchError::AliasedMutability(entity))
|
||||||
}
|
}
|
||||||
Err(EntityFetchError::NoSuchEntity(..)) => {
|
Err(EntityFetchError::NoSuchEntity(..)) => {
|
||||||
return Err(EntityFetchError::NoSuchEntity(entity, self.world))
|
return Err(EntityFetchError::NoSuchEntity(
|
||||||
|
entity,
|
||||||
|
self.entities()
|
||||||
|
.entity_does_not_exist_error_details_message(entity),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -124,7 +124,11 @@ unsafe impl WorldEntityFetch for Entity {
|
|||||||
let location = cell
|
let location = cell
|
||||||
.entities()
|
.entities()
|
||||||
.get(self)
|
.get(self)
|
||||||
.ok_or(EntityFetchError::NoSuchEntity(self, cell))?;
|
.ok_or(EntityFetchError::NoSuchEntity(
|
||||||
|
self,
|
||||||
|
cell.entities()
|
||||||
|
.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() };
|
||||||
// SAFETY: location was fetched from the same world's `Entities`.
|
// SAFETY: location was fetched from the same world's `Entities`.
|
||||||
@ -135,9 +139,11 @@ unsafe impl WorldEntityFetch for Entity {
|
|||||||
self,
|
self,
|
||||||
cell: UnsafeWorldCell<'_>,
|
cell: UnsafeWorldCell<'_>,
|
||||||
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
|
) -> Result<Self::DeferredMut<'_>, EntityFetchError> {
|
||||||
let ecell = cell
|
let ecell = cell.get_entity(self).ok_or(EntityFetchError::NoSuchEntity(
|
||||||
.get_entity(self)
|
self,
|
||||||
.ok_or(EntityFetchError::NoSuchEntity(self, cell))?;
|
cell.entities()
|
||||||
|
.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) })
|
||||||
}
|
}
|
||||||
@ -209,9 +215,11 @@ unsafe impl<const N: usize> WorldEntityFetch for &'_ [Entity; N] {
|
|||||||
|
|
||||||
let mut refs = [const { MaybeUninit::uninit() }; N];
|
let mut refs = [const { MaybeUninit::uninit() }; N];
|
||||||
for (r, &id) in core::iter::zip(&mut refs, self) {
|
for (r, &id) in core::iter::zip(&mut refs, self) {
|
||||||
let ecell = cell
|
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
||||||
.get_entity(id)
|
id,
|
||||||
.ok_or(EntityFetchError::NoSuchEntity(id, cell))?;
|
cell.entities()
|
||||||
|
.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) });
|
||||||
}
|
}
|
||||||
@ -267,9 +275,11 @@ unsafe impl WorldEntityFetch for &'_ [Entity] {
|
|||||||
|
|
||||||
let mut refs = Vec::with_capacity(self.len());
|
let mut refs = Vec::with_capacity(self.len());
|
||||||
for &id in self {
|
for &id in self {
|
||||||
let ecell = cell
|
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
||||||
.get_entity(id)
|
id,
|
||||||
.ok_or(EntityFetchError::NoSuchEntity(id, cell))?;
|
cell.entities()
|
||||||
|
.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) });
|
||||||
}
|
}
|
||||||
@ -312,9 +322,11 @@ unsafe impl WorldEntityFetch for &'_ EntityHashSet {
|
|||||||
) -> Result<Self::Mut<'_>, EntityFetchError> {
|
) -> Result<Self::Mut<'_>, EntityFetchError> {
|
||||||
let mut refs = EntityHashMap::with_capacity(self.len());
|
let mut refs = EntityHashMap::with_capacity(self.len());
|
||||||
for &id in self {
|
for &id in self {
|
||||||
let ecell = cell
|
let ecell = cell.get_entity(id).ok_or(EntityFetchError::NoSuchEntity(
|
||||||
.get_entity(id)
|
id,
|
||||||
.ok_or(EntityFetchError::NoSuchEntity(id, cell))?;
|
cell.entities()
|
||||||
|
.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) });
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{component::ComponentId, entity::Entity, schedule::InternedScheduleLabel};
|
use crate::{
|
||||||
|
component::ComponentId,
|
||||||
use super::unsafe_world_cell::UnsafeWorldCell;
|
entity::{Entity, EntityDoesNotExistDetails},
|
||||||
|
schedule::InternedScheduleLabel,
|
||||||
|
};
|
||||||
|
|
||||||
/// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist.
|
/// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist.
|
||||||
///
|
///
|
||||||
@ -25,53 +27,17 @@ pub enum EntityComponentError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An error that occurs when fetching entities mutably from a world.
|
/// An error that occurs when fetching entities mutably from a world.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Error, Debug, Clone, Copy)]
|
||||||
pub enum EntityFetchError<'w> {
|
pub enum EntityFetchError {
|
||||||
/// The entity with the given ID does not exist.
|
/// The entity with the given ID does not exist.
|
||||||
NoSuchEntity(Entity, UnsafeWorldCell<'w>),
|
#[error("The entity with ID {0} {1}")]
|
||||||
|
NoSuchEntity(Entity, EntityDoesNotExistDetails),
|
||||||
/// The entity with the given ID was requested mutably more than once.
|
/// The entity with the given ID was requested mutably more than once.
|
||||||
|
#[error("The entity with ID {0} was requested mutably more than once")]
|
||||||
AliasedMutability(Entity),
|
AliasedMutability(Entity),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w> core::error::Error for EntityFetchError<'w> {}
|
impl PartialEq for EntityFetchError {
|
||||||
|
|
||||||
impl<'w> core::fmt::Display for EntityFetchError<'w> {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Self::NoSuchEntity(entity, world) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Entity {entity} {}",
|
|
||||||
world
|
|
||||||
.entities()
|
|
||||||
.entity_does_not_exist_error_details_message(entity)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::AliasedMutability(entity) => {
|
|
||||||
write!(f, "Entity {entity} was requested mutably more than once")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'w> core::fmt::Debug for EntityFetchError<'w> {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Self::NoSuchEntity(entity, world) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"NoSuchEntity({entity} {})",
|
|
||||||
world
|
|
||||||
.entities()
|
|
||||||
.entity_does_not_exist_error_details_message(entity)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'w> PartialEq for EntityFetchError<'w> {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Self::NoSuchEntity(e1, _), Self::NoSuchEntity(e2, _)) if e1 == e2 => true,
|
(Self::NoSuchEntity(e1, _), Self::NoSuchEntity(e2, _)) if e1 == e2 => true,
|
||||||
@ -81,4 +47,4 @@ impl<'w> PartialEq for EntityFetchError<'w> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w> Eq for EntityFetchError<'w> {}
|
impl Eq for EntityFetchError {}
|
||||||
|
@ -1327,7 +1327,11 @@ impl World {
|
|||||||
return Err(EntityFetchError::AliasedMutability(entity))
|
return Err(EntityFetchError::AliasedMutability(entity))
|
||||||
}
|
}
|
||||||
Err(EntityFetchError::NoSuchEntity(..)) => {
|
Err(EntityFetchError::NoSuchEntity(..)) => {
|
||||||
return Err(EntityFetchError::NoSuchEntity(entity, self.into()))
|
return Err(EntityFetchError::NoSuchEntity(
|
||||||
|
entity,
|
||||||
|
self.entities()
|
||||||
|
.entity_does_not_exist_error_details_message(entity),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user