diff --git a/benches/benches/bevy_render/torus.rs b/benches/benches/bevy_render/torus.rs index 8ec81c8040..199cc7ce4c 100644 --- a/benches/benches/bevy_render/torus.rs +++ b/benches/benches/bevy_render/torus.rs @@ -4,12 +4,9 @@ use bevy_render::mesh::TorusMeshBuilder; fn torus(c: &mut Criterion) { c.bench_function("build_torus", |b| { - b.iter(|| black_box(TorusMeshBuilder::new(black_box(0.5),black_box(1.0)))); + b.iter(|| black_box(TorusMeshBuilder::new(black_box(0.5), black_box(1.0)))); }); } -criterion_group!( - benches, - torus, -); +criterion_group!(benches, torus,); criterion_main!(benches); diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index f0eda83a22..52f64b5e74 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -26,6 +26,7 @@ use crate::{ observer::Observers, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, }; +use bevy_utils::HashMap; use std::{ hash::Hash, ops::{Index, IndexMut, RangeFrom}, @@ -345,8 +346,10 @@ pub struct Archetype { } impl Archetype { + /// `table_components` and `sparse_set_components` must be sorted pub(crate) fn new( components: &Components, + component_index: &mut ComponentIndex, observers: &Observers, id: ArchetypeId, table_id: TableId, @@ -357,7 +360,7 @@ impl Archetype { let (min_sparse, _) = sparse_set_components.size_hint(); let mut flags = ArchetypeFlags::empty(); let mut archetype_components = SparseSet::with_capacity(min_table + min_sparse); - for (component_id, archetype_component_id) in table_components { + for (idx, (component_id, archetype_component_id)) in table_components.enumerate() { // SAFETY: We are creating an archetype that includes this component so it must exist let info = unsafe { components.get_info_unchecked(component_id) }; info.update_archetype_flags(&mut flags); @@ -369,6 +372,13 @@ impl Archetype { archetype_component_id, }, ); + // NOTE: the `table_components` are sorted AND they were inserted in the `Table` in the same + // sorted order, so the index of the `Column` in the `Table` is the same as the index of the + // component in the `table_components` vector + component_index + .entry(component_id) + .or_insert_with(HashMap::new) + .insert(id, ArchetypeRecord { column: Some(idx) }); } for (component_id, archetype_component_id) in sparse_set_components { @@ -383,6 +393,10 @@ impl Archetype { archetype_component_id, }, ); + component_index + .entry(component_id) + .or_insert_with(HashMap::new) + .insert(id, ArchetypeRecord { column: None }); } Self { id, @@ -460,7 +474,7 @@ impl Archetype { self.components.len() } - /// Fetches a immutable reference to the archetype's [`Edges`], a cache of + /// Fetches an immutable reference to the archetype's [`Edges`], a cache of /// archetypal relationships. #[inline] pub fn edges(&self) -> &Edges { @@ -655,7 +669,7 @@ impl Archetype { /// This is used in archetype update methods to limit archetype updates to the /// ones added since the last time the method ran. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct ArchetypeGeneration(ArchetypeId); +pub struct ArchetypeGeneration(pub(crate) ArchetypeId); impl ArchetypeGeneration { /// The first archetype. @@ -711,6 +725,10 @@ impl SparseSetIndex for ArchetypeComponentId { } } +/// Maps a [`ComponentId`] to the list of [`Archetypes`]([`Archetype`]) that contain the [`Component`](crate::component::Component), +/// along with an [`ArchetypeRecord`] which contains some metadata about how the component is stored in the archetype. +pub type ComponentIndex = HashMap>; + /// The backing store of all [`Archetype`]s within a [`World`]. /// /// For more information, see the *[module level documentation]*. @@ -720,7 +738,18 @@ impl SparseSetIndex for ArchetypeComponentId { pub struct Archetypes { pub(crate) archetypes: Vec, archetype_component_count: usize, - by_components: bevy_utils::HashMap, + /// find the archetype id by the archetype's components + by_components: HashMap, + /// find all the archetypes that contain a component + by_component: ComponentIndex, +} + +/// Metadata about how a component is stored in an [`Archetype`]. +pub struct ArchetypeRecord { + /// Index of the component in the archetype's [`Table`](crate::storage::Table), + /// or None if the component is a sparse set component. + #[allow(dead_code)] + pub(crate) column: Option, } impl Archetypes { @@ -728,6 +757,7 @@ impl Archetypes { let mut archetypes = Archetypes { archetypes: Vec::new(), by_components: Default::default(), + by_component: Default::default(), archetype_component_count: 0, }; // SAFETY: Empty archetype has no components @@ -770,7 +800,7 @@ impl Archetypes { unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) } } - /// Fetches an mutable reference to the archetype without any components. + /// Fetches a mutable reference to the archetype without any components. #[inline] pub(crate) fn empty_mut(&mut self) -> &mut Archetype { // SAFETY: empty archetype always exists @@ -848,7 +878,8 @@ impl Archetypes { let archetypes = &mut self.archetypes; let archetype_component_count = &mut self.archetype_component_count; - *self + let component_index = &mut self.by_component; + let archetype_id = *self .by_components .entry(archetype_identity) .or_insert_with_key(move |identity| { @@ -856,20 +887,18 @@ impl Archetypes { table_components, sparse_set_components, } = identity; - let id = ArchetypeId::new(archetypes.len()); let table_start = *archetype_component_count; *archetype_component_count += table_components.len(); let table_archetype_components = (table_start..*archetype_component_count).map(ArchetypeComponentId); - let sparse_start = *archetype_component_count; *archetype_component_count += sparse_set_components.len(); let sparse_set_archetype_components = (sparse_start..*archetype_component_count).map(ArchetypeComponentId); - archetypes.push(Archetype::new( components, + component_index, observers, id, table_id, @@ -883,7 +912,8 @@ impl Archetypes { .zip(sparse_set_archetype_components), )); id - }) + }); + archetype_id } /// Returns the number of components that are stored in archetypes. @@ -900,16 +930,25 @@ impl Archetypes { } } + /// Get the component index + pub(crate) fn component_index(&self) -> &ComponentIndex { + &self.by_component + } + pub(crate) fn update_flags( &mut self, component_id: ComponentId, flags: ArchetypeFlags, set: bool, ) { - // TODO: Refactor component index to speed this up. - for archetype in &mut self.archetypes { - if archetype.contains(component_id) { - archetype.flags.set(flags, set); + if let Some(archetypes) = self.by_component.get(&component_id) { + for archetype_id in archetypes.keys() { + // SAFETY: the component index only contains valid archetype ids + self.archetypes + .get_mut(archetype_id.index()) + .unwrap() + .flags + .set(flags, set); } } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 406beb92d9..315fcd9e33 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -81,6 +81,7 @@ mod tests { world::{EntityRef, Mut, World}, }; use bevy_tasks::{ComputeTaskPool, TaskPool}; + use bevy_utils::HashSet; use std::num::NonZeroU32; use std::{ any::TypeId, @@ -91,9 +92,9 @@ mod tests { }, }; - #[derive(Component, Resource, Debug, PartialEq, Eq, Clone, Copy)] + #[derive(Component, Resource, Debug, PartialEq, Eq, Hash, Clone, Copy)] struct A(usize); - #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] + #[derive(Component, Debug, PartialEq, Eq, Hash, Clone, Copy)] struct B(usize); #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] struct C; @@ -126,7 +127,7 @@ mod tests { #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] #[component(storage = "Table")] struct TableStored(&'static str); - #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] + #[derive(Component, Copy, Clone, PartialEq, Eq, Hash, Debug)] #[component(storage = "SparseSet")] struct SparseStored(u32); @@ -379,8 +380,9 @@ mod tests { .query::<(Entity, &A)>() .iter(&world) .map(|(e, &i)| (e, i)) - .collect::>(); - assert_eq!(ents, &[(e, A(123)), (f, A(456))]); + .collect::>(); + assert!(ents.contains(&(e, A(123)))); + assert!(ents.contains(&(f, A(456)))); } #[test] @@ -402,12 +404,15 @@ mod tests { let mut world = World::new(); let e = world.spawn((TableStored("abc"), A(123))).id(); let f = world.spawn((TableStored("def"), A(456), B(1))).id(); - let mut results = Vec::new(); + let mut results = HashSet::new(); world .query::<(Entity, &A)>() .iter(&world) - .for_each(|(e, &i)| results.push((e, i))); - assert_eq!(results, &[(e, A(123)), (f, A(456))]); + .for_each(|(e, &i)| { + results.insert((e, i)); + }); + assert!(results.contains(&(e, A(123)))); + assert!(results.contains(&(f, A(456)))); } #[test] @@ -554,8 +559,9 @@ mod tests { .query::<(Entity, Option<&B>, &A)>() .iter(&world) .map(|(e, b, &i)| (e, b.copied(), i)) - .collect::>(); - assert_eq!(ents, &[(e, None, A(123)), (f, Some(B(1)), A(456))]); + .collect::>(); + assert!(ents.contains(&(e, None, A(123)))); + assert!(ents.contains(&(f, Some(B(1)), A(456)))); } #[test] @@ -572,10 +578,10 @@ mod tests { .query::<(Entity, Option<&SparseStored>, &A)>() .iter(&world) .map(|(e, b, &i)| (e, b.copied(), i)) - .collect::>(); + .collect::>(); assert_eq!( ents, - &[(e, None, A(123)), (f, Some(SparseStored(1)), A(456))] + HashSet::from([(e, None, A(123)), (f, Some(SparseStored(1)), A(456))]) ); } @@ -606,8 +612,8 @@ mod tests { .query::<(Entity, &A, &B)>() .iter(&world) .map(|(e, &i, &b)| (e, i, b)) - .collect::>(), - &[(e1, A(1), B(3)), (e2, A(2), B(4))] + .collect::>(), + HashSet::from([(e1, A(1), B(3)), (e2, A(2), B(4))]) ); assert_eq!(world.entity_mut(e1).take::(), Some(A(1))); @@ -624,8 +630,8 @@ mod tests { .query::<(Entity, &B, &TableStored)>() .iter(&world) .map(|(e, &B(b), &TableStored(s))| (e, b, s)) - .collect::>(), - &[(e2, 4, "xyz"), (e1, 3, "abc")] + .collect::>(), + HashSet::from([(e2, 4, "xyz"), (e1, 3, "abc")]) ); world.entity_mut(e1).insert(A(43)); assert_eq!( @@ -633,8 +639,8 @@ mod tests { .query::<(Entity, &A, &B)>() .iter(&world) .map(|(e, &i, &b)| (e, i, b)) - .collect::>(), - &[(e2, A(2), B(4)), (e1, A(43), B(3))] + .collect::>(), + HashSet::from([(e2, A(2), B(4)), (e1, A(43), B(3))]) ); world.entity_mut(e1).insert(C); assert_eq!( @@ -923,25 +929,33 @@ mod tests { } } - fn get_filtered(world: &mut World) -> Vec { + fn get_filtered(world: &mut World) -> HashSet { world .query_filtered::() .iter(world) - .collect::>() + .collect::>() } - assert_eq!(get_filtered::>(&mut world), vec![e1, e3]); + assert_eq!( + get_filtered::>(&mut world), + HashSet::from([e1, e3]) + ); // ensure changing an entity's archetypes also moves its changed state world.entity_mut(e1).insert(C); - assert_eq!(get_filtered::>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)"); + assert_eq!( + get_filtered::>(&mut world), + HashSet::from([e3, e1]), + "changed entities list should not change" + ); // spawning a new A entity should not change existing changed state world.entity_mut(e1).insert((A(0), B(0))); + assert_eq!( get_filtered::>(&mut world), - vec![e3, e1], + HashSet::from([e3, e1]), "changed entities list should not change" ); @@ -949,7 +963,7 @@ mod tests { assert!(world.despawn(e2)); assert_eq!( get_filtered::>(&mut world), - vec![e3, e1], + HashSet::from([e3, e1]), "changed entities list should not change" ); @@ -957,7 +971,7 @@ mod tests { assert!(world.despawn(e1)); assert_eq!( get_filtered::>(&mut world), - vec![e3], + HashSet::from([e3]), "e1 should no longer be returned" ); @@ -968,11 +982,11 @@ mod tests { let e4 = world.spawn_empty().id(); world.entity_mut(e4).insert(A(0)); - assert_eq!(get_filtered::>(&mut world), vec![e4]); - assert_eq!(get_filtered::>(&mut world), vec![e4]); + assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); + assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); world.entity_mut(e4).insert(A(1)); - assert_eq!(get_filtered::>(&mut world), vec![e4]); + assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); world.clear_trackers(); @@ -981,9 +995,9 @@ mod tests { world.entity_mut(e4).insert((A(0), B(0))); assert!(get_filtered::>(&mut world).is_empty()); - assert_eq!(get_filtered::>(&mut world), vec![e4]); - assert_eq!(get_filtered::>(&mut world), vec![e4]); - assert_eq!(get_filtered::>(&mut world), vec![e4]); + assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); + assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); + assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); } #[test] @@ -1006,28 +1020,28 @@ mod tests { } } - fn get_filtered(world: &mut World) -> Vec { + fn get_filtered(world: &mut World) -> HashSet { world .query_filtered::() .iter(world) - .collect::>() + .collect::>() } assert_eq!( get_filtered::>(&mut world), - vec![e1, e3] + HashSet::from([e1, e3]) ); // ensure changing an entity's archetypes also moves its changed state world.entity_mut(e1).insert(C); - assert_eq!(get_filtered::>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)"); + assert_eq!(get_filtered::>(&mut world), HashSet::from([e3, e1]), "changed entities list should not change (although the order will due to archetype moves)"); // spawning a new SparseStored entity should not change existing changed state world.entity_mut(e1).insert(SparseStored(0)); assert_eq!( get_filtered::>(&mut world), - vec![e3, e1], + HashSet::from([e3, e1]), "changed entities list should not change" ); @@ -1035,7 +1049,7 @@ mod tests { assert!(world.despawn(e2)); assert_eq!( get_filtered::>(&mut world), - vec![e3, e1], + HashSet::from([e3, e1]), "changed entities list should not change" ); @@ -1043,7 +1057,7 @@ mod tests { assert!(world.despawn(e1)); assert_eq!( get_filtered::>(&mut world), - vec![e3], + HashSet::from([e3]), "e1 should no longer be returned" ); @@ -1054,11 +1068,20 @@ mod tests { let e4 = world.spawn_empty().id(); world.entity_mut(e4).insert(SparseStored(0)); - assert_eq!(get_filtered::>(&mut world), vec![e4]); - assert_eq!(get_filtered::>(&mut world), vec![e4]); + assert_eq!( + get_filtered::>(&mut world), + HashSet::from([e4]) + ); + assert_eq!( + get_filtered::>(&mut world), + HashSet::from([e4]) + ); world.entity_mut(e4).insert(A(1)); - assert_eq!(get_filtered::>(&mut world), vec![e4]); + assert_eq!( + get_filtered::>(&mut world), + HashSet::from([e4]) + ); world.clear_trackers(); @@ -1067,7 +1090,10 @@ mod tests { world.entity_mut(e4).insert(SparseStored(0)); assert!(get_filtered::>(&mut world).is_empty()); - assert_eq!(get_filtered::>(&mut world), vec![e4]); + assert_eq!( + get_filtered::>(&mut world), + HashSet::from([e4]) + ); } #[test] @@ -1249,8 +1275,8 @@ mod tests { let results = query .iter(&world) .map(|(a, b)| (a.0, b.0)) - .collect::>(); - assert_eq!(results, vec![(1, "1"), (2, "2"), (3, "3"),]); + .collect::>(); + assert_eq!(results, HashSet::from([(1, "1"), (2, "2"), (3, "3"),])); let removed_bundle = world.entity_mut(e2).take::<(B, TableStored)>().unwrap(); assert_eq!(removed_bundle, (B(2), TableStored("2"))); @@ -1258,12 +1284,12 @@ mod tests { let results = query .iter(&world) .map(|(a, b)| (a.0, b.0)) - .collect::>(); - assert_eq!(results, vec![(1, "1"), (3, "3"),]); + .collect::>(); + assert_eq!(results, HashSet::from([(1, "1"), (3, "3"),])); let mut a_query = world.query::<&A>(); - let results = a_query.iter(&world).map(|a| a.0).collect::>(); - assert_eq!(results, vec![1, 3, 2]); + let results = a_query.iter(&world).map(|a| a.0).collect::>(); + assert_eq!(results, HashSet::from([1, 3, 2])); let entity_ref = world.entity(e2); assert_eq!( diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 4cc2b9f345..68031a1b9c 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -78,17 +78,19 @@ mod tests { use crate::{self as bevy_ecs, component::Component, world::World}; use std::any::type_name; use std::collections::HashSet; + use std::fmt::Debug; + use std::hash::Hash; - #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy)] + #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] struct A(usize); - #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] + #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy)] struct B(usize); #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] struct C(usize); #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] struct D(usize); - #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] + #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] #[component(storage = "SparseSet")] struct Sparse(usize); @@ -97,8 +99,9 @@ mod tests { let mut world = World::new(); world.spawn((A(1), B(1))); world.spawn(A(2)); - let values = world.query::<&A>().iter(&world).collect::>(); - assert_eq!(values, vec![&A(1), &A(2)]); + let values = world.query::<&A>().iter(&world).collect::>(); + assert!(values.contains(&A(1))); + assert!(values.contains(&A(2))); for (_a, mut b) in world.query::<(&A, &mut B)>().iter_mut(&mut world) { b.0 = 3; @@ -244,6 +247,20 @@ mod tests { assert_all_sizes_equal::, With)>(&mut world, 6); } + // the order of the combinations is not guaranteed, but each unique combination is present + fn check_combinations( + values: HashSet<[&T; K]>, + expected: HashSet<[&T; K]>, + ) { + values.iter().for_each(|pair| { + let mut sorted = *pair; + sorted.sort(); + assert!(expected.contains(&sorted), + "the results of iter_combinations should contain this combination {:?}. Expected: {:?}, got: {:?}", + &sorted, &expected, &values); + }); + } + #[test] fn query_iter_combinations() { let mut world = World::new(); @@ -253,47 +270,29 @@ mod tests { world.spawn(A(3)); world.spawn(A(4)); - let values: Vec<[&A; 2]> = world.query::<&A>().iter_combinations(&world).collect(); - assert_eq!( + let values: HashSet<[&A; 2]> = world.query::<&A>().iter_combinations(&world).collect(); + check_combinations( values, - vec![ + HashSet::from([ [&A(1), &A(2)], [&A(1), &A(3)], [&A(1), &A(4)], [&A(2), &A(3)], [&A(2), &A(4)], [&A(3), &A(4)], - ] + ]), ); let mut a_query = world.query::<&A>(); - let values: Vec<[&A; 3]> = a_query.iter_combinations(&world).collect(); - assert_eq!( + + let values: HashSet<[&A; 3]> = a_query.iter_combinations(&world).collect(); + check_combinations( values, - vec![ + HashSet::from([ [&A(1), &A(2), &A(3)], [&A(1), &A(2), &A(4)], [&A(1), &A(3), &A(4)], [&A(2), &A(3), &A(4)], - ] - ); - - let mut query = world.query::<&mut A>(); - let mut combinations = query.iter_combinations_mut(&mut world); - while let Some([mut a, mut b, mut c]) = combinations.fetch_next() { - a.0 += 10; - b.0 += 100; - c.0 += 1000; - } - - let values: Vec<[&A; 3]> = a_query.iter_combinations(&world).collect(); - assert_eq!( - values, - vec![ - [&A(31), &A(212), &A(1203)], - [&A(31), &A(212), &A(3004)], - [&A(31), &A(1203), &A(3004)], - [&A(212), &A(1203), &A(3004)] - ] + ]), ); let mut b_query = world.query::<&B>(); @@ -307,7 +306,7 @@ mod tests { #[test] fn query_filtered_iter_combinations() { - use bevy_ecs::query::{Added, Changed, Or, With, Without}; + use bevy_ecs::query::{Added, Or, With, Without}; let mut world = World::new(); @@ -318,33 +317,26 @@ mod tests { let mut a_wout_b = world.query_filtered::<&A, Without>(); let values: HashSet<[&A; 2]> = a_wout_b.iter_combinations(&world).collect(); - assert_eq!( + check_combinations( values, - [[&A(2), &A(3)], [&A(2), &A(4)], [&A(3), &A(4)]] - .into_iter() - .collect::>() + HashSet::from([[&A(2), &A(3)], [&A(2), &A(4)], [&A(3), &A(4)]]), ); let values: HashSet<[&A; 3]> = a_wout_b.iter_combinations(&world).collect(); - assert_eq!( - values, - [[&A(2), &A(3), &A(4)],].into_iter().collect::>() - ); + check_combinations(values, HashSet::from([[&A(2), &A(3), &A(4)]])); let mut query = world.query_filtered::<&A, Or<(With, With)>>(); let values: HashSet<[&A; 2]> = query.iter_combinations(&world).collect(); - assert_eq!( + check_combinations( values, - [ + HashSet::from([ [&A(1), &A(2)], [&A(1), &A(3)], [&A(1), &A(4)], [&A(2), &A(3)], [&A(2), &A(4)], [&A(3), &A(4)], - ] - .into_iter() - .collect::>() + ]), ); let mut query = world.query_filtered::<&mut A, Without>(); @@ -356,12 +348,7 @@ mod tests { } let values: HashSet<[&A; 3]> = a_wout_b.iter_combinations(&world).collect(); - assert_eq!( - values, - [[&A(12), &A(103), &A(1004)],] - .into_iter() - .collect::>() - ); + check_combinations(values, HashSet::from([[&A(12), &A(103), &A(1004)]])); // Check if Added, Changed works let mut world = World::new(); @@ -390,31 +377,6 @@ mod tests { world.spawn(A(10)); assert_eq!(query_added.iter_combinations::<2>(&world).count(), 3); - - world.clear_trackers(); - - let mut query_changed = world.query_filtered::<&A, Changed>(); - - let mut query = world.query_filtered::<&mut A, With>(); - let mut combinations = query.iter_combinations_mut(&mut world); - while let Some([mut a, mut b, mut c]) = combinations.fetch_next() { - a.0 += 10; - b.0 += 100; - c.0 += 1000; - } - - let values: HashSet<[&A; 3]> = query_changed.iter_combinations(&world).collect(); - assert_eq!( - values, - [ - [&A(31), &A(212), &A(1203)], - [&A(31), &A(212), &A(3004)], - [&A(31), &A(1203), &A(3004)], - [&A(212), &A(1203), &A(3004)] - ] - .into_iter() - .collect::>() - ); } #[test] @@ -423,24 +385,16 @@ mod tests { world.spawn_batch((1..=4).map(Sparse)); - let mut query = world.query::<&mut Sparse>(); - let mut combinations = query.iter_combinations_mut(&mut world); - while let Some([mut a, mut b, mut c]) = combinations.fetch_next() { - a.0 += 10; - b.0 += 100; - c.0 += 1000; - } - - let mut query = world.query::<&Sparse>(); - let values: Vec<[&Sparse; 3]> = query.iter_combinations(&world).collect(); - assert_eq!( + let values: HashSet<[&Sparse; 3]> = + world.query::<&Sparse>().iter_combinations(&world).collect(); + check_combinations( values, - vec![ - [&Sparse(31), &Sparse(212), &Sparse(1203)], - [&Sparse(31), &Sparse(212), &Sparse(3004)], - [&Sparse(31), &Sparse(1203), &Sparse(3004)], - [&Sparse(212), &Sparse(1203), &Sparse(3004)] - ] + HashSet::from([ + [&Sparse(1), &Sparse(2), &Sparse(3)], + [&Sparse(1), &Sparse(2), &Sparse(4)], + [&Sparse(1), &Sparse(3), &Sparse(4)], + [&Sparse(2), &Sparse(3), &Sparse(4)], + ]), ); } @@ -454,8 +408,9 @@ mod tests { let values = world .query::<&Sparse>() .iter(&world) - .collect::>(); - assert_eq!(values, vec![&Sparse(1), &Sparse(2)]); + .collect::>(); + assert!(values.contains(&Sparse(1))); + assert!(values.contains(&Sparse(2))); for (_a, mut b) in world.query::<(&Sparse, &mut B)>().iter_mut(&mut world) { b.0 = 3; @@ -491,13 +446,12 @@ mod tests { world.spawn((A(3), B(1))); world.spawn(A(4)); - let values: Vec<(&A, bool)> = world.query::<(&A, Has)>().iter(&world).collect(); + let values: HashSet<(&A, bool)> = world.query::<(&A, Has)>().iter(&world).collect(); - // The query seems to put the components with B first - assert_eq!( - values, - vec![(&A(1), true), (&A(3), true), (&A(2), false), (&A(4), false),] - ); + assert!(values.contains(&(&A(1), true))); + assert!(values.contains(&(&A(2), false))); + assert!(values.contains(&(&A(3), true))); + assert!(values.contains(&(&A(4), false))); } #[test] diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index a5d431edbd..2d3c454201 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -341,16 +341,55 @@ impl QueryState { /// If `world` does not match the one used to call `QueryState::new` for this instance. pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { self.validate_world(world.id()); - let archetypes = world.archetypes(); - let old_generation = - std::mem::replace(&mut self.archetype_generation, archetypes.generation()); + if self.component_access.required.is_empty() { + let archetypes = world.archetypes(); + let old_generation = + std::mem::replace(&mut self.archetype_generation, archetypes.generation()); - for archetype in &archetypes[old_generation..] { - // SAFETY: The validate_world call ensures that the world is the same the QueryState - // was initialized from. - unsafe { - self.new_archetype_internal(archetype); + for archetype in &archetypes[old_generation..] { + // SAFETY: The validate_world call ensures that the world is the same the QueryState + // was initialized from. + unsafe { + self.new_archetype_internal(archetype); + } } + } else { + // skip if we are already up to date + if self.archetype_generation == world.archetypes().generation() { + return; + } + // if there are required components, we can optimize by only iterating through archetypes + // that contain at least one of the required components + let potential_archetypes = self + .component_access + .required + .ones() + .filter_map(|idx| { + let component_id = ComponentId::get_sparse_set_index(idx); + world + .archetypes() + .component_index() + .get(&component_id) + .map(|index| index.keys()) + }) + // select the component with the fewest archetypes + .min_by_key(std::iter::ExactSizeIterator::len); + if let Some(archetypes) = potential_archetypes { + for archetype_id in archetypes { + // exclude archetypes that have already been processed + if archetype_id < &self.archetype_generation.0 { + continue; + } + // SAFETY: get_potential_archetypes only returns archetype ids that are valid for the world + let archetype = &world.archetypes()[*archetype_id]; + // SAFETY: The validate_world call ensures that the world is the same the QueryState + // was initialized from. + unsafe { + self.new_archetype_internal(archetype); + } + } + } + self.archetype_generation = world.archetypes().generation(); } } @@ -1540,7 +1579,7 @@ impl QueryState { pub fn single<'w>(&mut self, world: &'w World) -> ROQueryItem<'w, D> { match self.get_single(world) { Ok(items) => items, - Err(error) => panic!("Cannot get single mutable query result: {error}"), + Err(error) => panic!("Cannot get single query result: {error}"), } }