Add a ComponentIndex and update QueryState creation/update to use it (#13460)

# Objective

To implement relations we will need to add a `ComponentIndex`, which is
a map from a Component to the list of archetypes that contain this
component.
One of the reasons is that with fragmenting relations the number of
archetypes will explode, so it will become inefficient to create and
update the query caches by iterating through the list of all archetypes.

In this PR, we introduce the `ComponentIndex`, and we update the
`QueryState` to make use of it:
- if a query has at least 1 required component (i.e. something other
than `()`, `Entity` or `Option<>`, etc.): for each of the required
components we find the list of archetypes that contain it (using the
ComponentIndex). Then, we select the smallest list among these. This
gives a small subset of archetypes to iterate through compared with
iterating through all new archetypes
- if it doesn't, then we keep using the current approach of iterating
through all new archetypes


# Implementation
- This breaks query iteration order, in the sense that we are not
guaranteed anymore to return results in the order in which the
archetypes were created. I think this should be fine because this wasn't
an explicit bevy guarantee so users should not be relying on this. I
updated a bunch of unit tests that were failing because of this.

- I had an issue with the borrow checker because iterating the list of
potential archetypes requires access to `&state.component_access`, which
was conflicting with the calls to
```
  if state.new_archetype_internal(archetype) {
      state.update_archetype_component_access(archetype, access);
  }
```
which need a mutable access to the state.

The solution I chose was to introduce a `QueryStateView` which is a
temporary view into the `QueryState` which enables a "split-borrows"
kind of approach. It is described in detail in this blog post:
https://smallcultfollowing.com/babysteps/blog/2018/11/01/after-nll-interprocedural-conflicts/

# Test

The unit tests pass.

Benchmark results:
```
❯ critcmp main pr
group                                  main                                   pr
-----                                  ----                                   --
iter_fragmented/base                   1.00   342.2±25.45ns        ? ?/sec    1.02   347.5±16.24ns        ? ?/sec
iter_fragmented/foreach                1.04   165.4±11.29ns        ? ?/sec    1.00    159.5±4.27ns        ? ?/sec
iter_fragmented/foreach_wide           1.03      3.3±0.04µs        ? ?/sec    1.00      3.2±0.06µs        ? ?/sec
iter_fragmented/wide                   1.03      3.1±0.06µs        ? ?/sec    1.00      3.0±0.08µs        ? ?/sec
iter_fragmented_sparse/base            1.00      6.5±0.14ns        ? ?/sec    1.02      6.6±0.08ns        ? ?/sec
iter_fragmented_sparse/foreach         1.00      6.3±0.08ns        ? ?/sec    1.04      6.6±0.08ns        ? ?/sec
iter_fragmented_sparse/foreach_wide    1.00     43.8±0.15ns        ? ?/sec    1.02     44.6±0.53ns        ? ?/sec
iter_fragmented_sparse/wide            1.00     29.8±0.44ns        ? ?/sec    1.00     29.8±0.26ns        ? ?/sec
iter_simple/base                       1.00      8.2±0.10µs        ? ?/sec    1.00      8.2±0.09µs        ? ?/sec
iter_simple/foreach                    1.00      3.8±0.02µs        ? ?/sec    1.02      3.9±0.03µs        ? ?/sec
iter_simple/foreach_sparse_set         1.00     19.0±0.26µs        ? ?/sec    1.01     19.3±0.16µs        ? ?/sec
iter_simple/foreach_wide               1.00     17.8±0.24µs        ? ?/sec    1.00     17.9±0.31µs        ? ?/sec
iter_simple/foreach_wide_sparse_set    1.06     95.6±6.23µs        ? ?/sec    1.00     90.6±0.59µs        ? ?/sec
iter_simple/sparse_set                 1.00     19.3±1.63µs        ? ?/sec    1.01     19.5±0.29µs        ? ?/sec
iter_simple/system                     1.00      8.1±0.10µs        ? ?/sec    1.00      8.1±0.09µs        ? ?/sec
iter_simple/wide                       1.05     37.7±2.53µs        ? ?/sec    1.00     35.8±0.57µs        ? ?/sec
iter_simple/wide_sparse_set            1.00     95.7±1.62µs        ? ?/sec    1.00     95.9±0.76µs        ? ?/sec
par_iter_simple/with_0_fragment        1.04     35.0±2.51µs        ? ?/sec    1.00     33.7±0.49µs        ? ?/sec
par_iter_simple/with_1000_fragment     1.00     50.4±2.52µs        ? ?/sec    1.01     51.0±3.84µs        ? ?/sec
par_iter_simple/with_100_fragment      1.02     40.3±2.23µs        ? ?/sec    1.00     39.5±1.32µs        ? ?/sec
par_iter_simple/with_10_fragment       1.14     38.8±7.79µs        ? ?/sec    1.00     34.0±0.78µs        ? ?/sec
```
This commit is contained in:
Periwink 2024-08-05 20:57:15 -04:00 committed by GitHub
parent 68ec6f4f50
commit ec4cf024f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 233 additions and 178 deletions

View file

@ -8,8 +8,5 @@ fn torus(c: &mut Criterion) {
}); });
} }
criterion_group!( criterion_group!(benches, torus,);
benches,
torus,
);
criterion_main!(benches); criterion_main!(benches);

