Skip to content

Commit 9218793

Browse files
committed
Improve or-with disjoint checks
1 parent b44b606 commit 9218793

File tree

3 files changed

+98
-6
lines changed

3 files changed

+98
-6
lines changed

crates/bevy_ecs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ fixedbitset = "0.4"
2727
fxhash = "0.2"
2828
downcast-rs = "1.2"
2929
serde = { version = "1", features = ["derive"] }
30+
smallvec = "1.6"
3031

3132
[dev-dependencies]
3233
rand = "0.8"

crates/bevy_ecs/src/query/access.rs

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::storage::SparseSetIndex;
22
use bevy_utils::HashSet;
33
use core::fmt;
44
use fixedbitset::FixedBitSet;
5+
use smallvec::SmallVec;
56
use std::marker::PhantomData;
67

78
/// A wrapper struct to make Debug representations of [`FixedBitSet`] easier
@@ -25,6 +26,7 @@ struct FormattedBitSet<'a, T: SparseSetIndex> {
2526
bit_set: &'a FixedBitSet,
2627
_marker: PhantomData<T>,
2728
}
29+
2830
impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> {
2931
fn new(bit_set: &'a FixedBitSet) -> Self {
3032
Self {
@@ -33,6 +35,7 @@ impl<'a, T: SparseSetIndex> FormattedBitSet<'a, T> {
3335
}
3436
}
3537
}
38+
3639
impl<'a, T: SparseSetIndex + fmt::Debug> fmt::Debug for FormattedBitSet<'a, T> {
3740
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3841
f.debug_list()
@@ -41,6 +44,28 @@ impl<'a, T: SparseSetIndex + fmt::Debug> fmt::Debug for FormattedBitSet<'a, T> {
4144
}
4245
}
4346

47+
struct FormattedExpandedOrWithAccesses<'a, T: SparseSetIndex> {
48+
with: &'a ExpandedOrWithAccesses,
49+
_marker: PhantomData<T>,
50+
}
51+
52+
impl<'a, T: SparseSetIndex> FormattedExpandedOrWithAccesses<'a, T> {
53+
fn new(with: &'a ExpandedOrWithAccesses) -> Self {
54+
Self {
55+
with,
56+
_marker: PhantomData,
57+
}
58+
}
59+
}
60+
61+
impl<'a, T: SparseSetIndex + fmt::Debug> fmt::Debug for FormattedExpandedOrWithAccesses<'a, T> {
62+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63+
f.debug_list()
64+
.entries(self.with.arr.iter().map(FormattedBitSet::<T>::new))
65+
.finish()
66+
}
67+
}
68+
4469
/// Tracks read and write access to specific elements in a collection.
4570
///
4671
/// Used internally to ensure soundness during system initialization and execution.
@@ -69,6 +94,7 @@ impl<T: SparseSetIndex + fmt::Debug> fmt::Debug for Access<T> {
6994
.finish()
7095
}
7196
}
97+
7298
impl<T: SparseSetIndex> Default for Access<T> {
7399
fn default() -> Self {
74100
Self::new()
@@ -213,20 +239,24 @@ impl<T: SparseSetIndex> Access<T> {
213239
/// is read/write `T`, read `U`. It must still have a read `U` access otherwise the following
214240
/// queries would be incorrectly considered disjoint:
215241
/// - `Query<&mut T>` read/write `T`
216-
/// - `Query<Option<&T>` accesses nothing
242+
/// - `Query<Option<&T>>` accesses nothing
217243
///
218244
/// See comments the `WorldQuery` impls of `AnyOf`/`Option`/`Or` for more information.
219245
#[derive(Clone, Eq, PartialEq)]
220246
pub struct FilteredAccess<T: SparseSetIndex> {
221247
access: Access<T>,
222-
with: FixedBitSet,
248+
with: ExpandedOrWithAccesses,
223249
without: FixedBitSet,
224250
}
251+
225252
impl<T: SparseSetIndex + fmt::Debug> fmt::Debug for FilteredAccess<T> {
226253
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227254
f.debug_struct("FilteredAccess")
228255
.field("access", &self.access)
229-
.field("with", &FormattedBitSet::<T>::new(&self.with))
256+
.field(
257+
"with",
258+
&FormattedExpandedOrWithAccesses::<T>::new(&self.with),
259+
)
230260
.field("without", &FormattedBitSet::<T>::new(&self.without))
231261
.finish()
232262
}
@@ -277,8 +307,7 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
277307

278308
/// Retains only combinations where the element given by `index` is also present.
279309
pub fn add_with(&mut self, index: T) {
280-
self.with.grow(index.sparse_set_index() + 1);
281-
self.with.insert(index.sparse_set_index());
310+
self.with.add(index.sparse_set_index());
282311
}
283312

284313
/// Retains only combinations where the element given by `index` is not present.
@@ -289,7 +318,7 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
289318

290319
pub fn extend_intersect_filter(&mut self, other: &FilteredAccess<T>) {
291320
self.without.intersect_with(&other.without);
292-
self.with.intersect_with(&other.with);
321+
self.with.extend_with_or(&other.with);
293322
}
294323

295324
pub fn extend_access(&mut self, other: &FilteredAccess<T>) {
@@ -325,6 +354,57 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
325354
}
326355
}
327356

357+
// A struct to express something like `Or<(With<A>, With<B>)>`.
358+
// Filters like `(With<A>, Or<(With<B>, With<C>)>` are expanded into `Or<(With<(A, B)>, With<(B, C)>)>`.
359+
#[derive(Clone, Eq, PartialEq)]
360+
struct ExpandedOrWithAccesses {
361+
arr: SmallVec<[FixedBitSet; 8]>,
362+
}
363+
364+
impl Default for ExpandedOrWithAccesses {
365+
fn default() -> Self {
366+
Self {
367+
arr: smallvec::smallvec![FixedBitSet::default()],
368+
}
369+
}
370+
}
371+
372+
impl ExpandedOrWithAccesses {
373+
fn add(&mut self, index: usize) {
374+
for with in &mut self.arr {
375+
with.grow(index + 1);
376+
with.insert(index);
377+
}
378+
}
379+
380+
fn extend_with_or(&mut self, other: &ExpandedOrWithAccesses) {
381+
self.arr.append(&mut other.arr.clone());
382+
}
383+
384+
fn is_disjoint(&self, without: &FixedBitSet) -> bool {
385+
self.arr.iter().any(|with| with.is_disjoint(without))
386+
}
387+
388+
fn union_with(&mut self, other: &Self) {
389+
if other.arr.len() == 1 {
390+
for with in &mut self.arr {
391+
with.union_with(&other.arr[0]);
392+
}
393+
return;
394+
}
395+
396+
let mut new_with = SmallVec::with_capacity(self.arr.len() * other.arr.len());
397+
for with in &self.arr {
398+
for other_with in &other.arr {
399+
let mut w = with.clone();
400+
w.union_with(other_with);
401+
new_with.push(w);
402+
}
403+
}
404+
self.arr = new_with;
405+
}
406+
}
407+
328408
/// A collection of [`FilteredAccess`] instances.
329409
///
330410
/// Used internally to statically check if systems have conflicting access.

crates/bevy_ecs/src/system/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,17 @@ mod tests {
390390
run_system(&mut world, sys);
391391
}
392392

393+
#[test]
394+
fn or_has_filter_with() {
395+
fn sys(
396+
_: Query<&mut C, Or<(With<A>, With<B>)>>,
397+
_: Query<&mut C, (Without<A>, Without<B>)>,
398+
) {
399+
}
400+
let mut world = World::default();
401+
run_system(&mut world, sys);
402+
}
403+
393404
#[test]
394405
fn or_doesnt_remove_unrelated_filter_with() {
395406
fn sys(_: Query<&mut B, (Or<(With<A>, With<B>)>, With<A>)>, _: Query<&mut B, Without<A>>) {}

0 commit comments

Comments
 (0)