Remove lifetime from QueryEntityError (#18157)

# Objective

- Allow `Query` methods such as `Query::get` to have their error
short-circuited using `?` in systems using Bevy's `Error` type

## Solution

- Removed `UnsafeWorldCell<'w>` from `QueryEntityError` and instead
store `ArchetypeId` (the information the error formatter was extracting
anyway).
- Replaced trait implementations with derives now that the type is
plain-old-data.

## Testing

- CI

---

## Migration Guide

- `QueryEntityError::QueryDoesNotMatch.1` is of type `ArchetypeId`
instead of `UnsafeWorldCell`. It is up to the caller to obtain an
`UnsafeWorldCell` now.
- `QueryEntityError` no longer has a lifetime parameter, remove it from
type signatures where required.

## Notes

This was discussed on Discord and accepted by Cart as the desirable path
forward in [this
message](https://discord.com/channels/691052431525675048/749335865876021248/1346611950527713310).
Scroll up from this point for context.

---------

Co-authored-by: SpecificProtagonist <30270112+SpecificProtagonist@users.noreply.github.com>
This commit is contained in:
Zachary Harrold 2025-03-10 07:05:22 +11:00 committed by GitHub
parent f22d93c90f
commit cbc931723e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 35 additions and 86 deletions

View File

@ -1,18 +1,18 @@
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
archetype::ArchetypeId,
entity::{Entity, EntityDoesNotExistError}, entity::{Entity, EntityDoesNotExistError},
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
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum QueryEntityError<'w> { pub enum QueryEntityError {
/// The given [`Entity`]'s components do not match the query. /// The given [`Entity`]'s components do not match the query.
/// ///
/// 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, ArchetypeId),
/// The given [`Entity`] does not exist. /// The given [`Entity`] does not exist.
EntityDoesNotExist(EntityDoesNotExistError), EntityDoesNotExist(EntityDoesNotExistError),
/// The [`Entity`] was requested mutably more than once. /// The [`Entity`] was requested mutably more than once.
@ -21,23 +21,19 @@ pub enum QueryEntityError<'w> {
AliasedMutability(Entity), AliasedMutability(Entity),
} }
impl<'w> From<EntityDoesNotExistError> for QueryEntityError<'w> { impl From<EntityDoesNotExistError> for QueryEntityError {
fn from(error: EntityDoesNotExistError) -> Self { fn from(error: EntityDoesNotExistError) -> Self {
QueryEntityError::EntityDoesNotExist(error) QueryEntityError::EntityDoesNotExist(error)
} }
} }
impl<'w> core::error::Error for QueryEntityError<'w> {} impl core::error::Error for QueryEntityError {}
impl<'w> core::fmt::Display for QueryEntityError<'w> { impl core::fmt::Display for QueryEntityError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self { match *self {
Self::QueryDoesNotMatch(entity, world) => { Self::QueryDoesNotMatch(entity, _) => {
write!( write!(f, "The query does not match entity {entity}")
f,
"The query does not match entity {entity}, which has components "
)?;
format_archetype(f, world, entity)
} }
Self::EntityDoesNotExist(error) => { Self::EntityDoesNotExist(error) => {
write!(f, "{error}") write!(f, "{error}")
@ -52,57 +48,6 @@ impl<'w> core::fmt::Display for QueryEntityError<'w> {
} }
} }
impl<'w> core::fmt::Debug for QueryEntityError<'w> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
Self::QueryDoesNotMatch(entity, world) => {
write!(f, "QueryDoesNotMatch({entity} with components ")?;
format_archetype(f, world, entity)?;
write!(f, ")")
}
Self::EntityDoesNotExist(error) => {
write!(f, "EntityDoesNotExist({error})")
}
Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"),
}
}
}
fn format_archetype(
f: &mut core::fmt::Formatter<'_>,
world: UnsafeWorldCell<'_>,
entity: Entity,
) -> core::fmt::Result {
// We know entity is still alive
let entity = world
.get_entity(entity)
.expect("entity does not belong to world");
for (i, component_id) in entity.archetype().components().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
let name = world
.components()
.get_name(component_id)
.expect("entity does not belong to world");
write!(f, "{}", disqualified::ShortName(name))?;
}
Ok(())
}
impl<'w> PartialEq for QueryEntityError<'w> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::QueryDoesNotMatch(e1, _), Self::QueryDoesNotMatch(e2, _)) if e1 == e2 => true,
(Self::EntityDoesNotExist(e1), Self::EntityDoesNotExist(e2)) if e1 == e2 => true,
(Self::AliasedMutability(e1), Self::AliasedMutability(e2)) if e1 == e2 => true,
_ => false,
}
}
}
impl<'w> Eq for QueryEntityError<'w> {}
/// An error that occurs when evaluating a [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState) as a single expected result via /// An error that occurs when evaluating a [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState) as a single expected result via
/// [`single`](crate::system::Query::single) or [`single_mut`](crate::system::Query::single_mut). /// [`single`](crate::system::Query::single) or [`single_mut`](crate::system::Query::single_mut).
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -117,8 +62,7 @@ pub enum QuerySingleError {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::prelude::World; use crate::{prelude::World, query::QueryEntityError};
use alloc::format;
use bevy_ecs_macros::Component; use bevy_ecs_macros::Component;
#[test] #[test]
@ -129,19 +73,18 @@ mod test {
struct Present1; struct Present1;
#[derive(Component)] #[derive(Component)]
struct Present2; struct Present2;
#[derive(Component, Debug)] #[derive(Component, Debug, PartialEq)]
struct NotPresent; struct NotPresent;
let entity = world.spawn((Present1, Present2)).id(); let entity = world.spawn((Present1, Present2));
let err = world let (entity, archetype_id) = (entity.id(), entity.archetype().id());
.query::<&NotPresent>()
.get(&world, entity) let result = world.query::<&NotPresent>().get(&world, entity);
.unwrap_err();
assert_eq!( assert_eq!(
format!("{err:?}"), result,
"QueryDoesNotMatch(0v1 with components Present1, Present2)" Err(QueryEntityError::QueryDoesNotMatch(entity, archetype_id))
); );
} }
} }

