Add a public API to ArchetypeGeneration/Id (#9825)

Objective
---------

- Since #6742, It is not possible to build an `ArchetypeId` from a
`ArchetypeGeneration`
- This was useful to 3rd party crate extending the base bevy ECS
capabilities, such as [`bevy_ecs_dynamic`] and now
[`bevy_mod_dynamic_query`]
- Making `ArchetypeGeneration` opaque this way made it completely
useless, and removed the ability to limit archetype updates to a subset
of archetypes.
- Making the `index` method on `ArchetypeId` private prevented the use
of bitfields and other optimized data structure to store sets of
archetype ids. (without `transmute`)

This PR is not a simple reversal of the change. It exposes a different
API, rethought to keep the private stuff private and the public stuff
less error-prone.

- Add a `StartRange<ArchetypeGeneration>` `Index` implementation to
`Archetypes`
- Instead of converting the generation into an index, then creating a
ArchetypeId from that index, and indexing `Archetypes` with it, use
directly the old `ArchetypeGeneration` to get the range of new
archetypes.

From careful benchmarking, it seems to also be a performance improvement
(~0-5%) on add_archetypes.

---

Changelog
---------

- Added `impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes` this
allows you to get a slice of newly added archetypes since the last
recorded generation.
- Added `ArchetypeId::index` and `ArchetypeId::new` methods. It should
enable 3rd party crates to use the `Archetypes` API in a meaningful way.

[`bevy_ecs_dynamic`]:
https://github.com/jakobhellermann/bevy_ecs_dynamic/tree/main
[`bevy_mod_dynamic_query`]:
https://github.com/nicopap/bevy_mod_dynamic_query/

---------

Co-authored-by: vero <email@atlasdostal.com>
This commit is contained in:
Nicola Papale 2023-10-02 14:54:45 +02:00 committed by GitHub
parent 47409c8a72
commit 1bf271d56e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 46 deletions

View File

@ -27,7 +27,7 @@ use crate::{
}; };
use std::{ use std::{
hash::Hash, hash::Hash,
ops::{Index, IndexMut}, ops::{Index, IndexMut, RangeFrom},
}; };
/// An opaque location within a [`Archetype`]. /// An opaque location within a [`Archetype`].
@ -70,7 +70,7 @@ impl ArchetypeRow {
/// ///
/// [`World`]: crate::world::World /// [`World`]: crate::world::World
/// [`EMPTY`]: crate::archetype::ArchetypeId::EMPTY /// [`EMPTY`]: crate::archetype::ArchetypeId::EMPTY
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
// SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation // SAFETY: Must be repr(transparent) due to the safety requirements on EntityLocation
#[repr(transparent)] #[repr(transparent)]
pub struct ArchetypeId(u32); pub struct ArchetypeId(u32);
@ -83,13 +83,26 @@ impl ArchetypeId {
/// This must always have an all-1s bit pattern to ensure soundness in fast entity id space allocation. /// This must always have an all-1s bit pattern to ensure soundness in fast entity id space allocation.
pub const INVALID: ArchetypeId = ArchetypeId(u32::MAX); pub const INVALID: ArchetypeId = ArchetypeId(u32::MAX);
/// Create an `ArchetypeId` from a plain value.
///
/// This is useful if you need to store the `ArchetypeId` as a plain value,
/// for example in a specialized data structure such as a bitset.
///
/// While it doesn't break any safety invariants, you should ensure the
/// values comes from a pre-existing [`ArchetypeId::index`] in this world
/// to avoid panics and other unexpected behaviors.
#[inline] #[inline]
pub(crate) const fn new(index: usize) -> Self { pub const fn new(index: usize) -> Self {
ArchetypeId(index as u32) ArchetypeId(index as u32)
} }
/// The plain value of this `ArchetypeId`.
///
/// In bevy, this is mostly used to store archetype ids in [`FixedBitSet`]s.
///
/// [`FixedBitSet`]: fixedbitset::FixedBitSet
#[inline] #[inline]
pub(crate) fn index(self) -> usize { pub fn index(self) -> usize {
self.0 as usize self.0 as usize
} }
} }
@ -525,24 +538,23 @@ impl Archetype {
} }
} }
/// An opaque generational id that changes every time the set of [`Archetypes`] changes. /// The next [`ArchetypeId`] in an [`Archetypes`] collection.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] ///
pub struct ArchetypeGeneration(usize); /// 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)]
pub struct ArchetypeGeneration(ArchetypeId);
impl ArchetypeGeneration { impl ArchetypeGeneration {
/// The first archetype.
#[inline] #[inline]
pub(crate) const fn initial() -> Self { pub const fn initial() -> Self {
ArchetypeGeneration(0) ArchetypeGeneration(ArchetypeId::EMPTY)
}
#[inline]
pub(crate) fn value(self) -> usize {
self.0
} }
} }
#[derive(Hash, PartialEq, Eq)] #[derive(Hash, PartialEq, Eq)]
struct ArchetypeIdentity { struct ArchetypeComponents {
table_components: Box<[ComponentId]>, table_components: Box<[ComponentId]>,
sparse_set_components: Box<[ComponentId]>, sparse_set_components: Box<[ComponentId]>,
} }
@ -603,25 +615,29 @@ impl SparseSetIndex for ArchetypeComponentId {
pub struct Archetypes { pub struct Archetypes {
pub(crate) archetypes: Vec<Archetype>, pub(crate) archetypes: Vec<Archetype>,
pub(crate) archetype_component_count: usize, pub(crate) archetype_component_count: usize,
archetype_ids: bevy_utils::HashMap<ArchetypeIdentity, ArchetypeId>, by_components: bevy_utils::HashMap<ArchetypeComponents, ArchetypeId>,
} }
impl Archetypes { impl Archetypes {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
let mut archetypes = Archetypes { let mut archetypes = Archetypes {
archetypes: Vec::new(), archetypes: Vec::new(),
archetype_ids: Default::default(), by_components: Default::default(),
archetype_component_count: 0, archetype_component_count: 0,
}; };
archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new()); archetypes.get_id_or_insert(TableId::empty(), Vec::new(), Vec::new());
archetypes archetypes
} }
/// Returns the current archetype generation. This is an ID indicating the current set of archetypes /// Returns the "generation", a handle to the current highest archetype ID.
/// that are registered with the world. ///
/// This can be used with the `Index` [`Archetypes`] implementation to
/// iterate over newly introduced [`Archetype`]s since the last time this
/// function was called.
#[inline] #[inline]
pub fn generation(&self) -> ArchetypeGeneration { pub fn generation(&self) -> ArchetypeGeneration {
ArchetypeGeneration(self.archetypes.len()) let id = ArchetypeId::new(self.archetypes.len());
ArchetypeGeneration(id)
} }
/// Fetches the total number of [`Archetype`]s within the world. /// Fetches the total number of [`Archetype`]s within the world.
@ -692,7 +708,7 @@ impl Archetypes {
table_components: Vec<ComponentId>, table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>, sparse_set_components: Vec<ComponentId>,
) -> ArchetypeId { ) -> ArchetypeId {
let archetype_identity = ArchetypeIdentity { let archetype_identity = ArchetypeComponents {
sparse_set_components: sparse_set_components.clone().into_boxed_slice(), sparse_set_components: sparse_set_components.clone().into_boxed_slice(),
table_components: table_components.clone().into_boxed_slice(), table_components: table_components.clone().into_boxed_slice(),
}; };
@ -700,7 +716,7 @@ 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 *self
.archetype_ids .by_components
.entry(archetype_identity) .entry(archetype_identity)
.or_insert_with(move || { .or_insert_with(move || {
let id = ArchetypeId::new(archetypes.len()); let id = ArchetypeId::new(archetypes.len());
@ -739,6 +755,14 @@ impl Archetypes {
} }
} }
impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes {
type Output = [Archetype];
#[inline]
fn index(&self, index: RangeFrom<ArchetypeGeneration>) -> &Self::Output {
&self.archetypes[index.start.0.index()..]
}
}
impl Index<ArchetypeId> for Archetypes { impl Index<ArchetypeId> for Archetypes {
type Output = Archetype; type Output = Archetype;

View File

@ -216,12 +216,11 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
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());
let archetypes = world.archetypes(); let archetypes = world.archetypes();
let new_generation = archetypes.generation(); let old_generation =
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation); std::mem::replace(&mut self.archetype_generation, archetypes.generation());
let archetype_index_range = old_generation.value()..new_generation.value();
for archetype_index in archetype_index_range { for archetype in &archetypes[old_generation..] {
self.new_archetype(&archetypes[ArchetypeId::new(archetype_index)]); self.new_archetype(archetype);
} }
} }

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
archetype::{ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, archetype::{ArchetypeComponentId, ArchetypeGeneration},
component::{ComponentId, Tick}, component::{ComponentId, Tick},
prelude::FromWorld, prelude::FromWorld,
query::{Access, FilteredAccessSet}, query::{Access, FilteredAccessSet},
@ -270,16 +270,11 @@ impl<Param: SystemParam> SystemState<Param> {
#[inline] #[inline]
pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) { pub fn update_archetypes_unsafe_world_cell(&mut self, world: UnsafeWorldCell) {
let archetypes = world.archetypes(); let archetypes = world.archetypes();
let new_generation = archetypes.generation(); let old_generation =
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation); std::mem::replace(&mut self.archetype_generation, archetypes.generation());
let archetype_index_range = old_generation.value()..new_generation.value();
for archetype_index in archetype_index_range { for archetype in &archetypes[old_generation..] {
Param::new_archetype( Param::new_archetype(&mut self.param_state, archetype, &mut self.meta);
&mut self.param_state,
&archetypes[ArchetypeId::new(archetype_index)],
&mut self.meta,
);
} }
} }
@ -515,17 +510,12 @@ where
fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) { fn update_archetype_component_access(&mut self, world: UnsafeWorldCell) {
assert!(self.world_id == Some(world.id()), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with."); assert!(self.world_id == Some(world.id()), "Encountered a mismatched World. A System cannot be used with Worlds other than the one it was initialized with.");
let archetypes = world.archetypes(); let archetypes = world.archetypes();
let new_generation = archetypes.generation(); let old_generation =
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation); std::mem::replace(&mut self.archetype_generation, archetypes.generation());
let archetype_index_range = old_generation.value()..new_generation.value();
for archetype_index in archetype_index_range { for archetype in &archetypes[old_generation..] {
let param_state = self.param_state.as_mut().unwrap(); let param_state = self.param_state.as_mut().unwrap();
F::Param::new_archetype( F::Param::new_archetype(param_state, archetype, &mut self.system_meta);
param_state,
&archetypes[ArchetypeId::new(archetype_index)],
&mut self.system_meta,
);
} }
} }