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

@ -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);

View file

@ -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<ComponentId, HashMap<ArchetypeId, ArchetypeRecord>>;
/// 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>,
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 {
@ -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);
}
}
}

View file

@ -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::<Vec<_>>();
assert_eq!(ents, &[(e, A(123)), (f, A(456))]);
.collect::<HashSet<_>>();
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::<Vec<_>>();
assert_eq!(ents, &[(e, None, A(123)), (f, Some(B(1)), A(456))]);
.collect::<HashSet<_>>();
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::<Vec<_>>();
.collect::<HashSet<_>>();
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::<Vec<_>>(),
&[(e1, A(1), B(3)), (e2, A(2), B(4))]
.collect::<HashSet<_>>(),
HashSet::from([(e1, A(1), B(3)), (e2, A(2), B(4))])
);
assert_eq!(world.entity_mut(e1).take::<A>(), 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::<Vec<_>>(),
&[(e2, 4, "xyz"), (e1, 3, "abc")]
.collect::<HashSet<_>>(),
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::<Vec<_>>(),
&[(e2, A(2), B(4)), (e1, A(43), B(3))]
.collect::<HashSet<_>>(),
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<F: QueryFilter>(world: &mut World) -> Vec<Entity> {
fn get_filtered<F: QueryFilter>(world: &mut World) -> HashSet<Entity> {
world
.query_filtered::<Entity, F>()
.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
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
world.entity_mut(e1).insert((A(0), B(0)));
assert_eq!(
get_filtered::<Changed<A>>(&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::<Changed<A>>(&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::<Changed<A>>(&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::<Changed<A>>(&mut world), vec![e4]);
assert_eq!(get_filtered::<Added<A>>(&mut world), vec![e4]);
assert_eq!(get_filtered::<Changed<A>>(&mut world), HashSet::from([e4]));
assert_eq!(get_filtered::<Added<A>>(&mut world), HashSet::from([e4]));
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();
@ -981,9 +995,9 @@ mod tests {
world.entity_mut(e4).insert((A(0), B(0)));
assert!(get_filtered::<Added<A>>(&mut world).is_empty());
assert_eq!(get_filtered::<Changed<A>>(&mut world), vec![e4]);
assert_eq!(get_filtered::<Added<B>>(&mut world), vec![e4]);
assert_eq!(get_filtered::<Changed<B>>(&mut world), vec![e4]);
assert_eq!(get_filtered::<Changed<A>>(&mut world), HashSet::from([e4]));
assert_eq!(get_filtered::<Added<B>>(&mut world), HashSet::from([e4]));
assert_eq!(get_filtered::<Changed<B>>(&mut world), HashSet::from([e4]));
}
#[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
.query_filtered::<Entity, F>()
.iter(world)
.collect::<Vec<Entity>>()
.collect::<HashSet<Entity>>()
}
assert_eq!(
get_filtered::<Changed<SparseStored>>(&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::<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
world.entity_mut(e1).insert(SparseStored(0));
assert_eq!(
get_filtered::<Changed<SparseStored>>(&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::<Changed<SparseStored>>(&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::<Changed<SparseStored>>(&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::<Changed<SparseStored>>(&mut world), vec![e4]);
assert_eq!(get_filtered::<Added<SparseStored>>(&mut world), vec![e4]);
assert_eq!(
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));
assert_eq!(get_filtered::<Changed<SparseStored>>(&mut world), vec![e4]);
assert_eq!(
get_filtered::<Changed<SparseStored>>(&mut world),
HashSet::from([e4])
);
world.clear_trackers();
@ -1067,7 +1090,10 @@ mod tests {
world.entity_mut(e4).insert(SparseStored(0));
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]
@ -1249,8 +1275,8 @@ mod tests {
let results = query
.iter(&world)
.map(|(a, b)| (a.0, b.0))
.collect::<Vec<_>>();
assert_eq!(results, vec![(1, "1"), (2, "2"), (3, "3"),]);
.collect::<HashSet<_>>();
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::<Vec<_>>();
assert_eq!(results, vec![(1, "1"), (3, "3"),]);
.collect::<HashSet<_>>();
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::<Vec<_>>();
assert_eq!(results, vec![1, 3, 2]);
let results = a_query.iter(&world).map(|a| a.0).collect::<HashSet<_>>();
assert_eq!(results, HashSet::from([1, 3, 2]));
let entity_ref = world.entity(e2);
assert_eq!(

View file

@ -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::<Vec<&A>>();
assert_eq!(values, vec![&A(1), &A(2)]);
let values = world.query::<&A>().iter(&world).collect::<HashSet<&A>>();
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::<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]
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<B>>();
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<_>>()
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::<HashSet<_>>()
);
check_combinations(values, HashSet::from([[&A(2), &A(3), &A(4)]]));
let mut query = world.query_filtered::<&A, Or<(With<A>, With<B>)>>();
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::<HashSet<_>>()
]),
);
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();
assert_eq!(
values,
[[&A(12), &A(103), &A(1004)],]
.into_iter()
.collect::<HashSet<_>>()
);
check_combinations(values, HashSet::from([[&A(12), &A(103), &A(1004)]]));
// Check if Added<T>, Changed<T> 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<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]
@ -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::<Vec<&Sparse>>();
assert_eq!(values, vec![&Sparse(1), &Sparse(2)]);
.collect::<HashSet<&Sparse>>();
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<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_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]

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.
pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) {
self.validate_world(world.id());
if self.component_access.required.is_empty() {
let archetypes = world.archetypes();
let old_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);
}
}
} 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
@ -1540,7 +1579,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
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}"),
}
}