View File

@ -952,7 +952,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self, &mut self,
world: &'w World, world: &'w World,
entity: Entity, entity: Entity,
) -> Result<ROQueryItem<'w, D>, QueryEntityError<'w>> { ) -> Result<ROQueryItem<'w, D>, QueryEntityError> {
self.query(world).get_inner(entity) self.query(world).get_inner(entity)
} }
@ -993,7 +993,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self, &mut self,
world: &'w World, world: &'w World,
entities: [Entity; N], entities: [Entity; N],
) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError<'w>> { ) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError> {
self.query(world).get_many_inner(entities) self.query(world).get_many_inner(entities)
} }
@ -1005,7 +1005,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self, &mut self,
world: &'w mut World, world: &'w mut World,
entity: Entity, entity: Entity,
) -> Result<D::Item<'w>, QueryEntityError<'w>> { ) -> Result<D::Item<'w>, QueryEntityError> {
self.query_mut(world).get_inner(entity) self.query_mut(world).get_inner(entity)
} }
@ -1052,7 +1052,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self, &mut self,
world: &'w mut World, world: &'w mut World,
entities: [Entity; N], entities: [Entity; N],
) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> { ) -> Result<[D::Item<'w>; N], QueryEntityError> {
self.query_mut(world).get_many_inner(entities) self.query_mut(world).get_many_inner(entities)
} }
@ -1074,7 +1074,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&self, &self,
world: &'w World, world: &'w World,
entity: Entity, entity: Entity,
) -> Result<ROQueryItem<'w, D>, QueryEntityError<'w>> { ) -> Result<ROQueryItem<'w, D>, QueryEntityError> {
self.query_manual(world).get_inner(entity) self.query_manual(world).get_inner(entity)
} }
@ -1091,7 +1091,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self, &mut self,
world: UnsafeWorldCell<'w>, world: UnsafeWorldCell<'w>,
entity: Entity, entity: Entity,
) -> Result<D::Item<'w>, QueryEntityError<'w>> { ) -> Result<D::Item<'w>, QueryEntityError> {
self.query_unchecked(world).get_inner(entity) self.query_unchecked(world).get_inner(entity)
} }

View File

@ -1430,7 +1430,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// ///
/// - [`get_mut`](Self::get_mut) to get the item using a mutable borrow of the [`Query`]. /// - [`get_mut`](Self::get_mut) to get the item using a mutable borrow of the [`Query`].
#[inline] #[inline]
pub fn get_inner(self, entity: Entity) -> Result<D::Item<'w>, QueryEntityError<'w>> { pub fn get_inner(self, entity: Entity) -> Result<D::Item<'w>, QueryEntityError> {
// SAFETY: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
@ -1444,7 +1444,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
.matched_archetypes .matched_archetypes
.contains(location.archetype_id.index()) .contains(location.archetype_id.index())
{ {
return Err(QueryEntityError::QueryDoesNotMatch(entity, self.world)); return Err(QueryEntityError::QueryDoesNotMatch(
entity,
location.archetype_id,
));
} }
let archetype = self let archetype = self
.world .world
@ -1476,7 +1479,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
if F::filter_fetch(&mut filter, entity, location.table_row) { if F::filter_fetch(&mut filter, entity, location.table_row) {
Ok(D::fetch(&mut fetch, entity, location.table_row)) Ok(D::fetch(&mut fetch, entity, location.table_row))
} else { } else {
Err(QueryEntityError::QueryDoesNotMatch(entity, self.world)) Err(QueryEntityError::QueryDoesNotMatch(
entity,
location.archetype_id,
))
} }
} }
} }
@ -1573,7 +1579,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
pub fn get_many_inner<const N: usize>( pub fn get_many_inner<const N: usize>(
self, self,
entities: [Entity; N], entities: [Entity; N],
) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> { ) -> Result<[D::Item<'w>; N], QueryEntityError> {
// Verify that all entities are unique // Verify that all entities are unique
for i in 0..N { for i in 0..N {
for j in 0..i { for j in 0..i {
@ -1602,7 +1608,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
pub fn get_many_readonly<const N: usize>( pub fn get_many_readonly<const N: usize>(
self, self,
entities: [Entity; N], entities: [Entity; N],
) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> ) -> Result<[D::Item<'w>; N], QueryEntityError>
where where
D: ReadOnlyQueryData, D: ReadOnlyQueryData,
{ {
@ -1620,7 +1626,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
unsafe fn get_many_impl<const N: usize>( unsafe fn get_many_impl<const N: usize>(
self, self,
entities: [Entity; N], entities: [Entity; N],
) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> { ) -> Result<[D::Item<'w>; N], QueryEntityError> {
let mut values = [(); N].map(|_| MaybeUninit::uninit()); let mut values = [(); N].map(|_| MaybeUninit::uninit());
for (value, entity) in core::iter::zip(&mut values, entities) { for (value, entity) in core::iter::zip(&mut values, entities) {