From a78a231f6237aca2ba523cb5e251e8342874fd74 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 16:39:29 -0400 Subject: [PATCH 01/32] Basic function signatures --- crates/bevy_ecs/src/query/state.rs | 18 ++++++++++++++++++ crates/bevy_ecs/src/system/query.rs | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index f675de4d89a1d..c17d94f2853a8 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -153,6 +153,15 @@ where } } + #[inline] + pub fn get_multiple<'w, 's, const N: usize>( + &'s mut self, + world: &'w World, + entities: [Entity; N], + ) -> Result<[>::Item; N], QueryEntityError> { + todo!() + } + /// Gets the query result for the given [`World`] and [`Entity`]. #[inline] pub fn get_mut<'w, 's>( @@ -172,6 +181,15 @@ where } } + #[inline] + pub fn get_multiple_mut<'w, 's, const N: usize>( + &'s mut self, + world: &'w mut World, + entities: [Entity; N], + ) -> Result<[>::Item; N], QueryEntityError> { + todo!() + } + #[inline] pub fn get_manual<'w, 's>( &'s self, diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 349121b06bd87..797b9e300ebbb 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -618,6 +618,14 @@ where } } + #[inline] + pub fn get_multiple( + &'s self, + entities: [Entity; N], + ) -> Result<[>::Item; N], QueryEntityError> { + todo!() + } + /// Returns the query result for the given [`Entity`]. /// /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is @@ -659,6 +667,14 @@ where } } + #[inline] + pub fn get_multiple_mut( + &'s mut self, + entities: [Entity; N], + ) -> Result<[::Item; N], QueryEntityError> { + todo!() + } + /// Returns the query result for the given [`Entity`]. /// /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is From 2a22d985f5ef02445ec353335bf42da219cf3dbe Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 16:41:12 -0400 Subject: [PATCH 02/32] Add AliasedMutability variant to QueryEntityError --- crates/bevy_ecs/src/query/state.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index c17d94f2853a8..641ba6d6e64fc 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -751,10 +751,13 @@ where } /// An error that occurs when retrieving a specific [`Entity`]'s query result. +// TODO: return the TypeID or invalid Entity as part of this error #[derive(Error, Debug)] pub enum QueryEntityError { #[error("The given entity does not have the requested component.")] QueryDoesNotMatch, #[error("The requested entity does not exist.")] NoSuchEntity, + #[error("The entity was requested mutably more than once.")] + AliasedMutability, } From f82209479f66e2217ac9cfce0c26c8ed16f0b66e Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 16:51:37 -0400 Subject: [PATCH 03/32] QueryState methods --- crates/bevy_ecs/src/query/state.rs | 58 ++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 641ba6d6e64fc..b9aea89550409 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -159,7 +159,29 @@ where world: &'w World, entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { - todo!() + self.update_archetypes(world); + + let array_of_results = entities.map(|entity| { + // SAFETY: query is read only + unsafe { + self.get_unchecked_manual::( + world, + entity, + world.last_change_tick(), + world.read_change_tick(), + ) + } + }); + + // If any of the entities were not present, return an error + for result in &array_of_results { + if result.is_err() { + return Err(QueryEntityError::NoSuchEntity); + } + } + + // Since we have verified that all entities are present, we can safely unwrap + Ok(array_of_results.map(|result| result.unwrap())) } /// Gets the query result for the given [`World`] and [`Entity`]. @@ -186,8 +208,38 @@ where &'s mut self, world: &'w mut World, entities: [Entity; N], - ) -> Result<[>::Item; N], QueryEntityError> { - todo!() + ) -> Result<[>::Item; N], QueryEntityError> { + self.update_archetypes(world); + + for i in 0..N { + for j in 0..i { + if entities[i] == entities[j] { + return Err(QueryEntityError::AliasedMutability); + } + } + } + + let array_of_results = entities.map(|entity| { + // SAFETY: entity list is checked for uniqueness, and we require exclusive access to the World + unsafe { + self.get_unchecked_manual::( + world, + entity, + world.last_change_tick(), + world.read_change_tick(), + ) + } + }); + + // If any of the entities were not present, return an error + for result in &array_of_results { + if result.is_err() { + return Err(QueryEntityError::NoSuchEntity); + } + } + + // Since we have verified that all entities are present, we can safely unwrap + Ok(array_of_results.map(|result| result.unwrap())) } #[inline] From f47b7eae2dfc13831b02bdb7723c04efdc9aed06 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 16:54:46 -0400 Subject: [PATCH 04/32] Query methods --- crates/bevy_ecs/src/system/query.rs | 52 +++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 797b9e300ebbb..b6049f27b1bef 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -623,7 +623,27 @@ where &'s self, entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { - todo!() + let array_of_results = entities.map(|entity| { + // SAFETY: query is read only + unsafe { + self.state.get_unchecked_manual::( + self.world, + entity, + self.last_change_tick, + self.change_tick, + ) + } + }); + + // If any of the entities were not present, return an error + for result in &array_of_results { + if result.is_err() { + return Err(QueryEntityError::NoSuchEntity); + } + } + + // Since we have verified that all entities are present, we can safely unwrap + Ok(array_of_results.map(|result| result.unwrap())) } /// Returns the query result for the given [`Entity`]. @@ -672,7 +692,35 @@ where &'s mut self, entities: [Entity; N], ) -> Result<[::Item; N], QueryEntityError> { - todo!() + for i in 0..N { + for j in 0..i { + if entities[i] == entities[j] { + return Err(QueryEntityError::AliasedMutability); + } + } + } + + let array_of_results = entities.map(|entity| { + // SAFETY: query is read only + unsafe { + self.state.get_unchecked_manual::( + self.world, + entity, + self.last_change_tick, + self.change_tick, + ) + } + }); + + // If any of the entities were not present, return an error + for result in &array_of_results { + if result.is_err() { + return Err(QueryEntityError::NoSuchEntity); + } + } + + // Since we have verified that all entities are present, we can safely unwrap + Ok(array_of_results.map(|result| result.unwrap())) } /// Returns the query result for the given [`Entity`]. From 854bb1e6607077c163cb899c885a292bfe31c9d8 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 16:56:53 -0400 Subject: [PATCH 05/32] Infallible variants --- crates/bevy_ecs/src/system/query.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index b6049f27b1bef..fa811cacf18f9 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -646,6 +646,14 @@ where Ok(array_of_results.map(|result| result.unwrap())) } + #[inline] + pub fn multiple( + &'s self, + entities: [Entity; N], + ) -> [>::Item; N] { + self.get_multiple(entities).unwrap() + } + /// Returns the query result for the given [`Entity`]. /// /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is @@ -723,6 +731,14 @@ where Ok(array_of_results.map(|result| result.unwrap())) } + #[inline] + pub fn multiple_mut( + &'s mut self, + entities: [Entity; N], + ) -> [>::Item; N] { + self.get_multiple_mut(entities).unwrap() + } + /// Returns the query result for the given [`Entity`]. /// /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is From 712abdafa08189fec7656e277c737022a711f235 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 16:59:52 -0400 Subject: [PATCH 06/32] Fix safety comment --- crates/bevy_ecs/src/system/query.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index fa811cacf18f9..d5669499db004 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -709,7 +709,9 @@ where } let array_of_results = entities.map(|entity| { - // SAFETY: query is read only + // SAFETY: Entities are checked for uniqueness above, + // the scheduler ensure that we do not have conflicting world access, + // and we require &mut self to avoid any other simultaneous operations on this Query unsafe { self.state.get_unchecked_manual::( self.world, From 34e92c7c38bbbff7b53aea436e7448b87f2e3f06 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 17:04:16 -0400 Subject: [PATCH 07/32] Make QueryEntityError more useful --- crates/bevy_ecs/src/query/state.rs | 18 +++++++++--------- crates/bevy_ecs/src/system/query.rs | 10 +++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index b9aea89550409..c26100f0dfa99 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -175,8 +175,8 @@ where // If any of the entities were not present, return an error for result in &array_of_results { - if result.is_err() { - return Err(QueryEntityError::NoSuchEntity); + if let Err(QueryEntityError::NoSuchEntity(entity)) = result { + return Err(QueryEntityError::NoSuchEntity(*entity)); } } @@ -214,7 +214,7 @@ where for i in 0..N { for j in 0..i { if entities[i] == entities[j] { - return Err(QueryEntityError::AliasedMutability); + return Err(QueryEntityError::AliasedMutability(entities[i])); } } } @@ -233,8 +233,8 @@ where // If any of the entities were not present, return an error for result in &array_of_results { - if result.is_err() { - return Err(QueryEntityError::NoSuchEntity); + if let Err(QueryEntityError::NoSuchEntity(entity)) = result { + return Err(QueryEntityError::NoSuchEntity(*entity)); } } @@ -298,7 +298,7 @@ where let location = world .entities .get(entity) - .ok_or(QueryEntityError::NoSuchEntity)?; + .ok_or(QueryEntityError::NoSuchEntity(entity))?; if !self .matched_archetypes .contains(location.archetype_id.index()) @@ -803,13 +803,13 @@ where } /// An error that occurs when retrieving a specific [`Entity`]'s query result. -// TODO: return the TypeID or invalid Entity as part of this error +// TODO: return the type_name as part of this error #[derive(Error, Debug)] pub enum QueryEntityError { #[error("The given entity does not have the requested component.")] QueryDoesNotMatch, #[error("The requested entity does not exist.")] - NoSuchEntity, + NoSuchEntity(Entity), #[error("The entity was requested mutably more than once.")] - AliasedMutability, + AliasedMutability(Entity), } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index d5669499db004..74138c92694eb 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -637,8 +637,8 @@ where // If any of the entities were not present, return an error for result in &array_of_results { - if result.is_err() { - return Err(QueryEntityError::NoSuchEntity); + if let Err(QueryEntityError::NoSuchEntity(entity)) = result { + return Err(QueryEntityError::NoSuchEntity(*entity)); } } @@ -703,7 +703,7 @@ where for i in 0..N { for j in 0..i { if entities[i] == entities[j] { - return Err(QueryEntityError::AliasedMutability); + return Err(QueryEntityError::AliasedMutability(entities[i])); } } } @@ -724,8 +724,8 @@ where // If any of the entities were not present, return an error for result in &array_of_results { - if result.is_err() { - return Err(QueryEntityError::NoSuchEntity); + if let Err(QueryEntityError::NoSuchEntity(entity)) = result { + return Err(QueryEntityError::NoSuchEntity(*entity)); } } From 063e6162bc81bd8ab3f243525fa5a1a9ab6a8018 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 17:08:08 -0400 Subject: [PATCH 08/32] Basic docs --- crates/bevy_ecs/src/query/state.rs | 10 ++++++++++ crates/bevy_ecs/src/system/query.rs | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index c26100f0dfa99..8d2e586155a1d 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -153,6 +153,12 @@ where } } + /// Returns the read-only query results for the given array of [`Entity`]. + /// + /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is + /// returned instead. + /// + /// Note that the unlike [`QueryState::get_multiple_mut`], the entities passed in do not need to be unique. #[inline] pub fn get_multiple<'w, 's, const N: usize>( &'s mut self, @@ -203,6 +209,10 @@ where } } + /// Returns the query results for the given array of [`Entity`]. + /// + /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is + /// returned instead. #[inline] pub fn get_multiple_mut<'w, 's, const N: usize>( &'s mut self, diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 74138c92694eb..51d8c032d10db 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -618,6 +618,12 @@ where } } + /// Returns the read-only query results for the given array of [`Entity`]. + /// + /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is + /// returned instead. + /// + /// Note that the unlike [`Query::get_multiple_mut`], the entities passed in do not need to be unique. #[inline] pub fn get_multiple( &'s self, @@ -646,6 +652,7 @@ where Ok(array_of_results.map(|result| result.unwrap())) } + /// Returns the read-only query items for the provided array of [`Entity`] #[inline] pub fn multiple( &'s self, @@ -695,6 +702,10 @@ where } } + /// Returns the query results for the given array of [`Entity`]. + /// + /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is + /// returned instead. #[inline] pub fn get_multiple_mut( &'s mut self, @@ -733,6 +744,7 @@ where Ok(array_of_results.map(|result| result.unwrap())) } + /// Returns the query items for the provided array of [`Entity`] #[inline] pub fn multiple_mut( &'s mut self, From 2423515c7f102206a130ec32c6b028b5bb539c6d Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 18:23:38 -0400 Subject: [PATCH 09/32] Basic doc tests --- crates/bevy_ecs/src/query/state.rs | 56 +++++++++++++++++++++ crates/bevy_ecs/src/system/query.rs | 78 +++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 8d2e586155a1d..1eaed2e75326f 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -159,6 +159,32 @@ where /// returned instead. /// /// Note that the unlike [`QueryState::get_multiple_mut`], the entities passed in do not need to be unique. + /// + /// # Examples + /// + /// ```rust + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Component, PartialEq, Debug)] + /// struct A(usize); + /// + /// let mut world = World::new(); + /// let mut entities: [Entity; 3] = [Entity::from_raw(0); 3]; + /// + /// world.spawn().insert(A(42)); + /// + /// for i in 0..3 { + /// entities[i] = world.spawn().insert(A(i)).id(); + /// } + /// + /// world.spawn().insert(A(73)); + /// + /// let mut query_state = world.query::<&A>(); + /// + /// let component_values = query_state.get_multiple(&world, entities).unwrap(); + /// + /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]) + /// ``` #[inline] pub fn get_multiple<'w, 's, const N: usize>( &'s mut self, @@ -213,6 +239,36 @@ where /// /// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is /// returned instead. + /// + /// ```rust + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Component, PartialEq, Debug)] + /// struct A(usize); + /// + /// let mut world = World::new(); + /// let mut entities: [Entity; 3] = [Entity::from_raw(0); 3]; + /// + /// world.spawn().insert(A(42)); + /// + /// for i in 0..3 { + /// entities[i] = world.spawn().insert(A(i)).id(); + /// } + /// + /// world.spawn().insert(A(73)); + /// + /// let mut query_state = world.query::<&mut A>(); + /// + /// let mut mutable_component_values = query_state.get_multiple_mut(&mut world, entities).unwrap(); + /// + /// for mut a in mutable_component_values.iter_mut(){ + /// a.0 += 5; + /// } + /// + /// let component_values = query_state.get_multiple(&world, entities).unwrap(); + /// + /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]) + /// ``` #[inline] pub fn get_multiple_mut<'w, 's, const N: usize>( &'s mut self, diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 51d8c032d10db..a099ef748b6f0 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -624,6 +624,8 @@ where /// returned instead. /// /// Note that the unlike [`Query::get_multiple_mut`], the entities passed in do not need to be unique. + /// + /// See [`Query::multiple`] for the infallible equivalent. #[inline] pub fn get_multiple( &'s self, @@ -653,6 +655,40 @@ where } /// Returns the read-only query items for the provided array of [`Entity`] + /// + /// See [`Query::get_multiple`] for the [`Result`]-returning equivalent. + /// + /// # Examples + /// ```rust, no_run + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Component)] + /// struct Targets([Entity; 3]); + /// + /// #[derive(Component)] + /// struct Position{ + /// x: i8, + /// y: i8 + /// }; + /// + /// impl Position { + /// fn distance(&self, other: &Position) -> i8 { + /// // Manhattan distance is way easier to compute! + /// (self.x - other.x).abs() + (self.y - other.y).abs() + /// } + /// } + /// + /// fn check_all_targets_in_range(targeting_query: Query<(Entity, &Targets, &Position)>, targets_query: Query<&Position>){ + /// for (targeting_entity, targets, origin) in targeting_query.iter(){ + /// // We can use "destructuring" to unpack the results nicely + /// let [target_1, target_2, target_3] = targets_query.multiple(targets.0); + /// + /// assert!(target_1.distance(origin) <= 5); + /// assert!(target_2.distance(origin) <= 5); + /// assert!(target_3.distance(origin) <= 5); + /// } + /// } + /// ``` #[inline] pub fn multiple( &'s self, @@ -706,6 +742,8 @@ where /// /// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is /// returned instead. + /// + /// See [`Query::multiple_mut`] for the infallible equivalent. #[inline] pub fn get_multiple_mut( &'s mut self, @@ -745,6 +783,46 @@ where } /// Returns the query items for the provided array of [`Entity`] + /// + /// See [`Query::get_multiple_mut`] for the [`Result`]-returning equivalent. + /// + /// # Examples + /// + /// ```rust, no_run + /// use bevy_ecs::prelude::*; + /// + /// #[derive(Component)] + /// struct Spring{ + /// connected_entities: [Entity; 2], + /// strength: f32, + /// } + /// + /// #[derive(Component)] + /// struct Position { + /// x: f32, + /// y: f32, + /// } + /// + /// #[derive(Component)] + /// struct Force { + /// x: f32, + /// y: f32, + /// } + /// + /// fn spring_forces(spring_query: Query<&Spring>, mut mass_query: Query<(&Position, &mut Force)>){ + /// for spring in spring_query.iter(){ + /// // We can use "destructuring" to unpack our query items nicely + /// let [(position_1, mut force_1), (position_2, mut force_2)] = mass_query.multiple_mut(spring.connected_entities); + /// + /// force_1.x += spring.strength * (position_1.x - position_2.x); + /// force_1.y += spring.strength * (position_1.y - position_2.y); + /// + /// // Silence borrow-checker: I have split your mutable borrow! + /// force_2.x += spring.strength * (position_2.x - position_1.x); + /// force_2.y += spring.strength * (position_2.y - position_1.y); + /// } + /// } + /// ``` #[inline] pub fn multiple_mut( &'s mut self, From f04c390e47bee06cbb7d33c836e52d3517e1d613 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 18:37:43 -0400 Subject: [PATCH 10/32] Doc tests for unhappy paths --- crates/bevy_ecs/src/query/state.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 1eaed2e75326f..900d0d15e91e3 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -164,6 +164,7 @@ where /// /// ```rust /// use bevy_ecs::prelude::*; + /// use bevy_ecs::query::QueryEntityError; /// /// #[derive(Component, PartialEq, Debug)] /// struct A(usize); @@ -183,7 +184,11 @@ where /// /// let component_values = query_state.get_multiple(&world, entities).unwrap(); /// - /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]) + /// assert_eq!(component_values, [&A(0), &A(1), &A(2)]); + /// + /// let wrong_entity = Entity::from_raw(365); + /// + /// assert_eq!(query_state.get_multiple(&world, [wrong_entity]), Err(QueryEntityError::NoSuchEntity(wrong_entity))); /// ``` #[inline] pub fn get_multiple<'w, 's, const N: usize>( @@ -242,6 +247,7 @@ where /// /// ```rust /// use bevy_ecs::prelude::*; + /// use bevy_ecs::query::QueryEntityError; /// /// #[derive(Component, PartialEq, Debug)] /// struct A(usize); @@ -267,7 +273,12 @@ where /// /// let component_values = query_state.get_multiple(&world, entities).unwrap(); /// - /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]) + /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); + /// + /// let wrong_entity = Entity::from_raw(365); + /// + /// assert_eq!(query_state.get_multiple_mut(&mut world, [wrong_entity]), Err(QueryEntityError::NoSuchEntity(wrong_entity))); + /// assert_eq!(query_state.get_multiple_mut(&mut world, [entities[0], entities[0]]), Err(QueryEntityError::AliasedMutability(entities[0]))); /// ``` #[inline] pub fn get_multiple_mut<'w, 's, const N: usize>( @@ -870,7 +881,7 @@ where /// An error that occurs when retrieving a specific [`Entity`]'s query result. // TODO: return the type_name as part of this error -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq, Clone, Copy)] pub enum QueryEntityError { #[error("The given entity does not have the requested component.")] QueryDoesNotMatch, From 3bc943257c63a4fe53d6a429db99d1be77b026ff Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 19:03:15 -0400 Subject: [PATCH 11/32] Fix lifetimes on self Co-authored-by: Boxy --- crates/bevy_ecs/src/system/query.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index a099ef748b6f0..56e6d6518f894 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -628,7 +628,7 @@ where /// See [`Query::multiple`] for the infallible equivalent. #[inline] pub fn get_multiple( - &'s self, + &self, entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { let array_of_results = entities.map(|entity| { @@ -691,7 +691,7 @@ where /// ``` #[inline] pub fn multiple( - &'s self, + &self, entities: [Entity; N], ) -> [>::Item; N] { self.get_multiple(entities).unwrap() @@ -746,9 +746,9 @@ where /// See [`Query::multiple_mut`] for the infallible equivalent. #[inline] pub fn get_multiple_mut( - &'s mut self, + &mut self, entities: [Entity; N], - ) -> Result<[::Item; N], QueryEntityError> { + ) -> Result<[>::Item; N], QueryEntityError> { for i in 0..N { for j in 0..i { if entities[i] == entities[j] { @@ -825,7 +825,7 @@ where /// ``` #[inline] pub fn multiple_mut( - &'s mut self, + &mut self, entities: [Entity; N], ) -> [>::Item; N] { self.get_multiple_mut(entities).unwrap() From fce42c7f01789ccf1c979369c2ba82d639c610d6 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 19:12:03 -0400 Subject: [PATCH 12/32] Use unwrap_err to fix broken doc test --- crates/bevy_ecs/src/query/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 900d0d15e91e3..8f4f0b7b0fca3 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -277,8 +277,8 @@ where /// /// let wrong_entity = Entity::from_raw(365); /// - /// assert_eq!(query_state.get_multiple_mut(&mut world, [wrong_entity]), Err(QueryEntityError::NoSuchEntity(wrong_entity))); - /// assert_eq!(query_state.get_multiple_mut(&mut world, [entities[0], entities[0]]), Err(QueryEntityError::AliasedMutability(entities[0]))); + /// assert_eq!(query_state.get_multiple_mut(&mut world, [wrong_entity]).unwrap_err(), QueryEntityError::NoSuchEntity(wrong_entity)); + /// assert_eq!(query_state.get_multiple_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); /// ``` #[inline] pub fn get_multiple_mut<'w, 's, const N: usize>( From 1932ee713fecc3fc0c6b047de2282b539560f951 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 19:44:28 -0400 Subject: [PATCH 13/32] Deduplicate logic --- crates/bevy_ecs/src/query/state.rs | 114 ++++++++++++++++------------ crates/bevy_ecs/src/system/query.rs | 63 ++++----------- 2 files changed, 83 insertions(+), 94 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 8f4f0b7b0fca3..7025ac65f9525 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -198,27 +198,15 @@ where ) -> Result<[>::Item; N], QueryEntityError> { self.update_archetypes(world); - let array_of_results = entities.map(|entity| { - // SAFETY: query is read only - unsafe { - self.get_unchecked_manual::( - world, - entity, - world.last_change_tick(), - world.read_change_tick(), - ) - } - }); - - // If any of the entities were not present, return an error - for result in &array_of_results { - if let Err(QueryEntityError::NoSuchEntity(entity)) = result { - return Err(QueryEntityError::NoSuchEntity(*entity)); - } + // SAFE: query is read-only + unsafe { + self.get_multiple_unchecked_manual::( + world, + entities, + world.last_change_tick(), + world.read_change_tick(), + ) } - - // Since we have verified that all entities are present, we can safely unwrap - Ok(array_of_results.map(|result| result.unwrap())) } /// Gets the query result for the given [`World`] and [`Entity`]. @@ -288,35 +276,17 @@ where ) -> Result<[>::Item; N], QueryEntityError> { self.update_archetypes(world); - for i in 0..N { - for j in 0..i { - if entities[i] == entities[j] { - return Err(QueryEntityError::AliasedMutability(entities[i])); - } - } - } - - let array_of_results = entities.map(|entity| { - // SAFETY: entity list is checked for uniqueness, and we require exclusive access to the World - unsafe { - self.get_unchecked_manual::( - world, - entity, - world.last_change_tick(), - world.read_change_tick(), - ) - } - }); + verify_entities_unique(entities)?; - // If any of the entities were not present, return an error - for result in &array_of_results { - if let Err(QueryEntityError::NoSuchEntity(entity)) = result { - return Err(QueryEntityError::NoSuchEntity(*entity)); - } + // SAFE: method requires exclusive world access, and entities are checked for uniqueness + unsafe { + self.get_multiple_unchecked_manual::( + world, + entities, + world.last_change_tick(), + world.read_change_tick(), + ) } - - // Since we have verified that all entities are present, we can safely unwrap - Ok(array_of_results.map(|result| result.unwrap())) } #[inline] @@ -396,6 +366,42 @@ where } } + /// Gets the query results for the given [`World`] and array of [`Entity`], where the last change and + /// the current change tick are given. + /// + /// # Safety + /// This does not check for mutable query correctness. To be safe, make sure mutable queries + /// have unique access to the components they query. + /// + /// If you are calling this method with a mutable query, you must verify that `entities` is free of duplicates: + /// use [`verify_entities_unique`] to do so efficiently for small `N`. + pub(crate) unsafe fn get_multiple_unchecked_manual< + 's, + 'w, + QF: Fetch<'w, 's, State = Q::State>, + const N: usize, + >( + &'s self, + world: &'w World, + entities: [Entity; N], + last_change_tick: u32, + change_tick: u32, + ) -> Result<[>::Item; N], QueryEntityError> { + let array_of_results = entities.map(|entity| { + self.get_unchecked_manual::(world, entity, last_change_tick, change_tick) + }); + + // If any of the entities were not present, return an error + for result in &array_of_results { + if let Err(QueryEntityError::NoSuchEntity(entity)) = result { + return Err(QueryEntityError::NoSuchEntity(*entity)); + } + } + + // Since we have verified that all entities are present, we can safely unwrap + Ok(array_of_results.map(|result| result.unwrap())) + } + /// Returns an [`Iterator`] over the query results for the given [`World`]. /// /// This can only be called for read-only queries, see [`Self::iter_mut`] for write-queries. @@ -890,3 +896,17 @@ pub enum QueryEntityError { #[error("The entity was requested mutably more than once.")] AliasedMutability(Entity), } + +#[inline] +pub(crate) fn verify_entities_unique( + entities: [Entity; N], +) -> Result<(), QueryEntityError> { + for i in 0..N { + for j in 0..i { + if entities[i] == entities[j] { + return Err(QueryEntityError::AliasedMutability(entities[i])); + } + } + } + Ok(()) +} diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 56e6d6518f894..91c303f672423 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -2,8 +2,8 @@ use crate::{ component::Component, entity::Entity, query::{ - Fetch, FilterFetch, NopFetch, QueryCombinationIter, QueryEntityError, QueryIter, - QueryState, ReadOnlyFetch, WorldQuery, + verify_entities_unique, Fetch, FilterFetch, NopFetch, QueryCombinationIter, + QueryEntityError, QueryIter, QueryState, ReadOnlyFetch, WorldQuery, }, world::{Mut, World}, }; @@ -631,27 +631,16 @@ where &self, entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { - let array_of_results = entities.map(|entity| { - // SAFETY: query is read only - unsafe { - self.state.get_unchecked_manual::( + // SAFE: Query is read-only + unsafe { + self.state + .get_multiple_unchecked_manual::( self.world, - entity, + entities, self.last_change_tick, self.change_tick, ) - } - }); - - // If any of the entities were not present, return an error - for result in &array_of_results { - if let Err(QueryEntityError::NoSuchEntity(entity)) = result { - return Err(QueryEntityError::NoSuchEntity(*entity)); - } } - - // Since we have verified that all entities are present, we can safely unwrap - Ok(array_of_results.map(|result| result.unwrap())) } /// Returns the read-only query items for the provided array of [`Entity`] @@ -749,37 +738,17 @@ where &mut self, entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { - for i in 0..N { - for j in 0..i { - if entities[i] == entities[j] { - return Err(QueryEntityError::AliasedMutability(entities[i])); - } - } - } - - let array_of_results = entities.map(|entity| { - // SAFETY: Entities are checked for uniqueness above, - // the scheduler ensure that we do not have conflicting world access, - // and we require &mut self to avoid any other simultaneous operations on this Query - unsafe { - self.state.get_unchecked_manual::( - self.world, - entity, - self.last_change_tick, - self.change_tick, - ) - } - }); + verify_entities_unique(entities)?; - // If any of the entities were not present, return an error - for result in &array_of_results { - if let Err(QueryEntityError::NoSuchEntity(entity)) = result { - return Err(QueryEntityError::NoSuchEntity(*entity)); - } + // SAFE: scheduler ensures safe Query world access, and entities are checked for uniqueness + unsafe { + self.state.get_multiple_unchecked_manual::( + self.world, + entities, + self.last_change_tick, + self.change_tick, + ) } - - // Since we have verified that all entities are present, we can safely unwrap - Ok(array_of_results.map(|result| result.unwrap())) } /// Returns the query items for the provided array of [`Entity`] From 1544dbb72dc428087ac65050bba1487b9511bc42 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 20:08:23 -0400 Subject: [PATCH 14/32] Split logic into mutable and non-mutable variants to reduce unsafety --- crates/bevy_ecs/src/query/state.rs | 80 ++++++++++++++++++----------- crates/bevy_ecs/src/system/query.rs | 24 ++++----- 2 files changed, 59 insertions(+), 45 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 7025ac65f9525..74b0846314074 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -198,15 +198,12 @@ where ) -> Result<[>::Item; N], QueryEntityError> { self.update_archetypes(world); - // SAFE: query is read-only - unsafe { - self.get_multiple_unchecked_manual::( - world, - entities, - world.last_change_tick(), - world.read_change_tick(), - ) - } + self.get_multiple_read_only_manual( + world, + entities, + world.last_change_tick(), + world.read_change_tick(), + ) } /// Gets the query result for the given [`World`] and [`Entity`]. @@ -276,9 +273,7 @@ where ) -> Result<[>::Item; N], QueryEntityError> { self.update_archetypes(world); - verify_entities_unique(entities)?; - - // SAFE: method requires exclusive world access, and entities are checked for uniqueness + // SAFE: method requires exclusive world access unsafe { self.get_multiple_unchecked_manual::( world, @@ -366,15 +361,45 @@ where } } + /// Gets the read-only query results for the given [`World`] and array of [`Entity`], where the last change and + /// the current change tick are given. + pub(crate) fn get_multiple_read_only_manual<'s, 'w, const N: usize>( + &'s self, + world: &'w World, + entities: [Entity; N], + last_change_tick: u32, + change_tick: u32, + ) -> Result<[>::Item; N], QueryEntityError> { + // SAFE: fetch is read-only + let array_of_results = unsafe { + entities.map(|entity| { + self.get_unchecked_manual::( + world, + entity, + last_change_tick, + change_tick, + ) + }) + }; + + // If any of the entities were not present, return an error + for result in &array_of_results { + if let Err(QueryEntityError::NoSuchEntity(entity)) = result { + return Err(QueryEntityError::NoSuchEntity(*entity)); + } + } + + // Since we have verified that all entities are present, we can safely unwrap + Ok(array_of_results.map(|result| result.unwrap())) + } + /// Gets the query results for the given [`World`] and array of [`Entity`], where the last change and /// the current change tick are given. /// /// # Safety - /// This does not check for mutable query correctness. To be safe, make sure mutable queries - /// have unique access to the components they query. /// - /// If you are calling this method with a mutable query, you must verify that `entities` is free of duplicates: - /// use [`verify_entities_unique`] to do so efficiently for small `N`. + /// This does not check for unique access to subsets of the entity-component data. + /// To be safe, make sure mutable queries have unique access to the components they query. pub(crate) unsafe fn get_multiple_unchecked_manual< 's, 'w, @@ -387,6 +412,15 @@ where last_change_tick: u32, change_tick: u32, ) -> Result<[>::Item; N], QueryEntityError> { + // Verify that all entities are unique + for i in 0..N { + for j in 0..i { + if entities[i] == entities[j] { + return Err(QueryEntityError::AliasedMutability(entities[i])); + } + } + } + let array_of_results = entities.map(|entity| { self.get_unchecked_manual::(world, entity, last_change_tick, change_tick) }); @@ -896,17 +930,3 @@ pub enum QueryEntityError { #[error("The entity was requested mutably more than once.")] AliasedMutability(Entity), } - -#[inline] -pub(crate) fn verify_entities_unique( - entities: [Entity; N], -) -> Result<(), QueryEntityError> { - for i in 0..N { - for j in 0..i { - if entities[i] == entities[j] { - return Err(QueryEntityError::AliasedMutability(entities[i])); - } - } - } - Ok(()) -} diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 91c303f672423..f30b788ec268b 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -2,8 +2,8 @@ use crate::{ component::Component, entity::Entity, query::{ - verify_entities_unique, Fetch, FilterFetch, NopFetch, QueryCombinationIter, - QueryEntityError, QueryIter, QueryState, ReadOnlyFetch, WorldQuery, + Fetch, FilterFetch, NopFetch, QueryCombinationIter, QueryEntityError, QueryIter, + QueryState, ReadOnlyFetch, WorldQuery, }, world::{Mut, World}, }; @@ -631,16 +631,12 @@ where &self, entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { - // SAFE: Query is read-only - unsafe { - self.state - .get_multiple_unchecked_manual::( - self.world, - entities, - self.last_change_tick, - self.change_tick, - ) - } + self.state.get_multiple_read_only_manual( + self.world, + entities, + self.last_change_tick, + self.change_tick, + ) } /// Returns the read-only query items for the provided array of [`Entity`] @@ -738,9 +734,7 @@ where &mut self, entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { - verify_entities_unique(entities)?; - - // SAFE: scheduler ensures safe Query world access, and entities are checked for uniqueness + // SAFE: scheduler ensures safe Query world access unsafe { self.state.get_multiple_unchecked_manual::( self.world, From 0f0d8ae39197857b4db1b952b1623fe68db7c69a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 20:10:24 -0400 Subject: [PATCH 15/32] Use read-write Fetch type for get_multiple_unchecked_manual --- crates/bevy_ecs/src/query/state.rs | 13 ++++--------- crates/bevy_ecs/src/system/query.rs | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 74b0846314074..4cd1efbbaa472 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -275,7 +275,7 @@ where // SAFE: method requires exclusive world access unsafe { - self.get_multiple_unchecked_manual::( + self.get_multiple_unchecked_manual( world, entities, world.last_change_tick(), @@ -400,18 +400,13 @@ where /// /// This does not check for unique access to subsets of the entity-component data. /// To be safe, make sure mutable queries have unique access to the components they query. - pub(crate) unsafe fn get_multiple_unchecked_manual< - 's, - 'w, - QF: Fetch<'w, 's, State = Q::State>, - const N: usize, - >( + pub(crate) unsafe fn get_multiple_unchecked_manual<'s, 'w, const N: usize>( &'s self, world: &'w World, entities: [Entity; N], last_change_tick: u32, change_tick: u32, - ) -> Result<[>::Item; N], QueryEntityError> { + ) -> Result<[>::Item; N], QueryEntityError> { // Verify that all entities are unique for i in 0..N { for j in 0..i { @@ -422,7 +417,7 @@ where } let array_of_results = entities.map(|entity| { - self.get_unchecked_manual::(world, entity, last_change_tick, change_tick) + self.get_unchecked_manual::(world, entity, last_change_tick, change_tick) }); // If any of the entities were not present, return an error diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index f30b788ec268b..0e53955c36946 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -736,7 +736,7 @@ where ) -> Result<[>::Item; N], QueryEntityError> { // SAFE: scheduler ensures safe Query world access unsafe { - self.state.get_multiple_unchecked_manual::( + self.state.get_multiple_unchecked_manual( self.world, entities, self.last_change_tick, From f5bb37984d7da413402a82fc4c27be9b94045abf Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 22 Mar 2022 23:48:17 -0400 Subject: [PATCH 16/32] Add compile-fail tests to verify lifetimes --- ...system_query_get_multiple_mut_lifetime_safety.rs | 13 +++++++++++++ ...em_query_get_multiple_mut_lifetime_safety.stderr | 10 ++++++++++ 2 files changed, 23 insertions(+) create mode 100644 crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.rs create mode 100644 crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.stderr diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.rs b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.rs new file mode 100644 index 0000000000000..e98d6809ecf75 --- /dev/null +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.rs @@ -0,0 +1,13 @@ +use bevy_ecs::prelude::*; + +#[derive(Component)] +struct A(usize); + +fn system(mut query: Query<&mut A>, e: Res) { + let a1 = query.get_multiple_mut([*e, *e]).unwrap(); + let a2 = query.get_mut(*e).unwrap(); + // this should fail to compile + println!("{} {}", a1.0, a2.0); +} + +fn main() {} diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.stderr new file mode 100644 index 0000000000000..20101a46464b2 --- /dev/null +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.stderr @@ -0,0 +1,10 @@ +error[E0499]: cannot borrow `query` as mutable more than once at a time + --> tests/ui/system_query_get_multiple_mut_lifetime_safety.rs:8:14 + | +7 | let a1 = query.get_multiple_mut([*e, *e]).unwrap(); + | ----------------- first mutable borrow occurs here +8 | let a2 = query.get_mut(*e).unwrap(); + | ^^^^^^^^^^^^^^^^^ second mutable borrow occurs here +9 | // this should fail to compile +10 | println!("{} {}", a1.0, a2.0); + | -- first borrow later used here From 913178b66c6482bf58216bfe7181fd1de51e1e58 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 00:09:30 -0400 Subject: [PATCH 17/32] Fail to compile correctly --- .../tests/ui/system_query_get_multiple_mut_lifetime_safety.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.rs b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.rs index e98d6809ecf75..4a1219f4e8df6 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.rs +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.rs @@ -7,7 +7,7 @@ fn system(mut query: Query<&mut A>, e: Res) { let a1 = query.get_multiple_mut([*e, *e]).unwrap(); let a2 = query.get_mut(*e).unwrap(); // this should fail to compile - println!("{} {}", a1.0, a2.0); + println!("{} {}", a1[0].0, a2.0); } fn main() {} From b2e37862541bded5503a05206506c17ff5bfbe7f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 01:28:49 -0400 Subject: [PATCH 18/32] Blessed be the compiler error message --- .../ui/system_query_get_multiple_mut_lifetime_safety.stderr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.stderr index 20101a46464b2..4ef0380eeeae4 100644 --- a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.stderr +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_mut_lifetime_safety.stderr @@ -2,9 +2,9 @@ error[E0499]: cannot borrow `query` as mutable more than once at a time --> tests/ui/system_query_get_multiple_mut_lifetime_safety.rs:8:14 | 7 | let a1 = query.get_multiple_mut([*e, *e]).unwrap(); - | ----------------- first mutable borrow occurs here + | -------------------------------- first mutable borrow occurs here 8 | let a2 = query.get_mut(*e).unwrap(); | ^^^^^^^^^^^^^^^^^ second mutable borrow occurs here 9 | // this should fail to compile -10 | println!("{} {}", a1.0, a2.0); - | -- first borrow later used here +10 | println!("{} {}", a1[0].0, a2.0); + | ----- first borrow later used here From 7d1b5929c5c5a34d8299d04df6eca1ccf9b3cd36 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 15:56:20 -0400 Subject: [PATCH 19/32] Leave TODO Co-authored-by: Yilin Wei --- crates/bevy_ecs/src/query/state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 4cd1efbbaa472..9ed0e948e8a0d 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -382,6 +382,7 @@ where }) }; + // TODO: Replace with TryMap once https://github.com/rust-lang/rust/issues/79711 is stabilized // If any of the entities were not present, return an error for result in &array_of_results { if let Err(QueryEntityError::NoSuchEntity(entity)) = result { From a266f5ab292a3b2feecda5d7b135f267f74c57f8 Mon Sep 17 00:00:00 2001 From: Alice Date: Wed, 23 Mar 2022 16:33:36 -0400 Subject: [PATCH 20/32] Compile fail test for get_multiple --- .../ui/system_query_get_multiple_lifetime_safety.rs | 13 +++++++++++++ ...system_query_get_multiple_lifetime_safety.stderr | 10 ++++++++++ 2 files changed, 23 insertions(+) create mode 100644 crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_lifetime_safety.rs create mode 100644 crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_lifetime_safety.stderr diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_lifetime_safety.rs b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_lifetime_safety.rs new file mode 100644 index 0000000000000..d50bf4da82d24 --- /dev/null +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_lifetime_safety.rs @@ -0,0 +1,13 @@ +use bevy_ecs::prelude::*; + +#[derive(Component)] +struct A(usize); + +fn system(mut query: Query<&mut A>, e: Res) { + let a1 = query.get_multiple([*e, *e]).unwrap(); + let a2 = query.get_mut(*e).unwrap(); + // this should fail to compile + println!("{} {}", a1[0].0, a2.0); +} + +fn main() {} diff --git a/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_lifetime_safety.stderr b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_lifetime_safety.stderr new file mode 100644 index 0000000000000..229c88181040f --- /dev/null +++ b/crates/bevy_ecs_compile_fail_tests/tests/ui/system_query_get_multiple_lifetime_safety.stderr @@ -0,0 +1,10 @@ +error[E0502]: cannot borrow `query` as mutable because it is also borrowed as immutable + --> tests/ui/system_query_get_multiple_lifetime_safety.rs:8:14 + | +7 | let a1 = query.get_multiple([*e, *e]).unwrap(); + | ---------------------------- immutable borrow occurs here +8 | let a2 = query.get_mut(*e).unwrap(); + | ^^^^^^^^^^^^^^^^^ mutable borrow occurs here +9 | // this should fail to compile +10 | println!("{} {}", a1[0].0, a2.0); + | ----- immutable borrow later used here From f41213dd23357f182daa3d246844adcccee15514 Mon Sep 17 00:00:00 2001 From: Alice Date: Wed, 23 Mar 2022 17:09:11 -0400 Subject: [PATCH 21/32] Dedicated aliased mutability test --- crates/bevy_ecs/src/query/state.rs | 74 ++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 9ed0e948e8a0d..4d70267bb83f2 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -926,3 +926,77 @@ pub enum QueryEntityError { #[error("The entity was requested mutably more than once.")] AliasedMutability(Entity), } + +#[cfg(test)] +mod tests { + use crate::{prelude::*, query::QueryEntityError}; + + #[test] + fn get_multiple_unchecked_manual_uniqueness() { + let mut world = World::new(); + + let entities: Vec = (0..10).map(|_| world.spawn().id()).collect(); + + let query_state = world.query::(); + + // These don't matter for the test + let last_change_tick = world.last_change_tick(); + let change_tick = world.read_change_tick(); + + // It's best to test get_multiple_unchecked_manual directly, + // as it is shared and unsafe + // We don't care about aliased mutabilty for the read-only equivalent + assert!(unsafe { + query_state + .get_multiple_unchecked_manual::<10>( + &world, + entities.clone().try_into().unwrap(), + last_change_tick, + change_tick, + ) + .is_ok() + }); + + assert_eq!( + unsafe { + query_state + .get_multiple_unchecked_manual( + &world, + [entities[0], entities[0]], + last_change_tick, + change_tick, + ) + .unwrap_err() + }, + QueryEntityError::AliasedMutability(entities[0]) + ); + + assert_eq!( + unsafe { + query_state + .get_multiple_unchecked_manual( + &world, + [entities[0], entities[1], entities[0]], + last_change_tick, + change_tick, + ) + .unwrap_err() + }, + QueryEntityError::AliasedMutability(entities[0]) + ); + + assert_eq!( + unsafe { + query_state + .get_multiple_unchecked_manual( + &world, + [entities[9], entities[9]], + last_change_tick, + change_tick, + ) + .unwrap_err() + }, + QueryEntityError::AliasedMutability(entities[9]) + ); + } +} From 606c36e68850cba8e7242229cc49877b83983d19 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 20:58:44 -0400 Subject: [PATCH 22/32] Verify that we're working on the right World --- crates/bevy_ecs/src/query/state.rs | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 4d70267bb83f2..4f83cade7bedc 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -370,6 +370,8 @@ where last_change_tick: u32, change_tick: u32, ) -> Result<[>::Item; N], QueryEntityError> { + self.validate_world(world); + // SAFE: fetch is read-only let array_of_results = unsafe { entities.map(|entity| { @@ -408,6 +410,8 @@ where last_change_tick: u32, change_tick: u32, ) -> Result<[>::Item; N], QueryEntityError> { + self.validate_world(world); + // Verify that all entities are unique for i in 0..N { for j in 0..i { @@ -999,4 +1003,34 @@ mod tests { QueryEntityError::AliasedMutability(entities[9]) ); } + + #[test] + #[should_panic] + fn right_world_get() { + let mut world_1 = World::new(); + let world_2 = World::new(); + + let mut query_state = world_1.query::(); + let _panics = query_state.get(&world_2, Entity::from_raw(0)); + } + + #[test] + #[should_panic] + fn right_world_get_multiple() { + let mut world_1 = World::new(); + let world_2 = World::new(); + + let mut query_state = world_1.query::(); + let _panics = query_state.get_multiple(&world_2, []); + } + + #[test] + #[should_panic] + fn right_world_get_multiple_mut() { + let mut world_1 = World::new(); + let mut world_2 = World::new(); + + let mut query_state = world_1.query::(); + let _panics = query_state.get_multiple_mut(&mut world_2, []); + } } From 2edcb87302342d1f153eaa7fa929aed1c89f6db3 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 21:33:14 -0400 Subject: [PATCH 23/32] Remove from_raw from doc tests --- crates/bevy_ecs/src/query/state.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 4f83cade7bedc..27f2ad73233fb 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -170,13 +170,8 @@ where /// struct A(usize); /// /// let mut world = World::new(); - /// let mut entities: [Entity; 3] = [Entity::from_raw(0); 3]; - /// - /// world.spawn().insert(A(42)); - /// - /// for i in 0..3 { - /// entities[i] = world.spawn().insert(A(i)).id(); - /// } + /// let entity_vec: Vec = (0..3).map(|i|world.spawn().insert(A(i)).id()).collect(); + /// let entities: [Entity; 3] = entity_vec.try_into().unwrap(); /// /// world.spawn().insert(A(73)); /// @@ -238,13 +233,9 @@ where /// struct A(usize); /// /// let mut world = World::new(); - /// let mut entities: [Entity; 3] = [Entity::from_raw(0); 3]; /// - /// world.spawn().insert(A(42)); - /// - /// for i in 0..3 { - /// entities[i] = world.spawn().insert(A(i)).id(); - /// } + /// let entities: Vec = (0..3).map(|i|world.spawn().insert(A(i)).id()).collect(); + /// let entities: [Entity; 3] = entities.try_into().unwrap(); /// /// world.spawn().insert(A(73)); /// @@ -260,7 +251,7 @@ where /// /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// let wrong_entity = Entity::from_raw(365); + /// let wrong_entity = world.spawn().id(); /// /// assert_eq!(query_state.get_multiple_mut(&mut world, [wrong_entity]).unwrap_err(), QueryEntityError::NoSuchEntity(wrong_entity)); /// assert_eq!(query_state.get_multiple_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); From d2ad4fb280dbfd8557ef1e6d26c5dfaa9fa7c861 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 21:36:38 -0400 Subject: [PATCH 24/32] More robust error handling --- crates/bevy_ecs/src/query/state.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 27f2ad73233fb..48c6e55b6cffb 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -376,10 +376,11 @@ where }; // TODO: Replace with TryMap once https://github.com/rust-lang/rust/issues/79711 is stabilized - // If any of the entities were not present, return an error + // If any of the get calls failed, bubble up the error for result in &array_of_results { - if let Err(QueryEntityError::NoSuchEntity(entity)) = result { - return Err(QueryEntityError::NoSuchEntity(*entity)); + match result { + Ok(_) => (), + Err(error) => return Err(*error), } } @@ -416,10 +417,11 @@ where self.get_unchecked_manual::(world, entity, last_change_tick, change_tick) }); - // If any of the entities were not present, return an error + // If any of the get calls failed, bubble up the error for result in &array_of_results { - if let Err(QueryEntityError::NoSuchEntity(entity)) = result { - return Err(QueryEntityError::NoSuchEntity(*entity)); + match result { + Ok(_) => (), + Err(error) => return Err(*error), } } From 5871b10ac7f266a22dff470d8e77c6d589fd8bd4 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 21:52:42 -0400 Subject: [PATCH 25/32] Explicitly test for invalid entities --- crates/bevy_ecs/src/query/state.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 48c6e55b6cffb..0d110afe64902 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -251,9 +251,11 @@ where /// /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// let wrong_entity = world.spawn().id(); + /// let wrong_entity = World::from_raw(57); + /// let invalid_entity = world.spawn().id(); /// /// assert_eq!(query_state.get_multiple_mut(&mut world, [wrong_entity]).unwrap_err(), QueryEntityError::NoSuchEntity(wrong_entity)); + /// assert_eq!(query_state.get_multiple_mut(&mut world, [invalid_entity]).unwrap_err(), QueryEntityError::QueryDoesNotMatch); /// assert_eq!(query_state.get_multiple_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); /// ``` #[inline] From 1cd296333ac82bf7a6b7e5397ccd40b5b1f4b3af Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 21:53:51 -0400 Subject: [PATCH 26/32] Also return the Entity for `QueryEntityError::QueryDoesNotMatch` --- crates/bevy_ecs/src/query/state.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 0d110afe64902..1db1f5063a84f 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -338,7 +338,7 @@ where .matched_archetypes .contains(location.archetype_id.index()) { - return Err(QueryEntityError::QueryDoesNotMatch); + return Err(QueryEntityError::QueryDoesNotMatch(entity)); } let archetype = &world.archetypes[location.archetype_id]; let mut fetch = QF::init(world, &self.fetch_state, last_change_tick, change_tick); @@ -350,7 +350,7 @@ where if filter.archetype_filter_fetch(location.index) { Ok(fetch.archetype_fetch(location.index)) } else { - Err(QueryEntityError::QueryDoesNotMatch) + Err(QueryEntityError::QueryDoesNotMatch(entity)) } } @@ -919,7 +919,7 @@ where #[derive(Error, Debug, PartialEq, Clone, Copy)] pub enum QueryEntityError { #[error("The given entity does not have the requested component.")] - QueryDoesNotMatch, + QueryDoesNotMatch(Entity), #[error("The requested entity does not exist.")] NoSuchEntity(Entity), #[error("The entity was requested mutably more than once.")] From 22337a810335bafd99d072b3920c2b9ac2780acf Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 23 Mar 2022 23:09:55 -0400 Subject: [PATCH 27/32] Fix tests --- crates/bevy_ecs/src/query/state.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 1db1f5063a84f..e0c6470f0af2a 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -251,11 +251,11 @@ where /// /// assert_eq!(component_values, [&A(5), &A(6), &A(7)]); /// - /// let wrong_entity = World::from_raw(57); + /// let wrong_entity = Entity::from_raw(57); /// let invalid_entity = world.spawn().id(); /// /// assert_eq!(query_state.get_multiple_mut(&mut world, [wrong_entity]).unwrap_err(), QueryEntityError::NoSuchEntity(wrong_entity)); - /// assert_eq!(query_state.get_multiple_mut(&mut world, [invalid_entity]).unwrap_err(), QueryEntityError::QueryDoesNotMatch); + /// assert_eq!(query_state.get_multiple_mut(&mut world, [invalid_entity]).unwrap_err(), QueryEntityError::QueryDoesNotMatch(invalid_entity)); /// assert_eq!(query_state.get_multiple_mut(&mut world, [entities[0], entities[0]]).unwrap_err(), QueryEntityError::AliasedMutability(entities[0])); /// ``` #[inline] From 86f1521d5e372d638735f40a0b3b1326ff18a8e0 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 29 Mar 2022 20:57:31 -0400 Subject: [PATCH 28/32] Move world validation into `QueryState::get_mulitple_mut` --- crates/bevy_ecs/src/query/state.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e0c6470f0af2a..de821fd21f780 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -265,8 +265,10 @@ where entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { self.update_archetypes(world); + self.validate_world(world); // SAFE: method requires exclusive world access + // and world has been validated unsafe { self.get_multiple_unchecked_manual( world, @@ -323,6 +325,9 @@ where /// /// This does not check for mutable query correctness. To be safe, make sure mutable queries /// have unique access to the components they query. + /// + /// This must be called on the same `World` that the `Query` was generated from: + /// use `QueryState::validate_world` to verify this. pub(crate) unsafe fn get_unchecked_manual<'w, 's, QF: Fetch<'w, 's, State = Q::State>>( &'s self, world: &'w World, @@ -397,6 +402,9 @@ where /// /// This does not check for unique access to subsets of the entity-component data. /// To be safe, make sure mutable queries have unique access to the components they query. + /// + /// This must be called on the same `World` that the `Query` was generated from: + /// use `QueryState::validate_world` to verify this. pub(crate) unsafe fn get_multiple_unchecked_manual<'s, 'w, const N: usize>( &'s self, world: &'w World, @@ -404,8 +412,6 @@ where last_change_tick: u32, change_tick: u32, ) -> Result<[>::Item; N], QueryEntityError> { - self.validate_world(world); - // Verify that all entities are unique for i in 0..N { for j in 0..i { From be22235290fd34b4d5e139d68a0ef982761f2007 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 29 Mar 2022 21:24:20 -0400 Subject: [PATCH 29/32] World is already validated by update_archetypes call --- crates/bevy_ecs/src/query/state.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index de821fd21f780..7c4bef3f9bf41 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -265,10 +265,9 @@ where entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { self.update_archetypes(world); - self.validate_world(world); // SAFE: method requires exclusive world access - // and world has been validated + // and world has been validated via update_archetypes unsafe { self.get_multiple_unchecked_manual( world, From f7aa7416e8d832c83d761e8fc55fc07d718a0582 Mon Sep 17 00:00:00 2001 From: Alice Date: Tue, 29 Mar 2022 21:43:12 -0400 Subject: [PATCH 30/32] Avoid repeated calls to validate_world in Query::get_multiple --- crates/bevy_ecs/src/query/state.rs | 41 +++++++++++++++++------------ crates/bevy_ecs/src/system/query.rs | 15 ++++++----- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 7c4bef3f9bf41..720dc4d88b2c2 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -193,12 +193,15 @@ where ) -> Result<[>::Item; N], QueryEntityError> { self.update_archetypes(world); - self.get_multiple_read_only_manual( - world, - entities, - world.last_change_tick(), - world.read_change_tick(), - ) + // SAFE: update_archetypes validates the `World` matches + unsafe { + self.get_multiple_read_only_manual( + world, + entities, + world.last_change_tick(), + world.read_change_tick(), + ) + } } /// Gets the query result for the given [`World`] and [`Entity`]. @@ -360,7 +363,12 @@ where /// Gets the read-only query results for the given [`World`] and array of [`Entity`], where the last change and /// the current change tick are given. - pub(crate) fn get_multiple_read_only_manual<'s, 'w, const N: usize>( + /// + /// # Safety + /// + /// This must be called on the same `World` that the `Query` was generated from: + /// use `QueryState::validate_world` to verify this. + pub(crate) unsafe fn get_multiple_read_only_manual<'s, 'w, const N: usize>( &'s self, world: &'w World, entities: [Entity; N], @@ -370,16 +378,15 @@ where self.validate_world(world); // SAFE: fetch is read-only - let array_of_results = unsafe { - entities.map(|entity| { - self.get_unchecked_manual::( - world, - entity, - last_change_tick, - change_tick, - ) - }) - }; + // and world must be validated + let array_of_results = entities.map(|entity| { + self.get_unchecked_manual::( + world, + entity, + last_change_tick, + change_tick, + ) + }); // TODO: Replace with TryMap once https://github.com/rust-lang/rust/issues/79711 is stabilized // If any of the get calls failed, bubble up the error diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 0e53955c36946..7fcaf72a0ac57 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -631,12 +631,15 @@ where &self, entities: [Entity; N], ) -> Result<[>::Item; N], QueryEntityError> { - self.state.get_multiple_read_only_manual( - self.world, - entities, - self.last_change_tick, - self.change_tick, - ) + // SAFE: it is the scheduler's responsibility to ensure that `Query` is never handed out on the wrong `World`. + unsafe { + self.state.get_multiple_read_only_manual( + self.world, + entities, + self.last_change_tick, + self.change_tick, + ) + } } /// Returns the read-only query items for the provided array of [`Entity`] From 141c01d1b41894aed2ffd6b3e841d9caf9ae9072 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Tue, 29 Mar 2022 22:25:26 -0400 Subject: [PATCH 31/32] Yeet validation Co-authored-by: Boxy --- crates/bevy_ecs/src/query/state.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 720dc4d88b2c2..804d0ccb400c0 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -375,7 +375,6 @@ where last_change_tick: u32, change_tick: u32, ) -> Result<[>::Item; N], QueryEntityError> { - self.validate_world(world); // SAFE: fetch is read-only // and world must be validated From c0e5903d35180d12230326b6be6552dc81c55fd5 Mon Sep 17 00:00:00 2001 From: Alice Date: Tue, 29 Mar 2022 22:49:54 -0400 Subject: [PATCH 32/32] Cargo fmt --- crates/bevy_ecs/src/query/state.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 804d0ccb400c0..8327d82648ff0 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -375,7 +375,6 @@ where last_change_tick: u32, change_tick: u32, ) -> Result<[>::Item; N], QueryEntityError> { - // SAFE: fetch is read-only // and world must be validated let array_of_results = entities.map(|entity| {