mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
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:
parent
68ec6f4f50
commit
ec4cf024f8
5 changed files with 233 additions and 178 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -341,16 +341,55 @@ 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());
|
||||
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<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}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue