Improve or-with disjoint checks (#7085)
# Objective
This PR attempts to improve query compatibility checks in scenarios
involving `Or` filters.
Currently, for the following two disjoint queries, Bevy will throw a
panic:
```
fn sys(_: Query<&mut C, Or<(With<A>, With<B>)>>, _: Query<&mut C, (Without<A>, Without<B>)>) {}
``` 
This PR addresses this particular scenario.
## Solution
`FilteredAccess::with` now stores a vector of `AccessFilters`
(representing a pair of `with` and `without` bitsets), where each member
represents an `Or` "variant".
Filters like `(With<A>, Or<(With<B>, Without<C>)>` are expected to be
expanded into `A * B + A * !C`.
When calculating whether queries are compatible, every `AccessFilters`
of a query is tested for incompatibility with every `AccessFilters` of
another query.
---
## Changelog
- Improved system and query data access compatibility checks in
scenarios involving `Or` filters
---------
Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
			
			
This commit is contained in:
		
							parent
							
								
									c488b7089c
								
							
						
					
					
						commit
						71fccb2897
					
				| @ -25,6 +25,7 @@ struct FormattedBitSet<'a, T: SparseSetIndex> { | |||||||
|     bit_set: &'a FixedBitSet, |     bit_set: &'a FixedBitSet, | ||||||
|     _marker: PhantomData<T>, |     _marker: PhantomData<T>, | ||||||
| } | } | ||||||
|  | 
 | ||||||
| impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> { | impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> { | ||||||
|     fn new(bit_set: &'a FixedBitSet) -> Self { |     fn new(bit_set: &'a FixedBitSet) -> Self { | ||||||
|         Self { |         Self { | ||||||
| @ -33,6 +34,7 @@ impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
| impl<'a, T: SparseSetIndex + fmt::Debug> fmt::Debug for FormattedBitSet<'a, T> { | impl<'a, T: SparseSetIndex + fmt::Debug> fmt::Debug for FormattedBitSet<'a, T> { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|         f.debug_list() |         f.debug_list() | ||||||
| @ -69,6 +71,7 @@ impl<T: SparseSetIndex + fmt::Debug> fmt::Debug for Access<T> { | |||||||
|             .finish() |             .finish() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
| impl<T: SparseSetIndex> Default for Access<T> { | impl<T: SparseSetIndex> Default for Access<T> { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self::new() |         Self::new() | ||||||
| @ -213,31 +216,22 @@ impl<T: SparseSetIndex> Access<T> { | |||||||
| /// is read/write `T`, read `U`. It must still have a read `U` access otherwise the following
 | /// is read/write `T`, read `U`. It must still have a read `U` access otherwise the following
 | ||||||
| /// queries would be incorrectly considered disjoint:
 | /// queries would be incorrectly considered disjoint:
 | ||||||
| /// - `Query<&mut T>`  read/write `T`
 | /// - `Query<&mut T>`  read/write `T`
 | ||||||
| /// - `Query<Option<&T>` accesses nothing
 | /// - `Query<Option<&T>>` accesses nothing
 | ||||||
| ///
 | ///
 | ||||||
| /// See comments the `WorldQuery` impls of `AnyOf`/`Option`/`Or` for more information.
 | /// See comments the `WorldQuery` impls of `AnyOf`/`Option`/`Or` for more information.
 | ||||||
| #[derive(Clone, Eq, PartialEq)] | #[derive(Debug, Clone, Eq, PartialEq)] | ||||||
| pub struct FilteredAccess<T: SparseSetIndex> { | pub struct FilteredAccess<T: SparseSetIndex> { | ||||||
|     access: Access<T>, |     access: Access<T>, | ||||||
|     with: FixedBitSet, |     // An array of filter sets to express `With` or `Without` clauses in disjunctive normal form, for example: `Or<(With<A>, With<B>)>`.
 | ||||||
|     without: FixedBitSet, |     // Filters like `(With<A>, Or<(With<B>, Without<C>)>` are expanded into `Or<((With<A>, With<B>), (With<A>, Without<C>))>`.
 | ||||||
| } |     filter_sets: Vec<AccessFilters<T>>, | ||||||
| impl<T: SparseSetIndex + fmt::Debug> fmt::Debug for FilteredAccess<T> { |  | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |  | ||||||
|         f.debug_struct("FilteredAccess") |  | ||||||
|             .field("access", &self.access) |  | ||||||
|             .field("with", &FormattedBitSet::<T>::new(&self.with)) |  | ||||||
|             .field("without", &FormattedBitSet::<T>::new(&self.without)) |  | ||||||
|             .finish() |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: SparseSetIndex> Default for FilteredAccess<T> { | impl<T: SparseSetIndex> Default for FilteredAccess<T> { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             access: Access::default(), |             access: Access::default(), | ||||||
|             with: Default::default(), |             filter_sets: vec![AccessFilters::default()], | ||||||
|             without: Default::default(), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -266,30 +260,46 @@ impl<T: SparseSetIndex> FilteredAccess<T> { | |||||||
|     /// Adds access to the element given by `index`.
 |     /// Adds access to the element given by `index`.
 | ||||||
|     pub fn add_read(&mut self, index: T) { |     pub fn add_read(&mut self, index: T) { | ||||||
|         self.access.add_read(index.clone()); |         self.access.add_read(index.clone()); | ||||||
|         self.add_with(index); |         self.and_with(index); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Adds exclusive access to the element given by `index`.
 |     /// Adds exclusive access to the element given by `index`.
 | ||||||
|     pub fn add_write(&mut self, index: T) { |     pub fn add_write(&mut self, index: T) { | ||||||
|         self.access.add_write(index.clone()); |         self.access.add_write(index.clone()); | ||||||
|         self.add_with(index); |         self.and_with(index); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Retains only combinations where the element given by `index` is also present.
 |     /// Adds a `With` filter: corresponds to a conjunction (AND) operation.
 | ||||||
|     pub fn add_with(&mut self, index: T) { |     ///
 | ||||||
|         self.with.grow(index.sparse_set_index() + 1); |     /// Suppose we begin with `Or<(With<A>, With<B>)>`, which is represented by an array of two `AccessFilter` instances.
 | ||||||
|         self.with.insert(index.sparse_set_index()); |     /// Adding `AND With<C>` via this method transforms it into the equivalent of  `Or<((With<A>, With<C>), (With<B>, With<C>))>`.
 | ||||||
|  |     pub fn and_with(&mut self, index: T) { | ||||||
|  |         let index = index.sparse_set_index(); | ||||||
|  |         for filter in &mut self.filter_sets { | ||||||
|  |             filter.with.grow(index + 1); | ||||||
|  |             filter.with.insert(index); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Retains only combinations where the element given by `index` is not present.
 |     /// Adds a `Without` filter: corresponds to a conjunction (AND) operation.
 | ||||||
|     pub fn add_without(&mut self, index: T) { |     ///
 | ||||||
|         self.without.grow(index.sparse_set_index() + 1); |     /// Suppose we begin with `Or<(With<A>, With<B>)>`, which is represented by an array of two `AccessFilter` instances.
 | ||||||
|         self.without.insert(index.sparse_set_index()); |     /// Adding `AND Without<C>` via this method transforms it into the equivalent of  `Or<((With<A>, Without<C>), (With<B>, Without<C>))>`.
 | ||||||
|  |     pub fn and_without(&mut self, index: T) { | ||||||
|  |         let index = index.sparse_set_index(); | ||||||
|  |         for filter in &mut self.filter_sets { | ||||||
|  |             filter.without.grow(index + 1); | ||||||
|  |             filter.without.insert(index); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn extend_intersect_filter(&mut self, other: &FilteredAccess<T>) { |     /// Appends an array of filters: corresponds to a disjunction (OR) operation.
 | ||||||
|         self.without.intersect_with(&other.without); |     ///
 | ||||||
|         self.with.intersect_with(&other.with); |     /// As the underlying array of filters represents a disjunction,
 | ||||||
|  |     /// where each element (`AccessFilters`) represents a conjunction,
 | ||||||
|  |     /// we can simply append to the array.
 | ||||||
|  |     pub fn append_or(&mut self, other: &FilteredAccess<T>) { | ||||||
|  |         self.filter_sets.append(&mut other.filter_sets.clone()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn extend_access(&mut self, other: &FilteredAccess<T>) { |     pub fn extend_access(&mut self, other: &FilteredAccess<T>) { | ||||||
| @ -298,9 +308,23 @@ impl<T: SparseSetIndex> FilteredAccess<T> { | |||||||
| 
 | 
 | ||||||
|     /// Returns `true` if this and `other` can be active at the same time.
 |     /// Returns `true` if this and `other` can be active at the same time.
 | ||||||
|     pub fn is_compatible(&self, other: &FilteredAccess<T>) -> bool { |     pub fn is_compatible(&self, other: &FilteredAccess<T>) -> bool { | ||||||
|         self.access.is_compatible(&other.access) |         if self.access.is_compatible(&other.access) { | ||||||
|             || !self.with.is_disjoint(&other.without) |             return true; | ||||||
|             || !other.with.is_disjoint(&self.without) |         } | ||||||
|  | 
 | ||||||
|  |         // If the access instances are incompatible, we want to check that whether filters can
 | ||||||
|  |         // guarantee that queries are disjoint.
 | ||||||
|  |         // Since the `filter_sets` array represents a Disjunctive Normal Form formula ("ORs of ANDs"),
 | ||||||
|  |         // we need to make sure that each filter set (ANDs) rule out every filter set from the `other` instance.
 | ||||||
|  |         //
 | ||||||
|  |         // For example, `Query<&mut C, Or<(With<A>, Without<B>)>>` is compatible `Query<&mut C, (With<B>, Without<A>)>`,
 | ||||||
|  |         // but `Query<&mut C, Or<(Without<A>, Without<B>)>>` isn't compatible with `Query<&mut C, Or<(With<A>, With<B>)>>`.
 | ||||||
|  |         self.filter_sets.iter().all(|filter| { | ||||||
|  |             other | ||||||
|  |                 .filter_sets | ||||||
|  |                 .iter() | ||||||
|  |                 .all(|other_filter| filter.is_ruled_out_by(other_filter)) | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Returns a vector of elements that this and `other` cannot access at the same time.
 |     /// Returns a vector of elements that this and `other` cannot access at the same time.
 | ||||||
| @ -313,10 +337,34 @@ impl<T: SparseSetIndex> FilteredAccess<T> { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Adds all access and filters from `other`.
 |     /// Adds all access and filters from `other`.
 | ||||||
|     pub fn extend(&mut self, access: &FilteredAccess<T>) { |     ///
 | ||||||
|         self.access.extend(&access.access); |     /// Corresponds to a conjunction operation (AND) for filters.
 | ||||||
|         self.with.union_with(&access.with); |     ///
 | ||||||
|         self.without.union_with(&access.without); |     /// Extending `Or<(With<A>, Without<B>)>` with `Or<(With<C>, Without<D>)>` will result in
 | ||||||
|  |     /// `Or<((With<A>, With<C>), (With<A>, Without<D>), (Without<B>, With<C>), (Without<B>, Without<D>))>`.
 | ||||||
|  |     pub fn extend(&mut self, other: &FilteredAccess<T>) { | ||||||
|  |         self.access.extend(&other.access); | ||||||
|  | 
 | ||||||
|  |         // We can avoid allocating a new array of bitsets if `other` contains just a single set of filters:
 | ||||||
|  |         // in this case we can short-circuit by performing an in-place union for each bitset.
 | ||||||
|  |         if other.filter_sets.len() == 1 { | ||||||
|  |             for filter in &mut self.filter_sets { | ||||||
|  |                 filter.with.union_with(&other.filter_sets[0].with); | ||||||
|  |                 filter.without.union_with(&other.filter_sets[0].without); | ||||||
|  |             } | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let mut new_filters = Vec::with_capacity(self.filter_sets.len() * other.filter_sets.len()); | ||||||
|  |         for filter in &self.filter_sets { | ||||||
|  |             for other_filter in &other.filter_sets { | ||||||
|  |                 let mut new_filter = filter.clone(); | ||||||
|  |                 new_filter.with.union_with(&other_filter.with); | ||||||
|  |                 new_filter.without.union_with(&other_filter.without); | ||||||
|  |                 new_filters.push(new_filter); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         self.filter_sets = new_filters; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Sets the underlying unfiltered access as having access to all indexed elements.
 |     /// Sets the underlying unfiltered access as having access to all indexed elements.
 | ||||||
| @ -325,6 +373,43 @@ impl<T: SparseSetIndex> FilteredAccess<T> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Clone, Eq, PartialEq)] | ||||||
|  | struct AccessFilters<T> { | ||||||
|  |     with: FixedBitSet, | ||||||
|  |     without: FixedBitSet, | ||||||
|  |     _index_type: PhantomData<T>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: SparseSetIndex + fmt::Debug> fmt::Debug for AccessFilters<T> { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|  |         f.debug_struct("AccessFilters") | ||||||
|  |             .field("with", &FormattedBitSet::<T>::new(&self.with)) | ||||||
|  |             .field("without", &FormattedBitSet::<T>::new(&self.without)) | ||||||
|  |             .finish() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: SparseSetIndex> Default for AccessFilters<T> { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self { | ||||||
|  |             with: FixedBitSet::default(), | ||||||
|  |             without: FixedBitSet::default(), | ||||||
|  |             _index_type: PhantomData, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: SparseSetIndex> AccessFilters<T> { | ||||||
|  |     fn is_ruled_out_by(&self, other: &Self) -> bool { | ||||||
|  |         // Although not technically complete, we don't consider the case when `AccessFilters`'s
 | ||||||
|  |         // `without` bitset contradicts its own `with` bitset (e.g. `(With<A>, Without<A>)`).
 | ||||||
|  |         // Such query would be considered compatible with any other query, but as it's almost
 | ||||||
|  |         // always an error, we ignore this case instead of treating such query as compatible
 | ||||||
|  |         // with others.
 | ||||||
|  |         !self.with.is_disjoint(&other.without) || !self.without.is_disjoint(&other.with) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// A collection of [`FilteredAccess`] instances.
 | /// A collection of [`FilteredAccess`] instances.
 | ||||||
| ///
 | ///
 | ||||||
| /// Used internally to statically check if systems have conflicting access.
 | /// Used internally to statically check if systems have conflicting access.
 | ||||||
| @ -441,7 +526,10 @@ impl<T: SparseSetIndex> Default for FilteredAccessSet<T> { | |||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|  |     use crate::query::access::AccessFilters; | ||||||
|     use crate::query::{Access, FilteredAccess, FilteredAccessSet}; |     use crate::query::{Access, FilteredAccess, FilteredAccessSet}; | ||||||
|  |     use fixedbitset::FixedBitSet; | ||||||
|  |     use std::marker::PhantomData; | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn read_all_access_conflicts() { |     fn read_all_access_conflicts() { | ||||||
| @ -514,22 +602,67 @@ mod tests { | |||||||
|         let mut access_a = FilteredAccess::<usize>::default(); |         let mut access_a = FilteredAccess::<usize>::default(); | ||||||
|         access_a.add_read(0); |         access_a.add_read(0); | ||||||
|         access_a.add_read(1); |         access_a.add_read(1); | ||||||
|         access_a.add_with(2); |         access_a.and_with(2); | ||||||
| 
 | 
 | ||||||
|         let mut access_b = FilteredAccess::<usize>::default(); |         let mut access_b = FilteredAccess::<usize>::default(); | ||||||
|         access_b.add_read(0); |         access_b.add_read(0); | ||||||
|         access_b.add_write(3); |         access_b.add_write(3); | ||||||
|         access_b.add_without(4); |         access_b.and_without(4); | ||||||
| 
 | 
 | ||||||
|         access_a.extend(&access_b); |         access_a.extend(&access_b); | ||||||
| 
 | 
 | ||||||
|         let mut expected = FilteredAccess::<usize>::default(); |         let mut expected = FilteredAccess::<usize>::default(); | ||||||
|         expected.add_read(0); |         expected.add_read(0); | ||||||
|         expected.add_read(1); |         expected.add_read(1); | ||||||
|         expected.add_with(2); |         expected.and_with(2); | ||||||
|         expected.add_write(3); |         expected.add_write(3); | ||||||
|         expected.add_without(4); |         expected.and_without(4); | ||||||
| 
 | 
 | ||||||
|         assert!(access_a.eq(&expected)); |         assert!(access_a.eq(&expected)); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn filtered_access_extend_or() { | ||||||
|  |         let mut access_a = FilteredAccess::<usize>::default(); | ||||||
|  |         // Exclusive access to `(&mut A, &mut B)`.
 | ||||||
|  |         access_a.add_write(0); | ||||||
|  |         access_a.add_write(1); | ||||||
|  | 
 | ||||||
|  |         // Filter by `With<C>`.
 | ||||||
|  |         let mut access_b = FilteredAccess::<usize>::default(); | ||||||
|  |         access_b.and_with(2); | ||||||
|  | 
 | ||||||
|  |         // Filter by `(With<D>, Without<E>)`.
 | ||||||
|  |         let mut access_c = FilteredAccess::<usize>::default(); | ||||||
|  |         access_c.and_with(3); | ||||||
|  |         access_c.and_without(4); | ||||||
|  | 
 | ||||||
|  |         // Turns `access_b` into `Or<(With<C>, (With<D>, Without<D>))>`.
 | ||||||
|  |         access_b.append_or(&access_c); | ||||||
|  |         // Applies the filters to the initial query, which corresponds to the FilteredAccess'
 | ||||||
|  |         // representation of `Query<(&mut A, &mut B), Or<(With<C>, (With<D>, Without<E>))>>`.
 | ||||||
|  |         access_a.extend(&access_b); | ||||||
|  | 
 | ||||||
|  |         // Construct the expected `FilteredAccess` struct.
 | ||||||
|  |         // The intention here is to test that exclusive access implied by `add_write`
 | ||||||
|  |         // forms correct normalized access structs when extended with `Or` filters.
 | ||||||
|  |         let mut expected = FilteredAccess::<usize>::default(); | ||||||
|  |         expected.add_write(0); | ||||||
|  |         expected.add_write(1); | ||||||
|  |         // The resulted access is expected to represent `Or<((With<A>, With<B>, With<C>), (With<A>, With<B>, With<D>, Without<E>))>`.
 | ||||||
|  |         expected.filter_sets = vec![ | ||||||
|  |             AccessFilters { | ||||||
|  |                 with: FixedBitSet::with_capacity_and_blocks(3, [0b111]), | ||||||
|  |                 without: FixedBitSet::default(), | ||||||
|  |                 _index_type: PhantomData, | ||||||
|  |             }, | ||||||
|  |             AccessFilters { | ||||||
|  |                 with: FixedBitSet::with_capacity_and_blocks(4, [0b1011]), | ||||||
|  |                 without: FixedBitSet::with_capacity_and_blocks(5, [0b10000]), | ||||||
|  |                 _index_type: PhantomData, | ||||||
|  |             }, | ||||||
|  |         ]; | ||||||
|  | 
 | ||||||
|  |         assert_eq!(access_a, expected); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1281,34 +1281,21 @@ macro_rules! impl_anytuple_fetch { | |||||||
|             fn update_component_access(state: &Self::State, _access: &mut FilteredAccess<ComponentId>) { |             fn update_component_access(state: &Self::State, _access: &mut FilteredAccess<ComponentId>) { | ||||||
|                 let ($($name,)*) = state; |                 let ($($name,)*) = state; | ||||||
| 
 | 
 | ||||||
|                 // We do not unconditionally add `$name`'s `with`/`without` accesses to `_access`
 |                 let mut _new_access = _access.clone(); | ||||||
|                 // as this would be unsound. For example the following two queries should conflict:
 |  | ||||||
|                 // - Query<(AnyOf<(&A, ())>, &mut B)>
 |  | ||||||
|                 // - Query<&mut B, Without<A>>
 |  | ||||||
|                 //
 |  | ||||||
|                 // If we were to unconditionally add `$name`'s `with`/`without` accesses then `AnyOf<(&A, ())>`
 |  | ||||||
|                 // would have a `With<A>` access which is incorrect as this `WorldQuery` will match entities that
 |  | ||||||
|                 // do not have the `A` component. This is the same logic as the `Or<...>: WorldQuery` impl.
 |  | ||||||
|                 //
 |  | ||||||
|                 // The correct thing to do here is to only add a `with`/`without` access to `_access` if all
 |  | ||||||
|                 // `$name` params have that `with`/`without` access. More jargony put- we add the intersection
 |  | ||||||
|                 // of all `with`/`without` accesses of the `$name` params to `_access`.
 |  | ||||||
|                 let mut _intersected_access = _access.clone(); |  | ||||||
|                 let mut _not_first = false; |                 let mut _not_first = false; | ||||||
|                 $( |                 $( | ||||||
|                     if _not_first { |                     if _not_first { | ||||||
|                         let mut intermediate = _access.clone(); |                         let mut intermediate = _access.clone(); | ||||||
|                         $name::update_component_access($name, &mut intermediate); |                         $name::update_component_access($name, &mut intermediate); | ||||||
|                         _intersected_access.extend_intersect_filter(&intermediate); |                         _new_access.append_or(&intermediate); | ||||||
|                         _intersected_access.extend_access(&intermediate); |                         _new_access.extend_access(&intermediate); | ||||||
|                     } else { |                     } else { | ||||||
| 
 |                         $name::update_component_access($name, &mut _new_access); | ||||||
|                         $name::update_component_access($name, &mut _intersected_access); |  | ||||||
|                         _not_first = true; |                         _not_first = true; | ||||||
|                     } |                     } | ||||||
|                 )* |                 )* | ||||||
| 
 | 
 | ||||||
|                 *_access = _intersected_access; |                 *_access = _new_access; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             fn update_archetype_component_access(state: &Self::State, _archetype: &Archetype, _access: &mut Access<ArchetypeComponentId>) { |             fn update_archetype_component_access(state: &Self::State, _archetype: &Archetype, _access: &mut Access<ArchetypeComponentId>) { | ||||||
|  | |||||||
| @ -86,7 +86,7 @@ unsafe impl<T: Component> WorldQuery for With<T> { | |||||||
| 
 | 
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) { |     fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) { | ||||||
|         access.add_with(id); |         access.and_with(id); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[inline] |     #[inline] | ||||||
| @ -183,7 +183,7 @@ unsafe impl<T: Component> WorldQuery for Without<T> { | |||||||
| 
 | 
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) { |     fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) { | ||||||
|         access.add_without(id); |         access.and_without(id); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[inline] |     #[inline] | ||||||
| @ -339,33 +339,21 @@ macro_rules! impl_query_filter_tuple { | |||||||
|             fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) { |             fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) { | ||||||
|                 let ($($filter,)*) = state; |                 let ($($filter,)*) = state; | ||||||
| 
 | 
 | ||||||
|                 // We do not unconditionally add `$filter`'s `with`/`without` accesses to `access`
 |                 let mut _new_access = access.clone(); | ||||||
|                 // as this would be unsound. For example the following two queries should conflict:
 |  | ||||||
|                 // - Query<&mut B, Or<(With<A>, ())>>
 |  | ||||||
|                 // - Query<&mut B, Without<A>>
 |  | ||||||
|                 //
 |  | ||||||
|                 // If we were to unconditionally add `$name`'s `with`/`without` accesses then `Or<(With<A>, ())>`
 |  | ||||||
|                 // would have a `With<A>` access which is incorrect as this `WorldQuery` will match entities that
 |  | ||||||
|                 // do not have the `A` component. This is the same logic as the `AnyOf<...>: WorldQuery` impl.
 |  | ||||||
|                 //
 |  | ||||||
|                 // The correct thing to do here is to only add a `with`/`without` access to `_access` if all
 |  | ||||||
|                 // `$filter` params have that `with`/`without` access. More jargony put- we add the intersection
 |  | ||||||
|                 // of all `with`/`without` accesses of the `$filter` params to `access`.
 |  | ||||||
|                 let mut _intersected_access = access.clone(); |  | ||||||
|                 let mut _not_first = false; |                 let mut _not_first = false; | ||||||
|                 $( |                 $( | ||||||
|                     if _not_first { |                     if _not_first { | ||||||
|                         let mut intermediate = access.clone(); |                         let mut intermediate = access.clone(); | ||||||
|                         $filter::update_component_access($filter, &mut intermediate); |                         $filter::update_component_access($filter, &mut intermediate); | ||||||
|                         _intersected_access.extend_intersect_filter(&intermediate); |                         _new_access.append_or(&intermediate); | ||||||
|                         _intersected_access.extend_access(&intermediate); |                         _new_access.extend_access(&intermediate); | ||||||
|                     } else { |                     } else { | ||||||
|                         $filter::update_component_access($filter, &mut _intersected_access); |                         $filter::update_component_access($filter, &mut _new_access); | ||||||
|                         _not_first = true; |                         _not_first = true; | ||||||
|                     } |                     } | ||||||
|                 )* |                 )* | ||||||
| 
 | 
 | ||||||
|                 *access = _intersected_access; |                 *access = _new_access; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             fn update_archetype_component_access(state: &Self::State, archetype: &Archetype, access: &mut Access<ArchetypeComponentId>) { |             fn update_archetype_component_access(state: &Self::State, archetype: &Archetype, access: &mut Access<ArchetypeComponentId>) { | ||||||
|  | |||||||
| @ -483,6 +483,13 @@ mod tests { | |||||||
|         run_system(&mut world, sys); |         run_system(&mut world, sys); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn any_of_and_without() { | ||||||
|  |         fn sys(_: Query<(AnyOf<(&A, &B)>, &mut C)>, _: Query<&mut C, (Without<A>, Without<B>)>) {} | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     #[should_panic = "error[B0001]"] |     #[should_panic = "error[B0001]"] | ||||||
|     fn or_has_no_filter_with() { |     fn or_has_no_filter_with() { | ||||||
| @ -498,6 +505,113 @@ mod tests { | |||||||
|         run_system(&mut world, sys); |         run_system(&mut world, sys); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn or_has_filter_with() { | ||||||
|  |         fn sys( | ||||||
|  |             _: Query<&mut C, Or<(With<A>, With<B>)>>, | ||||||
|  |             _: Query<&mut C, (Without<A>, Without<B>)>, | ||||||
|  |         ) { | ||||||
|  |         } | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn or_expanded_with_and_without_common() { | ||||||
|  |         fn sys(_: Query<&mut D, (With<A>, Or<(With<B>, With<C>)>)>, _: Query<&mut D, Without<A>>) {} | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn or_expanded_nested_with_and_without_common() { | ||||||
|  |         fn sys( | ||||||
|  |             _: Query<&mut E, (Or<((With<B>, With<C>), (With<C>, With<D>))>, With<A>)>, | ||||||
|  |             _: Query<&mut E, (Without<B>, Without<D>)>, | ||||||
|  |         ) { | ||||||
|  |         } | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[should_panic = "error[B0001]"] | ||||||
|  |     fn or_expanded_nested_with_and_disjoint_without() { | ||||||
|  |         fn sys( | ||||||
|  |             _: Query<&mut E, (Or<((With<B>, With<C>), (With<C>, With<D>))>, With<A>)>, | ||||||
|  |             _: Query<&mut E, Without<D>>, | ||||||
|  |         ) { | ||||||
|  |         } | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[should_panic = "error[B0001]"] | ||||||
|  |     fn or_expanded_nested_or_with_and_disjoint_without() { | ||||||
|  |         fn sys( | ||||||
|  |             _: Query<&mut D, Or<(Or<(With<A>, With<B>)>, Or<(With<A>, With<C>)>)>>, | ||||||
|  |             _: Query<&mut D, Without<A>>, | ||||||
|  |         ) { | ||||||
|  |         } | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn or_expanded_nested_with_and_common_nested_without() { | ||||||
|  |         fn sys( | ||||||
|  |             _: Query<&mut D, Or<((With<A>, With<B>), (With<B>, With<C>))>>, | ||||||
|  |             _: Query<&mut D, Or<(Without<D>, Without<B>)>>, | ||||||
|  |         ) { | ||||||
|  |         } | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn or_with_without_and_compatible_with_without() { | ||||||
|  |         fn sys( | ||||||
|  |             _: Query<&mut C, Or<(With<A>, Without<B>)>>, | ||||||
|  |             _: Query<&mut C, (With<B>, Without<A>)>, | ||||||
|  |         ) { | ||||||
|  |         } | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[should_panic = "error[B0001]"] | ||||||
|  |     fn with_and_disjoint_or_empty_without() { | ||||||
|  |         fn sys(_: Query<&mut B, With<A>>, _: Query<&mut B, Or<((), Without<A>)>>) {} | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[should_panic = "error[B0001]"] | ||||||
|  |     fn or_expanded_with_and_disjoint_nested_without() { | ||||||
|  |         fn sys( | ||||||
|  |             _: Query<&mut D, Or<(With<A>, With<B>)>>, | ||||||
|  |             _: Query<&mut D, Or<(Without<A>, Without<B>)>>, | ||||||
|  |         ) { | ||||||
|  |         } | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     #[should_panic = "error[B0001]"] | ||||||
|  |     fn or_expanded_nested_with_and_disjoint_nested_without() { | ||||||
|  |         fn sys( | ||||||
|  |             _: Query<&mut D, Or<((With<A>, With<B>), (With<B>, With<C>))>>, | ||||||
|  |             _: Query<&mut D, Or<(Without<A>, Without<B>)>>, | ||||||
|  |         ) { | ||||||
|  |         } | ||||||
|  |         let mut world = World::default(); | ||||||
|  |         run_system(&mut world, sys); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn or_doesnt_remove_unrelated_filter_with() { |     fn or_doesnt_remove_unrelated_filter_with() { | ||||||
|         fn sys(_: Query<&mut B, (Or<(With<A>, With<B>)>, With<A>)>, _: Query<&mut B, Without<A>>) {} |         fn sys(_: Query<&mut B, (Or<(With<A>, With<B>)>, With<A>)>, _: Query<&mut B, Without<A>>) {} | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Vladyslav Batyrenko
						Vladyslav Batyrenko