diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 41a23020ac5f4..fa7b5d6403927 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -229,7 +229,7 @@ pub struct FilteredAccess { access: Access, // An array of filter sets to express `With` or `Without` clauses in disjunctive normal form, for example: `Or<(With, With)>`. // Filters like `(With, Or<(With, Without)>` are expanded into `Or<((With, With), (With, Without))>`. - filter_sets: Vec>, + pub(crate) filter_sets: Vec>, } impl Default for FilteredAccess { @@ -380,9 +380,9 @@ impl FilteredAccess { } #[derive(Clone, Eq, PartialEq)] -struct AccessFilters { - with: FixedBitSet, - without: FixedBitSet, +pub(crate) struct AccessFilters { + pub(crate) with: FixedBitSet, + pub(crate) without: FixedBitSet, _index_type: PhantomData, } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index aee797ccf662a..621d8fdc6ebc2 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1,7 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, - component::{ComponentId, Tick}, - entity::Entity, + component::{ComponentId, Components, Tick}, + entity::{Entity, EntityLocation}, prelude::FromWorld, query::{ Access, BatchingStrategy, DebugCheckedUnwrap, FilteredAccess, QueryCombinationIter, @@ -231,7 +231,9 @@ impl QueryState { /// Gets the query result for the given [`World`] and [`Entity`]. /// - /// This can only be called for read-only queries, see [`Self::get_mut`] for write-queries. + /// In case of a nonexisting entity or mismatched components, a [`QueryEntityError`] is returned instead. + /// + /// This can only be called for read-only access to the component, see [`Self::get_mut`] for write access. #[inline] pub fn get<'w>( &mut self, @@ -252,7 +254,7 @@ impl QueryState { /// Returns the read-only query results for the given array of [`Entity`]. /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is + /// In case of a nonexisting entity or mismatched components, a [`QueryEntityError`] is /// returned instead. /// /// Note that the unlike [`QueryState::get_many_mut`], the entities passed in do not need to be unique. @@ -261,7 +263,7 @@ impl QueryState { /// /// ```rust /// use bevy_ecs::prelude::*; - /// use bevy_ecs::query::QueryEntityError; + /// use bevy_ecs::query::{QueryEntityError, QueryEntityErrorDetail}; /// /// #[derive(Component, PartialEq, Debug)] /// struct A(usize); @@ -280,7 +282,13 @@ impl QueryState { /// /// let wrong_entity = Entity::from_raw(365); /// - /// assert_eq!(query_state.get_many(&world, [wrong_entity]), Err(QueryEntityError::NoSuchEntity(wrong_entity))); + /// assert!(matches!( + /// query_state.get_many(&world, [wrong_entity]), + /// Err(QueryEntityError::NoSuchEntity(QueryEntityErrorDetail { + /// requested_entity: wrong_entity, + /// .. + /// })) + /// )); /// ``` #[inline] pub fn get_many<'w, const N: usize>( @@ -302,6 +310,9 @@ impl QueryState { } /// Gets the query result for the given [`World`] and [`Entity`]. + /// + /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is + /// returned instead. #[inline] pub fn get_mut<'w>( &mut self, @@ -329,7 +340,11 @@ impl QueryState { /// /// ```rust /// use bevy_ecs::prelude::*; - /// use bevy_ecs::query::QueryEntityError; + /// use bevy_ecs::query::{ + /// QueryEntityError, + /// QueryEntityErrorDetail, + /// QueryEntityMismatchDetail, + /// }; /// /// #[derive(Component, PartialEq, Debug)] /// struct A(usize); @@ -356,9 +371,31 @@ impl QueryState { /// let wrong_entity = Entity::from_raw(57); /// 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, [invalid_entity]).unwrap_err(), QueryEntityError::QueryDoesNotMatch(invalid_entity)); - /// assert_eq!(query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); + /// assert!(matches!( + /// query_state.get_many_mut(&mut world, [wrong_entity]).unwrap_err(), + /// QueryEntityError::NoSuchEntity(QueryEntityErrorDetail { + /// requested_entity: wrong_entity, + /// .. + /// }) + /// )); + /// assert!(matches!( + /// query_state.get_many_mut(&mut world, [invalid_entity]).unwrap_err(), + /// QueryEntityError::QueryDoesNotMatch( + /// QueryEntityMismatchDetail::ComponentMismatch(_), + /// QueryEntityErrorDetail { + /// requested_entity: invalid_entity, + /// .. + /// } + /// ) + /// )); + /// let first_entity = entities[0]; + /// assert!(matches!( + /// query_state.get_many_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), + /// QueryEntityError::AliasedMutability(QueryEntityErrorDetail { + /// requested_entity: first_entity, + /// .. + /// }) + /// )); /// ``` #[inline] pub fn get_many_mut<'w, const N: usize>( @@ -447,17 +484,32 @@ impl QueryState { let location = world .entities() .get(entity) - .ok_or(QueryEntityError::NoSuchEntity(entity))?; + .ok_or(QueryEntityError::NoSuchEntity(QueryEntityErrorDetail { + requested_entity: entity, + query_type: std::any::type_name::(), + }))?; + let archetype = world + .archetypes() + .get(location.archetype_id) + .debug_checked_unwrap(); + if !self .matched_archetypes .contains(location.archetype_id.index()) { - return Err(QueryEntityError::QueryDoesNotMatch(entity)); + return Err(QueryEntityError::QueryDoesNotMatch( + QueryEntityMismatchDetail::ComponentMismatch(self.get_mismatches_detail( + world.components(), + location, + archetype, + )), + QueryEntityErrorDetail { + requested_entity: entity, + query_type: std::any::type_name::(), + }, + )); } - let archetype = world - .archetypes() - .get(location.archetype_id) - .debug_checked_unwrap(); + let mut fetch = Q::init_fetch(world, &self.fetch_state, last_run, this_run); let mut filter = F::init_fetch(world, &self.filter_state, last_run, this_run); @@ -472,7 +524,13 @@ impl QueryState { if F::filter_fetch(&mut filter, entity, location.table_row) { Ok(Q::fetch(&mut fetch, entity, location.table_row)) } else { - Err(QueryEntityError::QueryDoesNotMatch(entity)) + Err(QueryEntityError::QueryDoesNotMatch( + QueryEntityMismatchDetail::ChangeDetectionMismatch, + QueryEntityErrorDetail { + requested_entity: entity, + query_type: std::any::type_name::(), + }, + )) } } @@ -529,7 +587,12 @@ impl QueryState { for i in 0..N { for j in 0..i { if entities[i] == entities[j] { - return Err(QueryEntityError::AliasedMutability(entities[i])); + return Err(QueryEntityError::AliasedMutability( + QueryEntityErrorDetail { + requested_entity: entities[i], + query_type: std::any::type_name::(), + }, + )); } } } @@ -1276,22 +1339,114 @@ impl QueryState { >())), } } + + /// Gets the reasons an [`Entity`] might not match this query, see [`QueryEntityMismatchDetail`] + pub fn get_mismatches_detail( + &self, + components: &Components, + entity_location: EntityLocation, + archetype: &Archetype, + ) -> Vec { + let mut mismatch_details = Vec::new(); + + for components_access in &self.component_access.filter_sets { + let query_with_components = components_access.with.clone(); + let query_without_components = components_access.without.clone(); + let mut query_unmatched_components = query_with_components.clone(); + + let mut query_without_components_in_entity = Vec::new(); + for component_id in archetype.components() { + if query_without_components.contains(component_id.index()) { + query_without_components_in_entity.push(( + components.get_info(component_id).unwrap().name().to_owned(), + component_id, + )); + } else if query_with_components.contains(component_id.index()) { + query_unmatched_components.set(component_id.index(), false); + } + } + + let query_with_components_not_in_entity = query_unmatched_components + .ones() + .map(|component_id| { + let component_id = ComponentId::new(component_id); + ( + components.get_info(component_id).unwrap().name().to_owned(), + component_id, + ) + }) + .collect(); + + mismatch_details.push(QueryEntityComponentMismatch { + query_with_components_not_in_entity, + query_without_components_in_entity, + entity_archetype: entity_location.archetype_id, + query_with_components, + query_without_components, + }); + } + + mismatch_details + } } /// An error that occurs when retrieving a specific [`Entity`]'s query result from [`Query`](crate::system::Query) or [`QueryState`]. -// TODO: return the type_name as part of this error -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum QueryEntityError { - /// 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. - QueryDoesNotMatch(Entity), + /// The given [`Entity`]'s components do not match the query, see [`QueryEntityMismatchDetail`]. + QueryDoesNotMatch(QueryEntityMismatchDetail, QueryEntityErrorDetail), /// The given [`Entity`] does not exist. - NoSuchEntity(Entity), + NoSuchEntity(QueryEntityErrorDetail), /// The [`Entity`] was requested mutably more than once. /// /// See [`QueryState::get_many_mut`] for an example. - AliasedMutability(Entity), + AliasedMutability(QueryEntityErrorDetail), +} + +/// Additional information in the context of a [`QueryEntityError`]. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct QueryEntityErrorDetail { + /// Specific entity which was requested when encountering the error. + pub requested_entity: Entity, + /// Representation of the query's type. + pub query_type: &'static str, +} + +/// Diagnosis about why an [`Entity`] and a [`Query`](crate::system::Query) or [`QueryState`] don't match. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum QueryEntityMismatchDetail { + /// The entity is missing components required by the query, + /// or it has components that are filtered out by the query. + ComponentMismatch(Vec), + /// The entity has the correct components, but some of them are requested + /// with change detection ([`Added`](crate::query::Added) or [`Changed`](crate::query::Changed)) + /// and the component for that entity is not in that change state. + ChangeDetectionMismatch, +} + +/// Represents which [`Entity`]'s component do not match a [`Query`](crate::system::Query) or [`QueryState`]. +/// +/// Will be returned automatically with [`QueryEntityMismatchDetail::ComponentMismatch`], +/// or can be requested manually with [`QueryState::get_mismatches_detail`]. +/// +/// This is usually used in an array because of a query like `QueryState, With>` +/// is separated into two queries `QueryState>` and `QueryState>`, +/// in that case a single `QueryEntityMismatchDetail` represents the reasons the entity +/// doesn't match one of those "sub-queries". +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct QueryEntityComponentMismatch { + /// Components that the query requires but the entity doesn't have, + /// with a representation of their name. + pub query_with_components_not_in_entity: Vec<(String, ComponentId)>, + /// Components that the query filters out but the entity have, + /// with a representation of their name. + pub query_without_components_in_entity: Vec<(String, ComponentId)>, + /// [`Archetype`] of the entity, used to retrieve its components. + pub entity_archetype: ArchetypeId, + /// Components that the query requires. + pub query_with_components: FixedBitSet, + /// Components that the query filters out. + pub query_without_components: FixedBitSet, } impl std::error::Error for QueryEntityError {} @@ -1299,12 +1454,33 @@ impl std::error::Error for QueryEntityError {} impl fmt::Display for QueryEntityError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - QueryEntityError::QueryDoesNotMatch(_) => { - write!(f, "The given entity's components do not match the query.") - } - QueryEntityError::NoSuchEntity(_) => write!(f, "The requested entity does not exist."), - QueryEntityError::AliasedMutability(_) => { - write!(f, "The entity was requested mutably more than once.") + QueryEntityError::QueryDoesNotMatch( + QueryEntityMismatchDetail::ComponentMismatch(_), + .., + ) => write!(f, "The given entity's components do not match the query."), + QueryEntityError::QueryDoesNotMatch( + QueryEntityMismatchDetail::ChangeDetectionMismatch, + .., + ) => write!( + f, + "The given entity's components are not in the change state required by the query." + ), + QueryEntityError::NoSuchEntity(QueryEntityErrorDetail { + requested_entity, .. + }) => write!( + f, + "The requested entity {} does not exist.", + requested_entity.index() + ), + QueryEntityError::AliasedMutability(QueryEntityErrorDetail { + requested_entity, + .. + }) => { + write!( + f, + "The entity {} was requested mutably more than once.", + requested_entity.index() + ) } } } @@ -1312,7 +1488,7 @@ impl fmt::Display for QueryEntityError { #[cfg(test)] mod tests { - use crate::{prelude::*, query::QueryEntityError}; + use crate::{prelude::*, query::QueryEntityError, query::QueryEntityErrorDetail}; #[test] fn get_many_unchecked_manual_uniqueness() { @@ -1354,7 +1530,10 @@ mod tests { ) .unwrap_err() }, - QueryEntityError::AliasedMutability(entities[0]) + QueryEntityError::AliasedMutability(QueryEntityErrorDetail { + requested_entity: entities[0], + query_type: "bevy_ecs::query::state::QueryState", + }) ); assert_eq!( @@ -1369,7 +1548,10 @@ mod tests { ) .unwrap_err() }, - QueryEntityError::AliasedMutability(entities[0]) + QueryEntityError::AliasedMutability(QueryEntityErrorDetail { + requested_entity: entities[0], + query_type: "bevy_ecs::query::state::QueryState", + }) ); assert_eq!( @@ -1384,7 +1566,10 @@ mod tests { ) .unwrap_err() }, - QueryEntityError::AliasedMutability(entities[9]) + QueryEntityError::AliasedMutability(QueryEntityErrorDetail { + requested_entity: entities[9], + query_type: "bevy_ecs::query::state::QueryState", + }) ); } @@ -1424,8 +1609,12 @@ mod tests { #[derive(Debug)] pub enum QuerySingleError { /// No entity fits the query. + /// + /// Provides a representation of the query's type. NoEntities(&'static str), /// Multiple entities fit the query. + /// + /// Provides a representation of the query's type. MultipleEntities(&'static str), } diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 3900dd68ac6ba..7ff8139e1a52d 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -511,7 +511,8 @@ mod tests { }, system::{ adapter::new, Commands, In, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, - QueryComponentError, Res, ResMut, Resource, System, SystemState, + QueryComponentError, QueryComponentErrorDetail, Res, ResMut, Resource, System, + SystemState, }, world::{FromWorld, World}, }; @@ -1726,7 +1727,11 @@ mod tests { run_system(&mut world, move |q: Query<&mut W>| { let mut rq = q.to_readonly(); assert_eq!( - QueryComponentError::MissingWriteAccess, + QueryComponentError::MissingWriteAccess(QueryComponentErrorDetail { + requested_entity: entity, + requested_component: "bevy_ecs::system::tests::W", + query_type: "bevy_ecs::system::query::Query<&bevy_ecs::system::tests::W>", + }), rq.get_component_mut::>(entity).unwrap_err(), ); }); diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index fe12856744edb..48f87750f24d2 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -814,7 +814,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns the read-only query item for the given [`Entity`]. /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. + /// In case of a nonexisting entity or mismatched components, a [`QueryEntityError`] is returned instead. /// /// # Example /// @@ -860,9 +860,10 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns the read-only query items for the given array of [`Entity`]. /// /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. /// The elements of the array do not need to be unique, unlike `get_many_mut`. /// + /// In case of a nonexisting entity or mismatched components, a [`QueryEntityError`] is returned instead. + /// /// # See also /// /// - [`get_many_mut`](Self::get_many_mut) to get mutable query items. @@ -887,7 +888,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// /// # Panics /// - /// This method panics if there is a query mismatch or a non-existing entity. + /// This method panics if there are mismatched components or a non-existing entity. /// /// # Examples /// ```rust, no_run @@ -931,7 +932,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns the query item for the given [`Entity`]. /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. + /// In case of a nonexisting entity or mismatched components, a [`QueryEntityError`] is returned instead. /// /// # Example /// @@ -969,7 +970,9 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns the query items for the given array of [`Entity`]. /// /// The returned query items are in the same order as the input. - /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is returned instead. + /// + /// In case of a nonexisting entity, duplicate entities or mismatched components, + /// a [`QueryEntityError`] is returned instead. /// /// # See also /// @@ -991,7 +994,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// /// # Panics /// - /// This method panics if there is a query mismatch, a non-existing entity, or the same `Entity` is included more than once in the array. + /// This method panics if there are mismatched components, a non-existing entity, + /// or the same `Entity` is included more than once in the array. /// /// # Examples /// @@ -1042,7 +1046,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns the query item for the given [`Entity`]. /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. + /// In case of a nonexisting entity or mismatched components, a [`QueryEntityError`] is returned instead. /// /// # Safety /// @@ -1062,7 +1066,9 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns a shared reference to the component `T` of the given [`Entity`]. /// - /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead. + /// In case of a nonexisting entity, if the entity doesn't have the requested component, + /// or if the query doesn't have read access to this component, + /// a [`QueryComponentError`] is returned instead. /// /// # Example /// @@ -1096,15 +1102,21 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { let world = self.world; let entity_ref = world .get_entity(entity) - .ok_or(QueryComponentError::NoSuchEntity)?; - let component_id = world - .components() - .get_id(TypeId::of::()) - .ok_or(QueryComponentError::MissingComponent)?; + .ok_or(QueryComponentError::NoSuchEntity( + QueryComponentErrorDetail::from::(entity), + ))?; + let component_id = world.components().get_id(TypeId::of::()).ok_or( + QueryComponentError::MissingComponent(QueryComponentErrorDetail::from::( + entity, + )), + )?; + let archetype_component = entity_ref .archetype() .get_archetype_component_id(component_id) - .ok_or(QueryComponentError::MissingComponent)?; + .ok_or(QueryComponentError::MissingComponent( + QueryComponentErrorDetail::from::(entity), + ))?; if self .state .archetype_component_access @@ -1112,15 +1124,21 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { { // SAFETY: `self.world` must have access to the component `T` for this entity, // since it was registered in `self.state`'s archetype component access set. - unsafe { entity_ref.get::() }.ok_or(QueryComponentError::MissingComponent) + unsafe { entity_ref.get::() }.ok_or(QueryComponentError::MissingComponent( + QueryComponentErrorDetail::from::(entity), + )) } else { - Err(QueryComponentError::MissingReadAccess) + Err(QueryComponentError::MissingReadAccess( + QueryComponentErrorDetail::from::(entity), + )) } } /// Returns a mutable reference to the component `T` of the given entity. /// - /// In case of a nonexisting entity or mismatched component, a [`QueryComponentError`] is returned instead. + /// In case of a nonexisting entity, if the entity doesn't have the requested component, + /// or if the query doesn't have write access to this component, + /// a [`QueryComponentError`] is returned instead. /// /// # Example /// @@ -1156,7 +1174,9 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns a mutable reference to the component `T` of the given entity. /// - /// In case of a nonexisting entity or mismatched component, a [`QueryComponentError`] is returned instead. + /// In case of a nonexisting entity, if the entity doesn't have the requested component, + /// or if the query doesn't have write access to this component, + /// a [`QueryComponentError`] is returned instead. /// /// # Safety /// @@ -1174,20 +1194,27 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { // SAFETY: this check is required to ensure soundness in the case of `to_readonly().get_component_mut()` // See the comments on the `force_read_only_component_access` field for more info. if self.force_read_only_component_access { - return Err(QueryComponentError::MissingWriteAccess); + return Err(QueryComponentError::MissingWriteAccess( + QueryComponentErrorDetail::from::(entity), + )); } let world = self.world; let entity_ref = world .get_entity(entity) - .ok_or(QueryComponentError::NoSuchEntity)?; - let component_id = world - .components() - .get_id(TypeId::of::()) - .ok_or(QueryComponentError::MissingComponent)?; + .ok_or(QueryComponentError::NoSuchEntity( + QueryComponentErrorDetail::from::(entity), + ))?; + let component_id = world.components().get_id(TypeId::of::()).ok_or( + QueryComponentError::MissingComponent(QueryComponentErrorDetail::from::( + entity, + )), + )?; let archetype_component = entity_ref .archetype() .get_archetype_component_id(component_id) - .ok_or(QueryComponentError::MissingComponent)?; + .ok_or(QueryComponentError::MissingComponent( + QueryComponentErrorDetail::from::(entity), + ))?; if self .state .archetype_component_access @@ -1195,9 +1222,13 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { { entity_ref .get_mut_using_ticks::(self.last_run, self.this_run) - .ok_or(QueryComponentError::MissingComponent) + .ok_or(QueryComponentError::MissingComponent( + QueryComponentErrorDetail::from::(entity), + )) } else { - Err(QueryComponentError::MissingWriteAccess) + Err(QueryComponentError::MissingWriteAccess( + QueryComponentErrorDetail::from::(entity), + )) } } @@ -1436,7 +1467,7 @@ pub enum QueryComponentError { /// # Example /// /// ``` - /// # use bevy_ecs::{prelude::*, system::QueryComponentError}; + /// # use bevy_ecs::{prelude::*, system::{QueryComponentError, QueryComponentErrorDetail}}; /// # /// # #[derive(Component)] /// # struct OtherComponent; @@ -1452,13 +1483,19 @@ pub enum QueryComponentError { /// fn get_missing_read_access_error(query: Query<&OtherComponent>, res: Res) { /// assert_eq!( /// query.get_component::(res.entity), - /// Err(QueryComponentError::MissingReadAccess), + /// Err(QueryComponentError::MissingReadAccess( + /// QueryComponentErrorDetail { + /// requested_entity: res.entity, + /// requested_component: "RequestedComponent", + /// query_type: "bevy_ecs::system::Query<&OtherComponent>", + /// } + /// )), /// ); /// println!("query doesn't have read access to RequestedComponent because it does not appear in Query<&OtherComponent>"); /// } /// # bevy_ecs::system::assert_is_system(get_missing_read_access_error); /// ``` - MissingReadAccess, + MissingReadAccess(QueryComponentErrorDetail), /// The [`Query`] does not have write access to the requested component. /// /// This error occurs when the requested component is not included in the original query, or the mutability of the requested component is mismatched with the original query. @@ -1466,7 +1503,7 @@ pub enum QueryComponentError { /// # Example /// /// ``` - /// # use bevy_ecs::{prelude::*, system::QueryComponentError}; + /// # use bevy_ecs::{prelude::*, system::{QueryComponentError, QueryComponentErrorDetail}}; /// # /// # #[derive(Component, PartialEq, Debug)] /// # struct RequestedComponent; @@ -1479,17 +1516,34 @@ pub enum QueryComponentError { /// fn get_missing_write_access_error(mut query: Query<&RequestedComponent>, res: Res) { /// assert_eq!( /// query.get_component::(res.entity), - /// Err(QueryComponentError::MissingWriteAccess), + /// Err(QueryComponentError::MissingWriteAccess( + /// QueryComponentErrorDetail { + /// requested_entity: res.entity, + /// requested_component: "RequestedComponent", + /// query_type: "bevy_ecs::system::Query<&RequestedComponent>", + /// } + /// )), /// ); /// println!("query doesn't have write access to RequestedComponent because it doesn't have &mut in Query<&RequestedComponent>"); /// } /// # bevy_ecs::system::assert_is_system(get_missing_write_access_error); /// ``` - MissingWriteAccess, + MissingWriteAccess(QueryComponentErrorDetail), /// The given [`Entity`] does not have the requested component. - MissingComponent, + MissingComponent(QueryComponentErrorDetail), /// The requested [`Entity`] does not exist. - NoSuchEntity, + NoSuchEntity(QueryComponentErrorDetail), +} + +/// Additional information in the context of a [`QueryComponentError`]. +#[derive(Debug, PartialEq, Eq)] +pub struct QueryComponentErrorDetail { + /// Specific entity which was requested when encountering the error. + pub requested_entity: Entity, + /// Component which was requested when encountering the error. + pub requested_component: &'static str, + /// Representation of the query's type. + pub query_type: &'static str, } impl std::error::Error for QueryComponentError {} @@ -1497,28 +1551,65 @@ impl std::error::Error for QueryComponentError {} impl std::fmt::Display for QueryComponentError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - QueryComponentError::MissingReadAccess => { + QueryComponentError::MissingReadAccess(QueryComponentErrorDetail { + requested_component, + query_type, + .. + }) => { write!( f, - "This query does not have read access to the requested component." + "The query {} does not have read access to the requested component {}.", + requested_component, query_type ) } - QueryComponentError::MissingWriteAccess => { + QueryComponentError::MissingWriteAccess(QueryComponentErrorDetail { + requested_component, + query_type, + .. + }) => { write!( f, - "This query does not have write access to the requested component." + "The query {} does not have write access to the requested component {}.", + requested_component, query_type ) } - QueryComponentError::MissingComponent => { - write!(f, "The given entity does not have the requested component.") + QueryComponentError::MissingComponent(QueryComponentErrorDetail { + requested_entity, + requested_component, + .. + }) => { + write!( + f, + "The given entity {} does not have the requested component {}.", + requested_entity.index(), + requested_component + ) } - QueryComponentError::NoSuchEntity => { - write!(f, "The requested entity does not exist.") + QueryComponentError::NoSuchEntity(QueryComponentErrorDetail { + requested_entity, + .. + }) => { + write!( + f, + "The requested entity {} does not exist.", + requested_entity.index(), + ) } } } } +impl QueryComponentErrorDetail { + /// Creates a [`QueryComponentErrorDetail`] given a [`Query`], a [`Component`] and an [`Entity`]. + pub fn from(entity: Entity) -> QueryComponentErrorDetail { + QueryComponentErrorDetail { + requested_entity: entity, + requested_component: std::any::type_name::(), + query_type: std::any::type_name::(), + } + } +} + impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { /// Returns the query item for the given [`Entity`], with the actual "inner" world lifetime. ///