@@ -10,6 +10,7 @@ use crate::{
1010 storage:: { SparseSetIndex , TableId } ,
1111 world:: { unsafe_world_cell:: UnsafeWorldCell , World , WorldId } ,
1212} ;
13+ use bevy_utils:: tracing:: warn;
1314#[ cfg( feature = "trace" ) ]
1415use bevy_utils:: tracing:: Span ;
1516use fixedbitset:: FixedBitSet ;
@@ -374,7 +375,11 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
374375 NewF :: update_component_access ( & filter_state, & mut filter_component_access) ;
375376
376377 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+ ) ;
378383
379384 QueryState {
380385 world_id : self . world_id ,
@@ -396,6 +401,114 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
396401 }
397402 }
398403
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+
399512 /// Gets the query result for the given [`World`] and [`Entity`].
400513 ///
401514 /// This can only be called for read-only queries, see [`Self::get_mut`] for write-queries.
@@ -1658,4 +1771,74 @@ mod tests {
16581771
16591772 assert_eq ! ( entity_a, detection_query. single( & world) ) ;
16601773 }
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+ }
16611844}
0 commit comments