1
1
use crate :: storage:: SparseSetIndex ;
2
2
use bevy_utils:: HashSet ;
3
+ use core:: fmt;
3
4
use fixedbitset:: FixedBitSet ;
4
5
use std:: marker:: PhantomData ;
5
6
7
+ /// A wrapper struct to make Debug representations of [`FixedBitSet`] easier
8
+ /// to read, when used to store [`SparseSetIndex`].
9
+ ///
10
+ /// Instead of the raw integer representation of the `FixedBitSet`, the list of
11
+ /// `T` valid for [`SparseSetIndex`] is shown.
12
+ ///
13
+ /// Normal `FixedBitSet` `Debug` output:
14
+ /// ```text
15
+ /// read_and_writes: FixedBitSet { data: [ 160 ], length: 8 }
16
+ /// ```
17
+ ///
18
+ /// Which, unless you are a computer, doesn't help much understand what's in
19
+ /// the set. With `FormattedBitSet`, we convert the present set entries into
20
+ /// what they stand for, it is much clearer what is going on:
21
+ /// ```text
22
+ /// read_and_writes: [ ComponentId(5), ComponentId(7) ]
23
+ /// ```
24
+ struct FormattedBitSet < ' a , T : SparseSetIndex > {
25
+ bit_set : & ' a FixedBitSet ,
26
+ _marker : PhantomData < T > ,
27
+ }
28
+ impl < ' a , T : SparseSetIndex > FormattedBitSet < ' a , T > {
29
+ fn new ( bit_set : & ' a FixedBitSet ) -> Self {
30
+ Self {
31
+ bit_set,
32
+ _marker : PhantomData ,
33
+ }
34
+ }
35
+ }
36
+ impl < ' a , T : SparseSetIndex + fmt:: Debug > fmt:: Debug for FormattedBitSet < ' a , T > {
37
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
38
+ f. debug_list ( )
39
+ . entries ( self . bit_set . ones ( ) . map ( T :: get_sparse_set_index) )
40
+ . finish ( )
41
+ }
42
+ }
43
+
6
44
/// Tracks read and write access to specific elements in a collection.
7
45
///
8
46
/// Used internally to ensure soundness during system initialization and execution.
9
47
/// See the [`is_compatible`](Access::is_compatible) and [`get_conflicts`](Access::get_conflicts) functions.
10
- #[ derive( Debug , Clone , Eq , PartialEq ) ]
48
+ #[ derive( Clone , Eq , PartialEq ) ]
11
49
pub struct Access < T : SparseSetIndex > {
12
50
/// All accessed elements.
13
51
reads_and_writes : FixedBitSet ,
@@ -19,6 +57,18 @@ pub struct Access<T: SparseSetIndex> {
19
57
marker : PhantomData < T > ,
20
58
}
21
59
60
+ impl < T : SparseSetIndex + fmt:: Debug > fmt:: Debug for Access < T > {
61
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
62
+ f. debug_struct ( "Access" )
63
+ . field (
64
+ "read_and_writes" ,
65
+ & FormattedBitSet :: < T > :: new ( & self . reads_and_writes ) ,
66
+ )
67
+ . field ( "writes" , & FormattedBitSet :: < T > :: new ( & self . writes ) )
68
+ . field ( "reads_all" , & self . reads_all )
69
+ . finish ( )
70
+ }
71
+ }
22
72
impl < T : SparseSetIndex > Default for Access < T > {
23
73
fn default ( ) -> Self {
24
74
Self {
@@ -55,11 +105,7 @@ impl<T: SparseSetIndex> Access<T> {
55
105
56
106
/// Returns `true` if this can access the element given by `index`.
57
107
pub fn has_read ( & self , index : T ) -> bool {
58
- if self . reads_all {
59
- true
60
- } else {
61
- self . reads_and_writes . contains ( index. sparse_set_index ( ) )
62
- }
108
+ self . reads_all || self . reads_and_writes . contains ( index. sparse_set_index ( ) )
63
109
}
64
110
65
111
/// Returns `true` if this can exclusively access the element given by `index`.
@@ -106,7 +152,7 @@ impl<T: SparseSetIndex> Access<T> {
106
152
}
107
153
108
154
self . writes . is_disjoint ( & other. reads_and_writes )
109
- && self . reads_and_writes . is_disjoint ( & other . writes )
155
+ && other . writes . is_disjoint ( & self . reads_and_writes )
110
156
}
111
157
112
158
/// Returns a vector of elements that the access and `other` cannot access at the same time.
@@ -153,7 +199,7 @@ impl<T: SparseSetIndex> Access<T> {
153
199
/// `with` access.
154
200
///
155
201
/// For example consider `Query<Option<&T>>` this only has a `read` of `T` as doing
156
- /// otherwise would allow for queries to be considered disjoint that actually aren 't:
202
+ /// otherwise would allow for queries to be considered disjoint when they shouldn 't:
157
203
/// - `Query<(&mut T, Option<&U>)>` read/write `T`, read `U`, with `U`
158
204
/// - `Query<&mut T, Without<U>>` read/write `T`, without `U`
159
205
/// from this we could reasonably conclude that the queries are disjoint but they aren't.
@@ -165,12 +211,21 @@ impl<T: SparseSetIndex> Access<T> {
165
211
/// - `Query<Option<&T>` accesses nothing
166
212
///
167
213
/// See comments the `WorldQuery` impls of `AnyOf`/`Option`/`Or` for more information.
168
- #[ derive( Debug , Clone , Eq , PartialEq ) ]
214
+ #[ derive( Clone , Eq , PartialEq ) ]
169
215
pub struct FilteredAccess < T : SparseSetIndex > {
170
216
access : Access < T > ,
171
217
with : FixedBitSet ,
172
218
without : FixedBitSet ,
173
219
}
220
+ impl < T : SparseSetIndex + fmt:: Debug > fmt:: Debug for FilteredAccess < T > {
221
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
222
+ f. debug_struct ( "FilteredAccess" )
223
+ . field ( "access" , & self . access )
224
+ . field ( "with" , & FormattedBitSet :: < T > :: new ( & self . with ) )
225
+ . field ( "without" , & FormattedBitSet :: < T > :: new ( & self . without ) )
226
+ . finish ( )
227
+ }
228
+ }
174
229
175
230
impl < T : SparseSetIndex > Default for FilteredAccess < T > {
176
231
fn default ( ) -> Self {
@@ -238,12 +293,9 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
238
293
239
294
/// Returns `true` if this and `other` can be active at the same time.
240
295
pub fn is_compatible ( & self , other : & FilteredAccess < T > ) -> bool {
241
- if self . access . is_compatible ( & other. access ) {
242
- true
243
- } else {
244
- self . with . intersection ( & other. without ) . next ( ) . is_some ( )
245
- || self . without . intersection ( & other. with ) . next ( ) . is_some ( )
246
- }
296
+ self . access . is_compatible ( & other. access )
297
+ || !self . with . is_disjoint ( & other. without )
298
+ || !other. with . is_disjoint ( & self . without )
247
299
}
248
300
249
301
/// Returns a vector of elements that this and `other` cannot access at the same time.
@@ -271,6 +323,10 @@ impl<T: SparseSetIndex> FilteredAccess<T> {
271
323
/// A collection of [`FilteredAccess`] instances.
272
324
///
273
325
/// Used internally to statically check if systems have conflicting access.
326
+ ///
327
+ /// It stores multiple sets of accesses.
328
+ /// - A "combined" set, which is the access of all filters in this set combined.
329
+ /// - The set of access of each individual filters in this set.
274
330
#[ derive( Debug , Clone ) ]
275
331
pub struct FilteredAccessSet < T : SparseSetIndex > {
276
332
combined_access : Access < T > ,
@@ -284,13 +340,18 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
284
340
& self . combined_access
285
341
}
286
342
287
- /// Returns a mutable reference to the unfiltered access of the entire set.
288
- #[ inline]
289
- pub fn combined_access_mut ( & mut self ) -> & mut Access < T > {
290
- & mut self . combined_access
291
- }
292
-
293
343
/// Returns `true` if this and `other` can be active at the same time.
344
+ ///
345
+ /// Access conflict resolution happen in two steps:
346
+ /// 1. A "coarse" check, if there is no mutual unfiltered conflict between
347
+ /// `self` and `other`, we already know that the two access sets are
348
+ /// compatible.
349
+ /// 2. A "fine grained" check, it kicks in when the "coarse" check fails.
350
+ /// the two access sets might still be compatible if some of the accesses
351
+ /// are restricted with the `With` or `Without` filters so that access is
352
+ /// mutually exclusive. The fine grained phase iterates over all filters in
353
+ /// the `self` set and compares it to all the filters in the `other` set,
354
+ /// making sure they are all mutually compatible.
294
355
pub fn is_compatible ( & self , other : & FilteredAccessSet < T > ) -> bool {
295
356
if self . combined_access . is_compatible ( other. combined_access ( ) ) {
296
357
return true ;
@@ -302,7 +363,6 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
302
363
}
303
364
}
304
365
}
305
-
306
366
true
307
367
}
308
368
@@ -338,6 +398,20 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
338
398
self . filtered_accesses . push ( filtered_access) ;
339
399
}
340
400
401
+ /// Adds a read access without filters to the set.
402
+ pub ( crate ) fn add_unfiltered_read ( & mut self , index : T ) {
403
+ let mut filter = FilteredAccess :: default ( ) ;
404
+ filter. add_read ( index) ;
405
+ self . add ( filter) ;
406
+ }
407
+
408
+ /// Adds a write access without filters to the set.
409
+ pub ( crate ) fn add_unfiltered_write ( & mut self , index : T ) {
410
+ let mut filter = FilteredAccess :: default ( ) ;
411
+ filter. add_write ( index) ;
412
+ self . add ( filter) ;
413
+ }
414
+
341
415
pub fn extend ( & mut self , filtered_access_set : FilteredAccessSet < T > ) {
342
416
self . combined_access
343
417
. extend ( & filtered_access_set. combined_access ) ;
@@ -362,7 +436,30 @@ impl<T: SparseSetIndex> Default for FilteredAccessSet<T> {
362
436
363
437
#[ cfg( test) ]
364
438
mod tests {
365
- use crate :: query:: { Access , FilteredAccess } ;
439
+ use crate :: query:: { Access , FilteredAccess , FilteredAccessSet } ;
440
+
441
+ #[ test]
442
+ fn read_all_access_conflicts ( ) {
443
+ // read_all / single write
444
+ let mut access_a = Access :: < usize > :: default ( ) ;
445
+ access_a. grow ( 10 ) ;
446
+ access_a. add_write ( 0 ) ;
447
+
448
+ let mut access_b = Access :: < usize > :: default ( ) ;
449
+ access_b. read_all ( ) ;
450
+
451
+ assert ! ( !access_b. is_compatible( & access_a) ) ;
452
+
453
+ // read_all / read_all
454
+ let mut access_a = Access :: < usize > :: default ( ) ;
455
+ access_a. grow ( 10 ) ;
456
+ access_a. read_all ( ) ;
457
+
458
+ let mut access_b = Access :: < usize > :: default ( ) ;
459
+ access_b. read_all ( ) ;
460
+
461
+ assert ! ( access_b. is_compatible( & access_a) ) ;
462
+ }
366
463
367
464
#[ test]
368
465
fn access_get_conflicts ( ) {
@@ -391,6 +488,22 @@ mod tests {
391
488
assert_eq ! ( access_d. get_conflicts( & access_c) , vec![ 0 ] ) ;
392
489
}
393
490
491
+ #[ test]
492
+ fn filtered_combined_access ( ) {
493
+ let mut access_a = FilteredAccessSet :: < usize > :: default ( ) ;
494
+ access_a. add_unfiltered_read ( 1 ) ;
495
+
496
+ let mut filter_b = FilteredAccess :: < usize > :: default ( ) ;
497
+ filter_b. add_write ( 1 ) ;
498
+
499
+ let conflicts = access_a. get_conflicts_single ( & filter_b) ;
500
+ assert_eq ! (
501
+ & conflicts,
502
+ & [ 1_usize ] ,
503
+ "access_a: {access_a:?}, filter_b: {filter_b:?}"
504
+ ) ;
505
+ }
506
+
394
507
#[ test]
395
508
fn filtered_access_extend ( ) {
396
509
let mut access_a = FilteredAccess :: < usize > :: default ( ) ;
0 commit comments