Skip to content

Commit ec4cf02

Browse files
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 ```
1 parent 68ec6f4 commit ec4cf02

File tree

5 files changed

+234
-179
lines changed

5 files changed

+234
-179
lines changed

benches/benches/bevy_render/torus.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@ use bevy_render::mesh::TorusMeshBuilder;
44

55
fn torus(c: &mut Criterion) {
66
c.bench_function("build_torus", |b| {
7-
b.iter(|| black_box(TorusMeshBuilder::new(black_box(0.5),black_box(1.0))));
7+
b.iter(|| black_box(TorusMeshBuilder::new(black_box(0.5), black_box(1.0))));
88
});
99
}
1010

11-
criterion_group!(
12-
benches,
13-
torus,
14-
);
11+
criterion_group!(benches, torus,);
1512
criterion_main!(benches);

crates/bevy_ecs/src/archetype.rs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use crate::{
2626
observer::Observers,
2727
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
2828
};
29+
use bevy_utils::HashMap;
2930
use std::{
3031
hash::Hash,
3132
ops::{Index, IndexMut, RangeFrom},
@@ -345,8 +346,10 @@ pub struct Archetype {
345346
}
346347

347348
impl Archetype {
349+
/// `table_components` and `sparse_set_components` must be sorted
348350
pub(crate) fn new(
349351
components: &Components,
352+
component_index: &mut ComponentIndex,
350353
observers: &Observers,
351354
id: ArchetypeId,
352355
table_id: TableId,
@@ -357,7 +360,7 @@ impl Archetype {
357360
let (min_sparse, _) = sparse_set_components.size_hint();
358361
let mut flags = ArchetypeFlags::empty();
359362
let mut archetype_components = SparseSet::with_capacity(min_table + min_sparse);
360-
for (component_id, archetype_component_id) in table_components {
363+
for (idx, (component_id, archetype_component_id)) in table_components.enumerate() {
361364
// SAFETY: We are creating an archetype that includes this component so it must exist
362365
let info = unsafe { components.get_info_unchecked(component_id) };
363366
info.update_archetype_flags(&mut flags);
@@ -369,6 +372,13 @@ impl Archetype {
369372
archetype_component_id,
370373
},
371374
);
375+
// NOTE: the `table_components` are sorted AND they were inserted in the `Table` in the same
376+
// sorted order, so the index of the `Column` in the `Table` is the same as the index of the
377+
// component in the `table_components` vector
378+
component_index
379+
.entry(component_id)
380+
.or_insert_with(HashMap::new)
381+
.insert(id, ArchetypeRecord { column: Some(idx) });
372382
}
373383

374384
for (component_id, archetype_component_id) in sparse_set_components {
@@ -383,6 +393,10 @@ impl Archetype {
383393
archetype_component_id,
384394
},
385395
);
396+
component_index
397+
.entry(component_id)
398+
.or_insert_with(HashMap::new)
399+
.insert(id, ArchetypeRecord { column: None });
386400
}
387401
Self {
388402
id,
@@ -460,7 +474,7 @@ impl Archetype {
460474
self.components.len()
461475
}
462476

463-
/// Fetches a immutable reference to the archetype's [`Edges`], a cache of
477+
/// Fetches an immutable reference to the archetype's [`Edges`], a cache of
464478
/// archetypal relationships.
465479
#[inline]
466480
pub fn edges(&self) -> &Edges {
@@ -655,7 +669,7 @@ impl Archetype {
655669
/// This is used in archetype update methods to limit archetype updates to the
656670
/// ones added since the last time the method ran.
657671
#[derive(Debug, Copy, Clone, PartialEq)]
658-
pub struct ArchetypeGeneration(ArchetypeId);
672+
pub struct ArchetypeGeneration(pub(crate) ArchetypeId);
659673

660674
impl ArchetypeGeneration {
661675
/// The first archetype.
@@ -711,6 +725,10 @@ impl SparseSetIndex for ArchetypeComponentId {
711725
}
712726
}
713727

728+
/// Maps a [`ComponentId`] to the list of [`Archetypes`]([`Archetype`]) that contain the [`Component`](crate::component::Component),
729+
/// along with an [`ArchetypeRecord`] which contains some metadata about how the component is stored in the archetype.
730+
pub type ComponentIndex = HashMap<ComponentId, HashMap<ArchetypeId, ArchetypeRecord>>;
731+
714732
/// The backing store of all [`Archetype`]s within a [`World`].
715733
///
716734
/// For more information, see the *[module level documentation]*.
@@ -720,14 +738,26 @@ impl SparseSetIndex for ArchetypeComponentId {
720738
pub struct Archetypes {
721739
pub(crate) archetypes: Vec<Archetype>,
722740
archetype_component_count: usize,
723-
by_components: bevy_utils::HashMap<ArchetypeComponents, ArchetypeId>,
741+
/// find the archetype id by the archetype's components
742+
by_components: HashMap<ArchetypeComponents, ArchetypeId>,
743+
/// find all the archetypes that contain a component
744+
by_component: ComponentIndex,
745+
}
746+
747+
/// Metadata about how a component is stored in an [`Archetype`].
748+
pub struct ArchetypeRecord {
749+
/// Index of the component in the archetype's [`Table`](crate::storage::Table),
750+
/// or None if the component is a sparse set component.
751+
#[allow(dead_code)]
752+
pub(crate) column: Option<usize>,
724753
}
725754

726755
impl Archetypes {
727756
pub(crate) fn new() -> Self {
728757
let mut archetypes = Archetypes {
729758
archetypes: Vec::new(),
730759
by_components: Default::default(),
760+
by_component: Default::default(),
731761
archetype_component_count: 0,
732762
};
733763
// SAFETY: Empty archetype has no components
@@ -770,7 +800,7 @@ impl Archetypes {
770800
unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) }
771801
}
772802

773-
/// Fetches an mutable reference to the archetype without any components.
803+
/// Fetches a mutable reference to the archetype without any components.
774804
#[inline]
775805
pub(crate) fn empty_mut(&mut self) -> &mut Archetype {
776806
// SAFETY: empty archetype always exists
@@ -848,28 +878,27 @@ impl Archetypes {
848878

849879
let archetypes = &mut self.archetypes;
850880
let archetype_component_count = &mut self.archetype_component_count;
851-
*self
881+
let component_index = &mut self.by_component;
882+
let archetype_id = *self
852883
.by_components
853884
.entry(archetype_identity)
854885
.or_insert_with_key(move |identity| {
855886
let ArchetypeComponents {
856887
table_components,
857888
sparse_set_components,
858889
} = identity;
859-
860890
let id = ArchetypeId::new(archetypes.len());
861891
let table_start = *archetype_component_count;
862892
*archetype_component_count += table_components.len();
863893
let table_archetype_components =
864894
(table_start..*archetype_component_count).map(ArchetypeComponentId);
865-
866895
let sparse_start = *archetype_component_count;
867896
*archetype_component_count += sparse_set_components.len();
868897
let sparse_set_archetype_components =
869898
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
870-
871899
archetypes.push(Archetype::new(
872900
components,
901+
component_index,
873902
observers,
874903
id,
875904
table_id,
@@ -883,7 +912,8 @@ impl Archetypes {
883912
.zip(sparse_set_archetype_components),
884913
));
885914
id
886-
})
915+
});
916+
archetype_id
887917
}
888918

889919
/// Returns the number of components that are stored in archetypes.
@@ -900,16 +930,25 @@ impl Archetypes {
900930
}
901931
}
902932

933+
/// Get the component index
934+
pub(crate) fn component_index(&self) -> &ComponentIndex {
935+
&self.by_component
936+
}
937+
903938
pub(crate) fn update_flags(
904939
&mut self,
905940
component_id: ComponentId,
906941
flags: ArchetypeFlags,
907942
set: bool,
908943
) {
909-
// TODO: Refactor component index to speed this up.
910-
for archetype in &mut self.archetypes {
911-
if archetype.contains(component_id) {
912-
archetype.flags.set(flags, set);
944+
if let Some(archetypes) = self.by_component.get(&component_id) {
945+
for archetype_id in archetypes.keys() {
946+
// SAFETY: the component index only contains valid archetype ids
947+
self.archetypes
948+
.get_mut(archetype_id.index())
949+
.unwrap()
950+
.flags
951+
.set(flags, set);
913952
}
914953
}
915954
}

0 commit comments

Comments
 (0)