View file

@ -26,6 +26,7 @@ use crate::{
observer::Observers, observer::Observers,
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
}; };
use bevy_utils::HashMap;
use std::{ use std::{
hash::Hash, hash::Hash,
ops::{Index, IndexMut, RangeFrom}, ops::{Index, IndexMut, RangeFrom},
@ -345,8 +346,10 @@ pub struct Archetype {
} }
impl Archetype { impl Archetype {
/// `table_components` and `sparse_set_components` must be sorted
pub(crate) fn new( pub(crate) fn new(
components: &Components, components: &Components,
component_index: &mut ComponentIndex,
observers: &Observers, observers: &Observers,
id: ArchetypeId, id: ArchetypeId,
table_id: TableId, table_id: TableId,
@ -357,7 +360,7 @@ impl Archetype {
let (min_sparse, _) = sparse_set_components.size_hint(); let (min_sparse, _) = sparse_set_components.size_hint();
let mut flags = ArchetypeFlags::empty(); let mut flags = ArchetypeFlags::empty();
let mut archetype_components = SparseSet::with_capacity(min_table + min_sparse); 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 // SAFETY: We are creating an archetype that includes this component so it must exist
let info = unsafe { components.get_info_unchecked(component_id) }; let info = unsafe { components.get_info_unchecked(component_id) };
info.update_archetype_flags(&mut flags); info.update_archetype_flags(&mut flags);
@ -369,6 +372,13 @@ impl Archetype {
archetype_component_id, 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 { for (component_id, archetype_component_id) in sparse_set_components {
@ -383,6 +393,10 @@ impl Archetype {
archetype_component_id, archetype_component_id,
}, },
); );
component_index
.entry(component_id)
.or_insert_with(HashMap::new)
.insert(id, ArchetypeRecord { column: None });
} }
Self { Self {
id, id,
@ -460,7 +474,7 @@ impl Archetype {
self.components.len() 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. /// archetypal relationships.
#[inline] #[inline]
pub fn edges(&self) -> &Edges { pub fn edges(&self) -> &Edges {
@ -655,7 +669,7 @@ impl Archetype {
/// This is used in archetype update methods to limit archetype updates to the /// This is used in archetype update methods to limit archetype updates to the
/// ones added since the last time the method ran. /// ones added since the last time the method ran.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct ArchetypeGeneration(ArchetypeId); pub struct ArchetypeGeneration(pub(crate) ArchetypeId);
impl ArchetypeGeneration { impl ArchetypeGeneration {
/// The first archetype. /// 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<ComponentId, HashMap<ArchetypeId, ArchetypeRecord>>;
/// The backing store of all [`Archetype`]s within a [`World`]. /// The backing store of all [`Archetype`]s within a [`World`].
/// ///
/// For more information, see the *[module level documentation]*. /// For more information, see the *[module level documentation]*.
@ -720,7 +738,18 @@ impl SparseSetIndex for ArchetypeComponentId {
pub struct Archetypes { pub struct Archetypes {
pub(crate) archetypes: Vec<Archetype>, pub(crate) archetypes: Vec<Archetype>,
archetype_component_count: usize, archetype_component_count: usize,
by_components: bevy_utils::HashMap<ArchetypeComponents, ArchetypeId>, /// find the archetype id by the archetype's components
by_components: HashMap<ArchetypeComponents, ArchetypeId>,
/// 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<usize>,
} }
impl Archetypes { impl Archetypes {
@ -728,6 +757,7 @@ impl Archetypes {
let mut archetypes = Archetypes { let mut archetypes = Archetypes {
archetypes: Vec::new(), archetypes: Vec::new(),
by_components: Default::default(), by_components: Default::default(),
by_component: Default::default(),
archetype_component_count: 0, archetype_component_count: 0,
}; };
// SAFETY: Empty archetype has no components // SAFETY: Empty archetype has no components
@ -770,7 +800,7 @@ impl Archetypes {
unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) } 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] #[inline]
pub(crate) fn empty_mut(&mut self) -> &mut Archetype { pub(crate) fn empty_mut(&mut self) -> &mut Archetype {
// SAFETY: empty archetype always exists // SAFETY: empty archetype always exists
@ -848,7 +878,8 @@ impl Archetypes {
let archetypes = &mut self.archetypes; let archetypes = &mut self.archetypes;
let archetype_component_count = &mut self.archetype_component_count; let archetype_component_count = &mut self.archetype_component_count;
*self let component_index = &mut self.by_component;
let archetype_id = *self
.by_components .by_components
.entry(archetype_identity) .entry(archetype_identity)
.or_insert_with_key(move |identity| { .or_insert_with_key(move |identity| {
@ -856,20 +887,18 @@ impl Archetypes {
table_components, table_components,
sparse_set_components, sparse_set_components,
} = identity; } = identity;
let id = ArchetypeId::new(archetypes.len()); let id = ArchetypeId::new(archetypes.len());
let table_start = *archetype_component_count; let table_start = *archetype_component_count;
*archetype_component_count += table_components.len(); *archetype_component_count += table_components.len();
let table_archetype_components = let table_archetype_components =
(table_start..*archetype_component_count).map(ArchetypeComponentId); (table_start..*archetype_component_count).map(ArchetypeComponentId);
let sparse_start = *archetype_component_count; let sparse_start = *archetype_component_count;
*archetype_component_count += sparse_set_components.len(); *archetype_component_count += sparse_set_components.len();
let sparse_set_archetype_components = let sparse_set_archetype_components =
(sparse_start..*archetype_component_count).map(ArchetypeComponentId); (sparse_start..*archetype_component_count).map(ArchetypeComponentId);
archetypes.push(Archetype::new( archetypes.push(Archetype::new(
components, components,
component_index,
observers, observers,
id, id,
table_id, table_id,
@ -883,7 +912,8 @@ impl Archetypes {
.zip(sparse_set_archetype_components), .zip(sparse_set_archetype_components),
)); ));
id id
}) });
archetype_id
} }
/// Returns the number of components that are stored in archetypes. /// 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( pub(crate) fn update_flags(
&mut self, &mut self,
component_id: ComponentId, component_id: ComponentId,
flags: ArchetypeFlags, flags: ArchetypeFlags,
set: bool, set: bool,
) { ) {
// TODO: Refactor component index to speed this up. if let Some(archetypes) = self.by_component.get(&component_id) {
for archetype in &mut self.archetypes { for archetype_id in archetypes.keys() {
if archetype.contains(component_id) { // SAFETY: the component index only contains valid archetype ids
archetype.flags.set(flags, set); self.archetypes
.get_mut(archetype_id.index())
.unwrap()
.flags
.set(flags, set);
} }
} }
} }

View file

@ -81,6 +81,7 @@ mod tests {
world::{EntityRef, Mut, World}, world::{EntityRef, Mut, World},
}; };
use bevy_tasks::{ComputeTaskPool, TaskPool}; use bevy_tasks::{ComputeTaskPool, TaskPool};
use bevy_utils::HashSet;
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::{ use std::{
any::TypeId, 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); struct A(usize);
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] #[derive(Component, Debug, PartialEq, Eq, Hash, Clone, Copy)]
struct B(usize); struct B(usize);
#[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)]
struct C; struct C;
@ -126,7 +127,7 @@ mod tests {
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] #[derive(Component, Copy, Clone, PartialEq, Eq, Debug)]
#[component(storage = "Table")] #[component(storage = "Table")]
struct TableStored(&'static str); struct TableStored(&'static str);
#[derive(Component, Copy, Clone, PartialEq, Eq, Debug)] #[derive(Component, Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[component(storage = "SparseSet")] #[component(storage = "SparseSet")]
struct SparseStored(u32); struct SparseStored(u32);
@ -379,8 +380,9 @@ mod tests {
.query::<(Entity, &A)>() .query::<(Entity, &A)>()
.iter(&world) .iter(&world)
.map(|(e, &i)| (e, i)) .map(|(e, &i)| (e, i))
.collect::<Vec<_>>(); .collect::<HashSet<_>>();
assert_eq!(ents, &[(e, A(123)), (f, A(456))]); assert!(ents.contains(&(e, A(123))));
assert!(ents.contains(&(f, A(456))));
} }
#[test] #[test]
@ -402,12 +404,15 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
let e = world.spawn((TableStored("abc"), A(123))).id(); let e = world.spawn((TableStored("abc"), A(123))).id();
let f = world.spawn((TableStored("def"), A(456), B(1))).id(); let f = world.spawn((TableStored("def"), A(456), B(1))).id();
let mut results = Vec::new(); let mut results = HashSet::new();
world world
.query::<(Entity, &A)>() .query::<(Entity, &A)>()
.iter(&world) .iter(&world)
.for_each(|(e, &i)| results.push((e, i))); .for_each(|(e, &i)| {
assert_eq!(results, &[(e, A(123)), (f, A(456))]); results.insert((e, i));
});
assert!(results.contains(&(e, A(123))));
assert!(results.contains(&(f, A(456))));
} }
#[test] #[test]
@ -554,8 +559,9 @@ mod tests {
.query::<(Entity, Option<&B>, &A)>() .query::<(Entity, Option<&B>, &A)>()
.iter(&world) .iter(&world)
.map(|(e, b, &i)| (e, b.copied(), i)) .map(|(e, b, &i)| (e, b.copied(), i))
.collect::<Vec<_>>(); .collect::<HashSet<_>>();
assert_eq!(ents, &[(e, None, A(123)), (f, Some(B(1)), A(456))]); assert!(ents.contains(&(e, None, A(123))));
assert!(ents.contains(&(f, Some(B(1)), A(456))));
} }
#[test] #[test]
@ -572,10 +578,10 @@ mod tests {
.query::<(Entity, Option<&SparseStored>, &A)>() .query::<(Entity, Option<&SparseStored>, &A)>()
.iter(&world) .iter(&world)
.map(|(e, b, &i)| (e, b.copied(), i)) .map(|(e, b, &i)| (e, b.copied(), i))
.collect::<Vec<_>>(); .collect::<HashSet<_>>();
assert_eq!( assert_eq!(
ents, 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)>() .query::<(Entity, &A, &B)>()
.iter(&world) .iter(&world)
.map(|(e, &i, &b)| (e, i, b)) .map(|(e, &i, &b)| (e, i, b))
.collect::<Vec<_>>(), .collect::<HashSet<_>>(),
&[(e1, A(1), B(3)), (e2, A(2), B(4))] HashSet::from([(e1, A(1), B(3)), (e2, A(2), B(4))])
); );
assert_eq!(world.entity_mut(e1).take::<A>(), Some(A(1))); assert_eq!(world.entity_mut(e1).take::<A>(), Some(A(1)));
@ -624,8 +630,8 @@ mod tests {
.query::<(Entity, &B, &TableStored)>() .query::<(Entity, &B, &TableStored)>()
.iter(&world) .iter(&world)
.map(|(e, &B(b), &TableStored(s))| (e, b, s)) .map(|(e, &B(b), &TableStored(s))| (e, b, s))
.collect::<Vec<_>>(), .collect::<HashSet<_>>(),
&[(e2, 4, "xyz"), (e1, 3, "abc")] HashSet::from([(e2, 4, "xyz"), (e1, 3, "abc")])
); );
world.entity_mut(e1).insert(A(43)); world.entity_mut(e1).insert(A(43));
assert_eq!( assert_eq!(
@ -633,8 +639,8 @@ mod tests {
.query::<(Entity, &A, &B)>() .query::<(Entity, &A, &B)>()
.iter(&world) .iter(&world)
.map(|(e, &i, &b)| (e, i, b)) .map(|(e, &i, &b)| (e, i, b))
.collect::<Vec<_>>(), .collect::<HashSet<_>>(),
&[(e2, A(2), B(4)), (e1, A(43), B(3))] HashSet::from([(e2, A(2), B(4)), (e1, A(43), B(3))])
); );
world.entity_mut(e1).insert(C); world.entity_mut(e1).insert(C);
assert_eq!( assert_eq!(
@ -923,25 +929,33 @@ mod tests {
} }
} }
fn get_filtered<F: QueryFilter>(world: &mut World) -> Vec<Entity> { fn get_filtered<F: QueryFilter>(world: &mut World) -> HashSet<Entity> {
world world
.query_filtered::<Entity, F>() .query_filtered::<Entity, F>()
.iter(world) .iter(world)
.collect::<Vec<Entity>>() .collect::<HashSet<Entity>>()
} }
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e1, e3]); assert_eq!(
get_filtered::<Changed<A>>(&mut world),
HashSet::from([e1, e3])
);
// ensure changing an entity's archetypes also moves its changed state // ensure changing an entity's archetypes also moves its changed state
world.entity_mut(e1).insert(C); world.entity_mut(e1).insert(C);
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)"); assert_eq!(
get_filtered::<Changed<A>>(&mut world),
HashSet::from([e3, e1]),
"changed entities list should not change"
);
// spawning a new A entity should not change existing changed state // spawning a new A entity should not change existing changed state
world.entity_mut(e1).insert((A(0), B(0))); world.entity_mut(e1).insert((A(0), B(0)));
assert_eq!( assert_eq!(
get_filtered::<Changed<A>>(&mut world), get_filtered::<Changed<A>>(&mut world),
vec![e3, e1], HashSet::from([e3, e1]),
"changed entities list should not change" "changed entities list should not change"
); );
@ -949,7 +963,7 @@ mod tests {
assert!(world.despawn(e2)); assert!(world.despawn(e2));
assert_eq!( assert_eq!(
get_filtered::<Changed<A>>(&mut world), get_filtered::<Changed<A>>(&mut world),
vec![e3, e1], HashSet::from([e3, e1]),
"changed entities list should not change" "changed entities list should not change"
); );
@ -957,7 +971,7 @@ mod tests {
assert!(world.despawn(e1)); assert!(world.despawn(e1));
assert_eq!( assert_eq!(
get_filtered::<Changed<A>>(&mut world), get_filtered::<Changed<A>>(&mut world),
vec![e3], HashSet::from([e3]),
"e1 should no longer be returned" "e1 should no longer be returned"
); );
@ -968,11 +982,11 @@ mod tests {
let e4 = world.spawn_empty().id(); let e4 = world.spawn_empty().id();
world.entity_mut(e4).insert(A(0)); world.entity_mut(e4).insert(A(0));
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e4]); assert_eq!(get_filtered::<Changed<A>>(&mut world), HashSet::from([e4]));
assert_eq!(get_filtered::<Added<A>>(&mut world), vec![e4]); assert_eq!(get_filtered::<Added<A>>(&mut world), HashSet::from([e4]));
world.entity_mut(e4).insert(A(1)); world.entity_mut(e4).insert(A(1));
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e4]); assert_eq!(get_filtered::<Changed<A>>(&mut world), HashSet::from([e4]));
world.clear_trackers(); world.clear_trackers();
@ -981,9 +995,9 @@ mod tests {
world.entity_mut(e4).insert((A(0), B(0))); world.entity_mut(e4).insert((A(0), B(0)));
assert!(get_filtered::<Added<A>>(&mut world).is_empty()); assert!(get_filtered::<Added<A>>(&mut world).is_empty());
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e4]); assert_eq!(get_filtered::<Changed<A>>(&mut world), HashSet::from([e4]));
assert_eq!(get_filtered::<Added<B>>(&mut world), vec![e4]); assert_eq!(get_filtered::<Added<B>>(&mut world), HashSet::from([e4]));
assert_eq!(get_filtered::<Changed<B>>(&mut world), vec![e4]); assert_eq!(get_filtered::<Changed<B>>(&mut world), HashSet::from([e4]));
} }
#[test] #[test]
@ -1006,28 +1020,28 @@ mod tests {
} }
} }
fn get_filtered<F: QueryFilter>(world: &mut World) -> Vec<Entity> { fn get_filtered<F: QueryFilter>(world: &mut World) -> HashSet<Entity> {
world world
.query_filtered::<Entity, F>() .query_filtered::<Entity, F>()
.iter(world) .iter(world)
.collect::<Vec<Entity>>() .collect::<HashSet<Entity>>()
} }
assert_eq!( assert_eq!(
get_filtered::<Changed<SparseStored>>(&mut world), get_filtered::<Changed<SparseStored>>(&mut world),
vec![e1, e3] HashSet::from([e1, e3])
); );
// ensure changing an entity's archetypes also moves its changed state // ensure changing an entity's archetypes also moves its changed state
world.entity_mut(e1).insert(C); world.entity_mut(e1).insert(C);
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e3, e1], "changed entities list should not change (although the order will due to archetype moves)"); assert_eq!(get_filtered::<Changed<SparseStored>>(&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 // spawning a new SparseStored entity should not change existing changed state
world.entity_mut(e1).insert(SparseStored(0)); world.entity_mut(e1).insert(SparseStored(0));
assert_eq!( assert_eq!(
get_filtered::<Changed<SparseStored>>(&mut world), get_filtered::<Changed<SparseStored>>(&mut world),
vec![e3, e1], HashSet::from([e3, e1]),
"changed entities list should not change" "changed entities list should not change"
); );
@ -1035,7 +1049,7 @@ mod tests {
assert!(world.despawn(e2)); assert!(world.despawn(e2));
assert_eq!( assert_eq!(
get_filtered::<Changed<SparseStored>>(&mut world), get_filtered::<Changed<SparseStored>>(&mut world),
vec![e3, e1], HashSet::from([e3, e1]),
"changed entities list should not change" "changed entities list should not change"
); );
@ -1043,7 +1057,7 @@ mod tests {
assert!(world.despawn(e1)); assert!(world.despawn(e1));
assert_eq!( assert_eq!(
get_filtered::<Changed<SparseStored>>(&mut world), get_filtered::<Changed<SparseStored>>(&mut world),
vec![e3], HashSet::from([e3]),
"e1 should no longer be returned" "e1 should no longer be returned"
); );
@ -1054,11 +1068,20 @@ mod tests {
let e4 = world.spawn_empty().id(); let e4 = world.spawn_empty().id();
world.entity_mut(e4).insert(SparseStored(0)); world.entity_mut(e4).insert(SparseStored(0));
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e4]); assert_eq!(
assert_eq!(get_filtered::<Added<SparseStored>>(&mut world), vec![e4]); get_filtered::<Changed<SparseStored>>(&mut world),
HashSet::from([e4])
);
assert_eq!(
get_filtered::<Added<SparseStored>>(&mut world),
HashSet::from([e4])
);
world.entity_mut(e4).insert(A(1)); world.entity_mut(e4).insert(A(1));
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e4]); assert_eq!(
get_filtered::<Changed<SparseStored>>(&mut world),
HashSet::from([e4])
);
world.clear_trackers(); world.clear_trackers();
@ -1067,7 +1090,10 @@ mod tests {
world.entity_mut(e4).insert(SparseStored(0)); world.entity_mut(e4).insert(SparseStored(0));
assert!(get_filtered::<Added<SparseStored>>(&mut world).is_empty()); assert!(get_filtered::<Added<SparseStored>>(&mut world).is_empty());
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e4]); assert_eq!(
get_filtered::<Changed<SparseStored>>(&mut world),
HashSet::from([e4])
);
} }
#[test] #[test]
@ -1249,8 +1275,8 @@ mod tests {
let results = query let results = query
.iter(&world) .iter(&world)
.map(|(a, b)| (a.0, b.0)) .map(|(a, b)| (a.0, b.0))
.collect::<Vec<_>>(); .collect::<HashSet<_>>();
assert_eq!(results, vec![(1, "1"), (2, "2"), (3, "3"),]); assert_eq!(results, HashSet::from([(1, "1"), (2, "2"), (3, "3"),]));
let removed_bundle = world.entity_mut(e2).take::<(B, TableStored)>().unwrap(); let removed_bundle = world.entity_mut(e2).take::<(B, TableStored)>().unwrap();
assert_eq!(removed_bundle, (B(2), TableStored("2"))); assert_eq!(removed_bundle, (B(2), TableStored("2")));
@ -1258,12 +1284,12 @@ mod tests {
let results = query let results = query
.iter(&world) .iter(&world)
.map(|(a, b)| (a.0, b.0)) .map(|(a, b)| (a.0, b.0))
.collect::<Vec<_>>(); .collect::<HashSet<_>>();
assert_eq!(results, vec![(1, "1"), (3, "3"),]); assert_eq!(results, HashSet::from([(1, "1"), (3, "3"),]));
let mut a_query = world.query::<&A>(); let mut a_query = world.query::<&A>();
let results = a_query.iter(&world).map(|a| a.0).collect::<Vec<_>>(); let results = a_query.iter(&world).map(|a| a.0).collect::<HashSet<_>>();
assert_eq!(results, vec![1, 3, 2]); assert_eq!(results, HashSet::from([1, 3, 2]));
let entity_ref = world.entity(e2); let entity_ref = world.entity(e2);
assert_eq!( assert_eq!(

View file

@ -78,17 +78,19 @@ mod tests {
use crate::{self as bevy_ecs, component::Component, world::World}; use crate::{self as bevy_ecs, component::Component, world::World};
use std::any::type_name; use std::any::type_name;
use std::collections::HashSet; 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); struct A(usize);
#[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy)]
struct B(usize); struct B(usize);
#[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)]
struct C(usize); struct C(usize);
#[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] #[derive(Component, Debug, Eq, PartialEq, Clone, Copy)]
struct D(usize); struct D(usize);
#[derive(Component, Debug, Eq, PartialEq, Clone, Copy)] #[derive(Component, Debug, Hash, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)]
#[component(storage = "SparseSet")] #[component(storage = "SparseSet")]
struct Sparse(usize); struct Sparse(usize);
@ -97,8 +99,9 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
world.spawn((A(1), B(1))); world.spawn((A(1), B(1)));
world.spawn(A(2)); world.spawn(A(2));
let values = world.query::<&A>().iter(&world).collect::<Vec<&A>>(); let values = world.query::<&A>().iter(&world).collect::<HashSet<&A>>();
assert_eq!(values, vec![&A(1), &A(2)]); assert!(values.contains(&A(1)));
assert!(values.contains(&A(2)));
for (_a, mut b) in world.query::<(&A, &mut B)>().iter_mut(&mut world) { for (_a, mut b) in world.query::<(&A, &mut B)>().iter_mut(&mut world) {
b.0 = 3; b.0 = 3;
@ -244,6 +247,20 @@ mod tests {
assert_all_sizes_equal::<Entity, (With<C>, With<D>)>(&mut world, 6); assert_all_sizes_equal::<Entity, (With<C>, With<D>)>(&mut world, 6);
} }
// the order of the combinations is not guaranteed, but each unique combination is present
fn check_combinations<T: Ord + Hash + Debug, const K: usize>(
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] #[test]
fn query_iter_combinations() { fn query_iter_combinations() {
let mut world = World::new(); let mut world = World::new();
@ -253,47 +270,29 @@ mod tests {
world.spawn(A(3)); world.spawn(A(3));
world.spawn(A(4)); world.spawn(A(4));
let values: Vec<[&A; 2]> = world.query::<&A>().iter_combinations(&world).collect(); let values: HashSet<[&A; 2]> = world.query::<&A>().iter_combinations(&world).collect();
assert_eq!( check_combinations(
values, values,
vec![ HashSet::from([
[&A(1), &A(2)], [&A(1), &A(2)],
[&A(1), &A(3)], [&A(1), &A(3)],
[&A(1), &A(4)], [&A(1), &A(4)],
[&A(2), &A(3)], [&A(2), &A(3)],
[&A(2), &A(4)], [&A(2), &A(4)],
[&A(3), &A(4)], [&A(3), &A(4)],
] ]),
); );
let mut a_query = world.query::<&A>(); 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, values,
vec![ HashSet::from([
[&A(1), &A(2), &A(3)], [&A(1), &A(2), &A(3)],
[&A(1), &A(2), &A(4)], [&A(1), &A(2), &A(4)],
[&A(1), &A(3), &A(4)], [&A(1), &A(3), &A(4)],
[&A(2), &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>(); let mut b_query = world.query::<&B>();
@ -307,7 +306,7 @@ mod tests {
#[test] #[test]
fn query_filtered_iter_combinations() { 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(); let mut world = World::new();
@ -318,33 +317,26 @@ mod tests {
let mut a_wout_b = world.query_filtered::<&A, Without<B>>(); let mut a_wout_b = world.query_filtered::<&A, Without<B>>();
let values: HashSet<[&A; 2]> = a_wout_b.iter_combinations(&world).collect(); let values: HashSet<[&A; 2]> = a_wout_b.iter_combinations(&world).collect();
assert_eq!( check_combinations(
values, values,
[[&A(2), &A(3)], [&A(2), &A(4)], [&A(3), &A(4)]] HashSet::from([[&A(2), &A(3)], [&A(2), &A(4)], [&A(3), &A(4)]]),
.into_iter()
.collect::<HashSet<_>>()
); );
let values: HashSet<[&A; 3]> = a_wout_b.iter_combinations(&world).collect(); let values: HashSet<[&A; 3]> = a_wout_b.iter_combinations(&world).collect();
assert_eq!( check_combinations(values, HashSet::from([[&A(2), &A(3), &A(4)]]));
values,
[[&A(2), &A(3), &A(4)],].into_iter().collect::<HashSet<_>>()
);
let mut query = world.query_filtered::<&A, Or<(With<A>, With<B>)>>(); let mut query = world.query_filtered::<&A, Or<(With<A>, With<B>)>>();
let values: HashSet<[&A; 2]> = query.iter_combinations(&world).collect(); let values: HashSet<[&A; 2]> = query.iter_combinations(&world).collect();
assert_eq!( check_combinations(
values, values,
[ HashSet::from([
[&A(1), &A(2)], [&A(1), &A(2)],
[&A(1), &A(3)], [&A(1), &A(3)],
[&A(1), &A(4)], [&A(1), &A(4)],
[&A(2), &A(3)], [&A(2), &A(3)],
[&A(2), &A(4)], [&A(2), &A(4)],
[&A(3), &A(4)], [&A(3), &A(4)],
] ]),
.into_iter()
.collect::<HashSet<_>>()
); );
let mut query = world.query_filtered::<&mut A, Without<B>>(); let mut query = world.query_filtered::<&mut A, Without<B>>();
@ -356,12 +348,7 @@ mod tests {
} }
let values: HashSet<[&A; 3]> = a_wout_b.iter_combinations(&world).collect(); let values: HashSet<[&A; 3]> = a_wout_b.iter_combinations(&world).collect();
assert_eq!( check_combinations(values, HashSet::from([[&A(12), &A(103), &A(1004)]]));
values,
[[&A(12), &A(103), &A(1004)],]
.into_iter()
.collect::<HashSet<_>>()
);
// Check if Added<T>, Changed<T> works // Check if Added<T>, Changed<T> works
let mut world = World::new(); let mut world = World::new();
@ -390,31 +377,6 @@ mod tests {
world.spawn(A(10)); world.spawn(A(10));
assert_eq!(query_added.iter_combinations::<2>(&world).count(), 3); assert_eq!(query_added.iter_combinations::<2>(&world).count(), 3);
world.clear_trackers();
let mut query_changed = world.query_filtered::<&A, Changed<A>>();
let mut query = world.query_filtered::<&mut A, With<B>>();
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::<HashSet<_>>()
);
} }
#[test] #[test]
@ -423,24 +385,16 @@ mod tests {
world.spawn_batch((1..=4).map(Sparse)); world.spawn_batch((1..=4).map(Sparse));
let mut query = world.query::<&mut Sparse>(); let values: HashSet<[&Sparse; 3]> =
let mut combinations = query.iter_combinations_mut(&mut world); world.query::<&Sparse>().iter_combinations(&world).collect();
while let Some([mut a, mut b, mut c]) = combinations.fetch_next() { check_combinations(
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!(
values, values,
vec![ HashSet::from([
[&Sparse(31), &Sparse(212), &Sparse(1203)], [&Sparse(1), &Sparse(2), &Sparse(3)],
[&Sparse(31), &Sparse(212), &Sparse(3004)], [&Sparse(1), &Sparse(2), &Sparse(4)],
[&Sparse(31), &Sparse(1203), &Sparse(3004)], [&Sparse(1), &Sparse(3), &Sparse(4)],
[&Sparse(212), &Sparse(1203), &Sparse(3004)] [&Sparse(2), &Sparse(3), &Sparse(4)],
] ]),
); );
} }
@ -454,8 +408,9 @@ mod tests {
let values = world let values = world
.query::<&Sparse>() .query::<&Sparse>()
.iter(&world) .iter(&world)
.collect::<Vec<&Sparse>>(); .collect::<HashSet<&Sparse>>();
assert_eq!(values, vec![&Sparse(1), &Sparse(2)]); assert!(values.contains(&Sparse(1)));
assert!(values.contains(&Sparse(2)));
for (_a, mut b) in world.query::<(&Sparse, &mut B)>().iter_mut(&mut world) { for (_a, mut b) in world.query::<(&Sparse, &mut B)>().iter_mut(&mut world) {
b.0 = 3; b.0 = 3;
@ -491,13 +446,12 @@ mod tests {
world.spawn((A(3), B(1))); world.spawn((A(3), B(1)));
world.spawn(A(4)); world.spawn(A(4));
let values: Vec<(&A, bool)> = world.query::<(&A, Has<B>)>().iter(&world).collect(); let values: HashSet<(&A, bool)> = world.query::<(&A, Has<B>)>().iter(&world).collect();
// The query seems to put the components with B first assert!(values.contains(&(&A(1), true)));
assert_eq!( assert!(values.contains(&(&A(2), false)));
values, assert!(values.contains(&(&A(3), true)));
vec![(&A(1), true), (&A(3), true), (&A(2), false), (&A(4), false),] assert!(values.contains(&(&A(4), false)));
);
} }
#[test] #[test]

View file

@ -341,6 +341,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// If `world` does not match the one used to call `QueryState::new` for this instance. /// 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) { pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) {
self.validate_world(world.id()); self.validate_world(world.id());
if self.component_access.required.is_empty() {
let archetypes = world.archetypes(); let archetypes = world.archetypes();
let old_generation = let old_generation =
std::mem::replace(&mut self.archetype_generation, archetypes.generation()); std::mem::replace(&mut self.archetype_generation, archetypes.generation());
@ -352,6 +353,44 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
self.new_archetype_internal(archetype); 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();
}
} }
/// # Panics /// # Panics
@ -1540,7 +1579,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
pub fn single<'w>(&mut self, world: &'w World) -> ROQueryItem<'w, D> { pub fn single<'w>(&mut self, world: &'w World) -> ROQueryItem<'w, D> {
match self.get_single(world) { match self.get_single(world) {
Ok(items) => items, Ok(items) => items,
Err(error) => panic!("Cannot get single mutable query result: {error}"), Err(error) => panic!("Cannot get single query result: {error}"),
} }
} }