@@ -10,6 +10,7 @@ use crate::{
10
10
storage:: { SparseSetIndex , TableId } ,
11
11
world:: { unsafe_world_cell:: UnsafeWorldCell , World , WorldId } ,
12
12
} ;
13
+ use bevy_utils:: tracing:: warn;
13
14
#[ cfg( feature = "trace" ) ]
14
15
use bevy_utils:: tracing:: Span ;
15
16
use fixedbitset:: FixedBitSet ;
@@ -374,7 +375,11 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
374
375
NewF :: update_component_access ( & filter_state, & mut filter_component_access) ;
375
376
376
377
component_access. extend ( & filter_component_access) ;
377
- assert ! ( component_access. is_subset( & self . component_access) , "Transmuted state for {} attempts to access terms that are not allowed by original state {}." , std:: any:: type_name:: <( NewD , NewF ) >( ) , std:: any:: type_name:: <( D , F ) >( ) ) ;
378
+ assert ! (
379
+ component_access. is_subset( & self . component_access) ,
380
+ "Transmuted state for {} attempts to access terms that are not allowed by original state {}." ,
381
+ std:: any:: type_name:: <( NewD , NewF ) >( ) , std:: any:: type_name:: <( D , F ) >( )
382
+ ) ;
378
383
379
384
QueryState {
380
385
world_id : self . world_id ,
@@ -396,6 +401,114 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
396
401
}
397
402
}
398
403
404
+ /// Use this to combine two queries. The data accessed will be the intersection
405
+ /// of archetypes included in both queries. This can be useful for accessing a
406
+ /// subset of the entities between two queries.
407
+ ///
408
+ /// You should not call `update_archetypes` on the returned `QueryState` as the result
409
+ /// could be unpredictable. You might end up with a mix of archetypes that only matched
410
+ /// the original query + archetypes that only match the new `QueryState`. Most of the
411
+ /// safe methods on `QueryState` call [`QueryState::update_archetypes`] internally, so
412
+ /// this is best used through a `Query`.
413
+ ///
414
+ /// ## Performance
415
+ ///
416
+ /// This will have similar performance as constructing a new `QueryState` since much of internal state
417
+ /// needs to be reconstructed. But it will be a little faster as it only needs to compare the intersection
418
+ /// of matching archetypes rather than iterating over all archetypes.
419
+ ///
420
+ /// ## Panics
421
+ ///
422
+ /// Will panic if `NewD` contains accesses not in `Q` or `OtherQ`.
423
+ pub fn join < OtherD : QueryData , NewD : QueryData > (
424
+ & self ,
425
+ world : & World ,
426
+ other : & QueryState < OtherD > ,
427
+ ) -> QueryState < NewD , ( ) > {
428
+ self . join_filtered :: < _ , ( ) , NewD , ( ) > ( world, other)
429
+ }
430
+
431
+ /// Use this to combine two queries. The data accessed will be the intersection
432
+ /// of archetypes included in both queries.
433
+ ///
434
+ /// ## Panics
435
+ ///
436
+ /// Will panic if `NewD` or `NewF` requires accesses not in `Q` or `OtherQ`.
437
+ pub fn join_filtered <
438
+ OtherD : QueryData ,
439
+ OtherF : QueryFilter ,
440
+ NewD : QueryData ,
441
+ NewF : QueryFilter ,
442
+ > (
443
+ & self ,
444
+ world : & World ,
445
+ other : & QueryState < OtherD , OtherF > ,
446
+ ) -> QueryState < NewD , NewF > {
447
+ if self . world_id != other. world_id {
448
+ panic ! ( "Joining queries initialized on different worlds is not allowed." ) ;
449
+ }
450
+
451
+ let mut component_access = FilteredAccess :: default ( ) ;
452
+ let mut new_fetch_state = NewD :: get_state ( world)
453
+ . expect ( "Could not create fetch_state, Please initialize all referenced components before transmuting." ) ;
454
+ let new_filter_state = NewF :: get_state ( world)
455
+ . expect ( "Could not create filter_state, Please initialize all referenced components before transmuting." ) ;
456
+
457
+ NewD :: set_access ( & mut new_fetch_state, & self . component_access ) ;
458
+ NewD :: update_component_access ( & new_fetch_state, & mut component_access) ;
459
+
460
+ let mut new_filter_component_access = FilteredAccess :: default ( ) ;
461
+ NewF :: update_component_access ( & new_filter_state, & mut new_filter_component_access) ;
462
+
463
+ component_access. extend ( & new_filter_component_access) ;
464
+
465
+ let mut joined_component_access = self . component_access . clone ( ) ;
466
+ joined_component_access. extend ( & other. component_access ) ;
467
+
468
+ assert ! (
469
+ component_access. is_subset( & joined_component_access) ,
470
+ "Joined state for {} attempts to access terms that are not allowed by state {} joined with {}." ,
471
+ std:: any:: type_name:: <( NewD , NewF ) >( ) , std:: any:: type_name:: <( D , F ) >( ) , std:: any:: type_name:: <( OtherD , OtherF ) >( )
472
+ ) ;
473
+
474
+ if self . archetype_generation != other. archetype_generation {
475
+ warn ! ( "You have tried to join queries with different archetype_generations. This could lead to unpredictable results." ) ;
476
+ }
477
+
478
+ // take the intersection of the matched ids
479
+ let matched_tables: FixedBitSet = self
480
+ . matched_tables
481
+ . intersection ( & other. matched_tables )
482
+ . collect ( ) ;
483
+ let matched_table_ids: Vec < TableId > =
484
+ matched_tables. ones ( ) . map ( TableId :: from_usize) . collect ( ) ;
485
+ let matched_archetypes: FixedBitSet = self
486
+ . matched_archetypes
487
+ . intersection ( & other. matched_archetypes )
488
+ . collect ( ) ;
489
+ let matched_archetype_ids: Vec < ArchetypeId > =
490
+ matched_archetypes. ones ( ) . map ( ArchetypeId :: new) . collect ( ) ;
491
+
492
+ QueryState {
493
+ world_id : self . world_id ,
494
+ archetype_generation : self . archetype_generation ,
495
+ matched_table_ids,
496
+ matched_archetype_ids,
497
+ fetch_state : new_fetch_state,
498
+ filter_state : new_filter_state,
499
+ component_access : joined_component_access,
500
+ matched_tables,
501
+ matched_archetypes,
502
+ archetype_component_access : self . archetype_component_access . clone ( ) ,
503
+ #[ cfg( feature = "trace" ) ]
504
+ par_iter_span : bevy_utils:: tracing:: info_span!(
505
+ "par_for_each" ,
506
+ query = std:: any:: type_name:: <NewD >( ) ,
507
+ filter = std:: any:: type_name:: <NewF >( ) ,
508
+ ) ,
509
+ }
510
+ }
511
+
399
512
/// Gets the query result for the given [`World`] and [`Entity`].
400
513
///
401
514
/// This can only be called for read-only queries, see [`Self::get_mut`] for write-queries.
@@ -1658,4 +1771,74 @@ mod tests {
1658
1771
1659
1772
assert_eq ! ( entity_a, detection_query. single( & world) ) ;
1660
1773
}
1774
+
1775
+ #[ test]
1776
+ #[ should_panic(
1777
+ expected = "Transmuted state for (bevy_ecs::entity::Entity, bevy_ecs::query::filter::Changed<bevy_ecs::query::state::tests::B>) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
1778
+ ) ]
1779
+ fn cannot_transmute_changed_without_access ( ) {
1780
+ let mut world = World :: new ( ) ;
1781
+ world. init_component :: < A > ( ) ;
1782
+ world. init_component :: < B > ( ) ;
1783
+ let query = QueryState :: < & A > :: new ( & mut world) ;
1784
+ let _new_query = query. transmute_filtered :: < Entity , Changed < B > > ( & world) ;
1785
+ }
1786
+
1787
+ #[ test]
1788
+ fn join ( ) {
1789
+ let mut world = World :: new ( ) ;
1790
+ world. spawn ( A ( 0 ) ) ;
1791
+ world. spawn ( B ( 1 ) ) ;
1792
+ let entity_ab = world. spawn ( ( A ( 2 ) , B ( 3 ) ) ) . id ( ) ;
1793
+ world. spawn ( ( A ( 4 ) , B ( 5 ) , C ( 6 ) ) ) ;
1794
+
1795
+ let query_1 = QueryState :: < & A , Without < C > > :: new ( & mut world) ;
1796
+ let query_2 = QueryState :: < & B , Without < C > > :: new ( & mut world) ;
1797
+ let mut new_query: QueryState < Entity , ( ) > = query_1. join_filtered ( & world, & query_2) ;
1798
+
1799
+ assert_eq ! ( new_query. single( & world) , entity_ab) ;
1800
+ }
1801
+
1802
+ #[ test]
1803
+ fn join_with_get ( ) {
1804
+ let mut world = World :: new ( ) ;
1805
+ world. spawn ( A ( 0 ) ) ;
1806
+ world. spawn ( B ( 1 ) ) ;
1807
+ let entity_ab = world. spawn ( ( A ( 2 ) , B ( 3 ) ) ) . id ( ) ;
1808
+ let entity_abc = world. spawn ( ( A ( 4 ) , B ( 5 ) , C ( 6 ) ) ) . id ( ) ;
1809
+
1810
+ let query_1 = QueryState :: < & A > :: new ( & mut world) ;
1811
+ let query_2 = QueryState :: < & B , Without < C > > :: new ( & mut world) ;
1812
+ let mut new_query: QueryState < Entity , ( ) > = query_1. join_filtered ( & world, & query_2) ;
1813
+
1814
+ assert ! ( new_query. get( & world, entity_ab) . is_ok( ) ) ;
1815
+ // should not be able to get entity with c.
1816
+ assert ! ( new_query. get( & world, entity_abc) . is_err( ) ) ;
1817
+ }
1818
+
1819
+ #[ test]
1820
+ #[ should_panic( expected = "Joined state for (&bevy_ecs::query::state::tests::C, ()) \
1821
+ attempts to access terms that are not allowed by state \
1822
+ (&bevy_ecs::query::state::tests::A, ()) joined with (&bevy_ecs::query::state::tests::B, ()).") ]
1823
+ fn cannot_join_wrong_fetch ( ) {
1824
+ let mut world = World :: new ( ) ;
1825
+ world. init_component :: < C > ( ) ;
1826
+ let query_1 = QueryState :: < & A > :: new ( & mut world) ;
1827
+ let query_2 = QueryState :: < & B > :: new ( & mut world) ;
1828
+ let _query: QueryState < & C > = query_1. join ( & world, & query_2) ;
1829
+ }
1830
+
1831
+ #[ test]
1832
+ #[ should_panic(
1833
+ expected = "Joined state for (bevy_ecs::entity::Entity, bevy_ecs::query::filter::Changed<bevy_ecs::query::state::tests::C>) \
1834
+ attempts to access terms that are not allowed by state \
1835
+ (&bevy_ecs::query::state::tests::A, bevy_ecs::query::filter::Without<bevy_ecs::query::state::tests::C>) \
1836
+ joined with (&bevy_ecs::query::state::tests::B, bevy_ecs::query::filter::Without<bevy_ecs::query::state::tests::C>)."
1837
+ ) ]
1838
+ fn cannot_join_wrong_filter ( ) {
1839
+ let mut world = World :: new ( ) ;
1840
+ let query_1 = QueryState :: < & A , Without < C > > :: new ( & mut world) ;
1841
+ let query_2 = QueryState :: < & B , Without < C > > :: new ( & mut world) ;
1842
+ let _: QueryState < Entity , Changed < C > > = query_1. join_filtered ( & world, & query_2) ;
1843
+ }
1661
1844
}
0 commit comments