List components for QueryEntityError::QueryDoesNotMatch (#15435)
# Objective Make it easier to debug why an entity doesn't match a query. ## Solution List the entities components in `QueryEntityError::QueryDoesNotMatch`'s message, e.g. `The query does not match the entity 0v1, which has components foo::Bar, foo::Baz`. This covers most cases as expected components are typically known and filtering for change detection is rare when assessing a query by entity id. ## Testing Added a test confirming the new message matches the entity's components. ## Migration Guide - `QueryEntityError` now has a lifetime. Convert it to a custom error if you need to store it. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: poopy <gonesbird@gmail.com>
This commit is contained in:
parent
9a0bfc6f14
commit
5e6b141c13
@ -1,26 +1,93 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::entity::Entity;
|
use crate::{entity::Entity, 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(Debug, PartialEq, Eq, Clone, Copy, Error)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum QueryEntityError {
|
pub enum QueryEntityError<'w> {
|
||||||
/// 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.
|
||||||
#[error("The components of entity {0:?} do not match the query")]
|
QueryDoesNotMatch(Entity, UnsafeWorldCell<'w>),
|
||||||
QueryDoesNotMatch(Entity),
|
|
||||||
/// The given [`Entity`] does not exist.
|
/// The given [`Entity`] does not exist.
|
||||||
#[error("The entity {0:?} does not exist")]
|
|
||||||
NoSuchEntity(Entity),
|
NoSuchEntity(Entity),
|
||||||
/// 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.
|
||||||
#[error("The entity {0:?} was requested mutably more than once")]
|
|
||||||
AliasedMutability(Entity),
|
AliasedMutability(Entity),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'w> std::error::Error for QueryEntityError<'w> {}
|
||||||
|
|
||||||
|
impl<'w> std::fmt::Display for QueryEntityError<'w> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Self::QueryDoesNotMatch(entity, world) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"The query does not match the entity {entity}, which has components "
|
||||||
|
)?;
|
||||||
|
format_archetype(f, world, entity)
|
||||||
|
}
|
||||||
|
Self::NoSuchEntity(entity) => write!(f, "The entity {entity} does not exist"),
|
||||||
|
Self::AliasedMutability(entity) => write!(
|
||||||
|
f,
|
||||||
|
"The entity {entity} was requested mutably more than once"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w> std::fmt::Debug for QueryEntityError<'w> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Self::QueryDoesNotMatch(entity, world) => {
|
||||||
|
write!(f, "QueryDoesNotMatch({entity} with components ")?;
|
||||||
|
format_archetype(f, world, entity)?;
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
|
Self::NoSuchEntity(entity) => write!(f, "NoSuchEntity({entity})"),
|
||||||
|
Self::AliasedMutability(entity) => write!(f, "AliasedMutability({entity})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_archetype(
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
world: UnsafeWorldCell<'_>,
|
||||||
|
entity: Entity,
|
||||||
|
) -> std::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, "{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::NoSuchEntity(e1), Self::NoSuchEntity(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
|
||||||
/// [`get_single`](crate::system::Query::get_single) or [`get_single_mut`](crate::system::Query::get_single_mut).
|
/// [`get_single`](crate::system::Query::get_single) or [`get_single_mut`](crate::system::Query::get_single_mut).
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@ -32,3 +99,31 @@ pub enum QuerySingleError {
|
|||||||
#[error("Multiple entities fit the query {0}")]
|
#[error("Multiple entities fit the query {0}")]
|
||||||
MultipleEntities(&'static str),
|
MultipleEntities(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate as bevy_ecs;
|
||||||
|
use crate::prelude::World;
|
||||||
|
use bevy_ecs_macros::Component;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn query_does_not_match() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Present1;
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Present2;
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
struct NotPresent;
|
||||||
|
|
||||||
|
let entity = world.spawn((Present1, Present2)).id();
|
||||||
|
|
||||||
|
let err = world
|
||||||
|
.query::<&NotPresent>()
|
||||||
|
.get(&world, entity)
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(format!("{err:?}"), "QueryDoesNotMatch(0v1 with components bevy_ecs::query::error::test::query_does_not_match::Present1, bevy_ecs::query::error::test::query_does_not_match::Present2)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -744,7 +744,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> {
|
) -> Result<ROQueryItem<'w, D>, QueryEntityError<'w>> {
|
||||||
self.update_archetypes(world);
|
self.update_archetypes(world);
|
||||||
// SAFETY: query is read only
|
// SAFETY: query is read only
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -794,7 +794,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> {
|
) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError<'w>> {
|
||||||
self.update_archetypes(world);
|
self.update_archetypes(world);
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
@ -818,7 +818,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> {
|
) -> Result<D::Item<'w>, QueryEntityError<'w>> {
|
||||||
self.update_archetypes(world);
|
self.update_archetypes(world);
|
||||||
let change_tick = world.change_tick();
|
let change_tick = world.change_tick();
|
||||||
let last_change_tick = world.last_change_tick();
|
let last_change_tick = world.last_change_tick();
|
||||||
@ -868,7 +868,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||||||
/// let invalid_entity = world.spawn_empty().id();
|
/// let invalid_entity = world.spawn_empty().id();
|
||||||
///
|
///
|
||||||
/// assert_eq!(query_state.get_many_mut(&mut world, [wrong_entity]).unwrap_err(), QueryEntityError::NoSuchEntity(wrong_entity));
|
/// assert_eq!(query_state.get_many_mut(&mut world, [wrong_entity]).unwrap_err(), QueryEntityError::NoSuchEntity(wrong_entity));
|
||||||
/// assert_eq!(query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err(), QueryEntityError::QueryDoesNotMatch(invalid_entity));
|
/// assert_eq!(match query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity);
|
||||||
/// assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0]));
|
/// assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0]));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -876,7 +876,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> {
|
) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> {
|
||||||
self.update_archetypes(world);
|
self.update_archetypes(world);
|
||||||
|
|
||||||
let change_tick = world.change_tick();
|
let change_tick = world.change_tick();
|
||||||
@ -911,7 +911,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> {
|
) -> Result<ROQueryItem<'w, D>, QueryEntityError<'w>> {
|
||||||
self.validate_world(world.id());
|
self.validate_world(world.id());
|
||||||
// SAFETY: query is read only and world is validated
|
// SAFETY: query is read only and world is validated
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -937,7 +937,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> {
|
) -> Result<D::Item<'w>, QueryEntityError<'w>> {
|
||||||
self.update_archetypes_unsafe_world_cell(world);
|
self.update_archetypes_unsafe_world_cell(world);
|
||||||
self.get_unchecked_manual(world, entity, world.last_change_tick(), world.change_tick())
|
self.get_unchecked_manual(world, entity, world.last_change_tick(), world.change_tick())
|
||||||
}
|
}
|
||||||
@ -960,7 +960,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||||||
entity: Entity,
|
entity: Entity,
|
||||||
last_run: Tick,
|
last_run: Tick,
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
) -> Result<D::Item<'w>, QueryEntityError> {
|
) -> Result<D::Item<'w>, QueryEntityError<'w>> {
|
||||||
let location = world
|
let location = world
|
||||||
.entities()
|
.entities()
|
||||||
.get(entity)
|
.get(entity)
|
||||||
@ -969,7 +969,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||||||
.matched_archetypes
|
.matched_archetypes
|
||||||
.contains(location.archetype_id.index())
|
.contains(location.archetype_id.index())
|
||||||
{
|
{
|
||||||
return Err(QueryEntityError::QueryDoesNotMatch(entity));
|
return Err(QueryEntityError::QueryDoesNotMatch(entity, world));
|
||||||
}
|
}
|
||||||
let archetype = world
|
let archetype = world
|
||||||
.archetypes()
|
.archetypes()
|
||||||
@ -989,7 +989,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<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))
|
Err(QueryEntityError::QueryDoesNotMatch(entity, world))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,7 +1008,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||||||
entities: [Entity; N],
|
entities: [Entity; N],
|
||||||
last_run: Tick,
|
last_run: Tick,
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError> {
|
) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError<'w>> {
|
||||||
let mut values = [(); N].map(|_| MaybeUninit::uninit());
|
let mut values = [(); N].map(|_| MaybeUninit::uninit());
|
||||||
|
|
||||||
for (value, entity) in std::iter::zip(&mut values, entities) {
|
for (value, entity) in std::iter::zip(&mut values, entities) {
|
||||||
@ -1042,7 +1042,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||||||
entities: [Entity; N],
|
entities: [Entity; N],
|
||||||
last_run: Tick,
|
last_run: Tick,
|
||||||
this_run: Tick,
|
this_run: Tick,
|
||||||
) -> Result<[D::Item<'w>; N], QueryEntityError> {
|
) -> Result<[D::Item<'w>; N], QueryEntityError<'w>> {
|
||||||
// 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 {
|
||||||
@ -1442,7 +1442,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
|
|||||||
/// # let invalid_entity = world.spawn_empty().id();
|
/// # let invalid_entity = world.spawn_empty().id();
|
||||||
///
|
///
|
||||||
/// # assert_eq!(query_state.get_many_mut(&mut world, [wrong_entity]).unwrap_err(), QueryEntityError::NoSuchEntity(wrong_entity));
|
/// # assert_eq!(query_state.get_many_mut(&mut world, [wrong_entity]).unwrap_err(), QueryEntityError::NoSuchEntity(wrong_entity));
|
||||||
/// # assert_eq!(query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err(), QueryEntityError::QueryDoesNotMatch(invalid_entity));
|
/// assert_eq!(match query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err() {QueryEntityError::QueryDoesNotMatch(entity, _) => entity, _ => panic!()}, invalid_entity);
|
||||||
/// # assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0]));
|
/// # assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0]));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
|||||||
@ -723,7 +723,7 @@ impl World {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// If any entities are duplicated.
|
/// If any entities are duplicated.
|
||||||
fn verify_unique_entities(entities: &[Entity]) -> Result<(), QueryEntityError> {
|
fn verify_unique_entities(entities: &[Entity]) -> Result<(), QueryEntityError<'static>> {
|
||||||
for i in 0..entities.len() {
|
for i in 0..entities.len() {
|
||||||
for j in 0..i {
|
for j in 0..i {
|
||||||
if entities[i] == entities[j] {
|
if entities[i] == entities[j] {
|
||||||
|
|||||||
@ -325,7 +325,8 @@ where
|
|||||||
Ok(view) => view,
|
Ok(view) => view,
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
QueryEntityError::NoSuchEntity(_) => return Err(DrawError::ViewEntityNotFound),
|
QueryEntityError::NoSuchEntity(_) => return Err(DrawError::ViewEntityNotFound),
|
||||||
QueryEntityError::QueryDoesNotMatch(_) | QueryEntityError::AliasedMutability(_) => {
|
QueryEntityError::QueryDoesNotMatch(_, _)
|
||||||
|
| QueryEntityError::AliasedMutability(_) => {
|
||||||
return Err(DrawError::InvalidViewQuery)
|
return Err(DrawError::InvalidViewQuery)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -51,7 +51,7 @@ impl<'w, 's> TransformHelper<'w, 's> {
|
|||||||
fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformError {
|
fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformError {
|
||||||
use ComputeGlobalTransformError::*;
|
use ComputeGlobalTransformError::*;
|
||||||
match err {
|
match err {
|
||||||
QueryEntityError::QueryDoesNotMatch(entity) => MissingTransform(entity),
|
QueryEntityError::QueryDoesNotMatch(entity, _) => MissingTransform(entity),
|
||||||
QueryEntityError::NoSuchEntity(entity) => {
|
QueryEntityError::NoSuchEntity(entity) => {
|
||||||
if ancestor {
|
if ancestor {
|
||||||
MalformedHierarchy(entity)
|
MalformedHierarchy(entity)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user