Trigger ArchetypeCreated event when new archetype is created (#19455)

# Objective

- Part 1 of #19454 .
- Split from PR #18860(authored by @notmd) for better review and limit
implementation impact. so all credit for this work belongs to @notmd .

## Solution

- Trigger `ArchetypeCreated ` when new archetype is createed

---------

Co-authored-by: mgi388 <135186256+mgi388@users.noreply.github.com>
This commit is contained in:
re0312 2025-06-03 06:27:45 +08:00 committed by GitHub
parent f93e5c5622
commit 50aa40e980
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 93 additions and 34 deletions

View File

@ -23,17 +23,22 @@ use crate::{
bundle::BundleId, bundle::BundleId,
component::{ComponentId, Components, RequiredComponentConstructor, StorageType}, component::{ComponentId, Components, RequiredComponentConstructor, StorageType},
entity::{Entity, EntityLocation}, entity::{Entity, EntityLocation},
event::Event,
observer::Observers, observer::Observers,
storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow}, storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow},
}; };
use alloc::{boxed::Box, vec::Vec}; use alloc::{boxed::Box, vec::Vec};
use bevy_platform::collections::HashMap; use bevy_platform::collections::{hash_map::Entry, HashMap};
use core::{ use core::{
hash::Hash, hash::Hash,
ops::{Index, IndexMut, RangeFrom}, ops::{Index, IndexMut, RangeFrom},
}; };
use nonmax::NonMaxU32; use nonmax::NonMaxU32;
#[derive(Event)]
#[expect(dead_code, reason = "Prepare for the upcoming Query as Entities")]
pub(crate) struct ArchetypeCreated(pub ArchetypeId);
/// An opaque location within a [`Archetype`]. /// An opaque location within a [`Archetype`].
/// ///
/// This can be used in conjunction with [`ArchetypeId`] to find the exact location /// This can be used in conjunction with [`ArchetypeId`] to find the exact location
@ -869,6 +874,10 @@ impl Archetypes {
} }
/// Gets the archetype id matching the given inputs or inserts a new one if it doesn't exist. /// Gets the archetype id matching the given inputs or inserts a new one if it doesn't exist.
///
/// Specifically, it returns a tuple where the first element
/// is the [`ArchetypeId`] that the given inputs belong to, and the second element is a boolean indicating whether a new archetype was created.
///
/// `table_components` and `sparse_set_components` must be sorted /// `table_components` and `sparse_set_components` must be sorted
/// ///
/// # Safety /// # Safety
@ -881,7 +890,7 @@ impl Archetypes {
table_id: TableId, table_id: TableId,
table_components: Vec<ComponentId>, table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>, sparse_set_components: Vec<ComponentId>,
) -> ArchetypeId { ) -> (ArchetypeId, bool) {
let archetype_identity = ArchetypeComponents { let archetype_identity = ArchetypeComponents {
sparse_set_components: sparse_set_components.into_boxed_slice(), sparse_set_components: sparse_set_components.into_boxed_slice(),
table_components: table_components.into_boxed_slice(), table_components: table_components.into_boxed_slice(),
@ -889,14 +898,13 @@ impl Archetypes {
let archetypes = &mut self.archetypes; let archetypes = &mut self.archetypes;
let component_index = &mut self.by_component; let component_index = &mut self.by_component;
*self match self.by_components.entry(archetype_identity) {
.by_components Entry::Occupied(occupied) => (*occupied.get(), false),
.entry(archetype_identity) Entry::Vacant(vacant) => {
.or_insert_with_key(move |identity| {
let ArchetypeComponents { let ArchetypeComponents {
table_components, table_components,
sparse_set_components, sparse_set_components,
} = identity; } = vacant.key();
let id = ArchetypeId::new(archetypes.len()); let id = ArchetypeId::new(archetypes.len());
archetypes.push(Archetype::new( archetypes.push(Archetype::new(
components, components,
@ -907,8 +915,10 @@ impl Archetypes {
table_components.iter().copied(), table_components.iter().copied(),
sparse_set_components.iter().copied(), sparse_set_components.iter().copied(),
)); ));
id vacant.insert(id);
}) (id, true)
}
}
} }
/// Clears all entities from all archetypes. /// Clears all entities from all archetypes.

View File

@ -6,8 +6,8 @@ pub use bevy_ecs_macros::Bundle;
use crate::{ use crate::{
archetype::{ archetype::{
Archetype, ArchetypeAfterBundleInsert, ArchetypeId, Archetypes, BundleComponentStatus, Archetype, ArchetypeAfterBundleInsert, ArchetypeCreated, ArchetypeId, Archetypes,
ComponentStatus, SpawnBundleStatus, BundleComponentStatus, ComponentStatus, SpawnBundleStatus,
}, },
change_detection::MaybeLocation, change_detection::MaybeLocation,
component::{ component::{
@ -732,7 +732,7 @@ impl BundleInfo {
} }
} }
/// Inserts a bundle into the given archetype and returns the resulting archetype. /// Inserts a bundle into the given archetype and returns the resulting archetype and whether a new archetype was created.
/// This could be the same [`ArchetypeId`], in the event that inserting the given bundle /// This could be the same [`ArchetypeId`], in the event that inserting the given bundle
/// does not result in an [`Archetype`] change. /// does not result in an [`Archetype`] change.
/// ///
@ -747,12 +747,12 @@ impl BundleInfo {
components: &Components, components: &Components,
observers: &Observers, observers: &Observers,
archetype_id: ArchetypeId, archetype_id: ArchetypeId,
) -> ArchetypeId { ) -> (ArchetypeId, bool) {
if let Some(archetype_after_insert_id) = archetypes[archetype_id] if let Some(archetype_after_insert_id) = archetypes[archetype_id]
.edges() .edges()
.get_archetype_after_bundle_insert(self.id) .get_archetype_after_bundle_insert(self.id)
{ {
return archetype_after_insert_id; return (archetype_after_insert_id, false);
} }
let mut new_table_components = Vec::new(); let mut new_table_components = Vec::new();
let mut new_sparse_set_components = Vec::new(); let mut new_sparse_set_components = Vec::new();
@ -806,7 +806,7 @@ impl BundleInfo {
added, added,
existing, existing,
); );
archetype_id (archetype_id, false)
} else { } else {
let table_id; let table_id;
let table_components; let table_components;
@ -842,13 +842,14 @@ impl BundleInfo {
}; };
}; };
// SAFETY: ids in self must be valid // SAFETY: ids in self must be valid
let new_archetype_id = archetypes.get_id_or_insert( let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert(
components, components,
observers, observers,
table_id, table_id,
table_components, table_components,
sparse_set_components, sparse_set_components,
); );
// Add an edge from the old archetype to the new archetype. // Add an edge from the old archetype to the new archetype.
archetypes[archetype_id] archetypes[archetype_id]
.edges_mut() .edges_mut()
@ -860,11 +861,11 @@ impl BundleInfo {
added, added,
existing, existing,
); );
new_archetype_id (new_archetype_id, is_new_created)
} }
} }
/// Removes a bundle from the given archetype and returns the resulting archetype /// Removes a bundle from the given archetype and returns the resulting archetype and whether a new archetype was created.
/// (or `None` if the removal was invalid). /// (or `None` if the removal was invalid).
/// This could be the same [`ArchetypeId`], in the event that removing the given bundle /// This could be the same [`ArchetypeId`], in the event that removing the given bundle
/// does not result in an [`Archetype`] change. /// does not result in an [`Archetype`] change.
@ -887,7 +888,7 @@ impl BundleInfo {
observers: &Observers, observers: &Observers,
archetype_id: ArchetypeId, archetype_id: ArchetypeId,
intersection: bool, intersection: bool,
) -> Option<ArchetypeId> { ) -> (Option<ArchetypeId>, bool) {
// Check the archetype graph to see if the bundle has been // Check the archetype graph to see if the bundle has been
// removed from this archetype in the past. // removed from this archetype in the past.
let archetype_after_remove_result = { let archetype_after_remove_result = {
@ -898,9 +899,9 @@ impl BundleInfo {
edges.get_archetype_after_bundle_take(self.id()) edges.get_archetype_after_bundle_take(self.id())
} }
}; };
let result = if let Some(result) = archetype_after_remove_result { let (result, is_new_created) = if let Some(result) = archetype_after_remove_result {
// This bundle removal result is cached. Just return that! // This bundle removal result is cached. Just return that!
result (result, false)
} else { } else {
let mut next_table_components; let mut next_table_components;
let mut next_sparse_set_components; let mut next_sparse_set_components;
@ -925,7 +926,7 @@ impl BundleInfo {
current_archetype current_archetype
.edges_mut() .edges_mut()
.cache_archetype_after_bundle_take(self.id(), None); .cache_archetype_after_bundle_take(self.id(), None);
return None; return (None, false);
} }
} }
@ -953,14 +954,14 @@ impl BundleInfo {
}; };
} }
let new_archetype_id = archetypes.get_id_or_insert( let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert(
components, components,
observers, observers,
next_table_id, next_table_id,
next_table_components, next_table_components,
next_sparse_set_components, next_sparse_set_components,
); );
Some(new_archetype_id) (Some(new_archetype_id), is_new_created)
}; };
let current_archetype = &mut archetypes[archetype_id]; let current_archetype = &mut archetypes[archetype_id];
// Cache the result in an edge. // Cache the result in an edge.
@ -973,7 +974,7 @@ impl BundleInfo {
.edges_mut() .edges_mut()
.cache_archetype_after_bundle_take(self.id(), result); .cache_archetype_after_bundle_take(self.id(), result);
} }
result (result, is_new_created)
} }
} }
@ -1036,14 +1037,15 @@ impl<'w> BundleInserter<'w> {
// SAFETY: We will not make any accesses to the command queue, component or resource data of this world // SAFETY: We will not make any accesses to the command queue, component or resource data of this world
let bundle_info = world.bundles.get_unchecked(bundle_id); let bundle_info = world.bundles.get_unchecked(bundle_id);
let bundle_id = bundle_info.id(); let bundle_id = bundle_info.id();
let new_archetype_id = bundle_info.insert_bundle_into_archetype( let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype(
&mut world.archetypes, &mut world.archetypes,
&mut world.storages, &mut world.storages,
&world.components, &world.components,
&world.observers, &world.observers,
archetype_id, archetype_id,
); );
if new_archetype_id == archetype_id {
let inserter = if new_archetype_id == archetype_id {
let archetype = &mut world.archetypes[archetype_id]; let archetype = &mut world.archetypes[archetype_id];
// SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype // SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype
let archetype_after_insert = unsafe { let archetype_after_insert = unsafe {
@ -1103,7 +1105,15 @@ impl<'w> BundleInserter<'w> {
world: world.as_unsafe_world_cell(), world: world.as_unsafe_world_cell(),
} }
} }
};
if is_new_created {
inserter
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
} }
inserter
} }
/// # Safety /// # Safety
@ -1421,7 +1431,7 @@ impl<'w> BundleRemover<'w> {
) -> Option<Self> { ) -> Option<Self> {
let bundle_info = world.bundles.get_unchecked(bundle_id); let bundle_info = world.bundles.get_unchecked(bundle_id);
// SAFETY: Caller ensures archetype and bundle ids are correct. // SAFETY: Caller ensures archetype and bundle ids are correct.
let new_archetype_id = unsafe { let (new_archetype_id, is_new_created) = unsafe {
bundle_info.remove_bundle_from_archetype( bundle_info.remove_bundle_from_archetype(
&mut world.archetypes, &mut world.archetypes,
&mut world.storages, &mut world.storages,
@ -1429,11 +1439,14 @@ impl<'w> BundleRemover<'w> {
&world.observers, &world.observers,
archetype_id, archetype_id,
!require_all, !require_all,
)? )
}; };
let new_archetype_id = new_archetype_id?;
if new_archetype_id == archetype_id { if new_archetype_id == archetype_id {
return None; return None;
} }
let (old_archetype, new_archetype) = let (old_archetype, new_archetype) =
world.archetypes.get_2_mut(archetype_id, new_archetype_id); world.archetypes.get_2_mut(archetype_id, new_archetype_id);
@ -1447,13 +1460,20 @@ impl<'w> BundleRemover<'w> {
Some((old.into(), new.into())) Some((old.into(), new.into()))
}; };
Some(Self { let remover = Self {
bundle_info: bundle_info.into(), bundle_info: bundle_info.into(),
new_archetype: new_archetype.into(), new_archetype: new_archetype.into(),
old_archetype: old_archetype.into(), old_archetype: old_archetype.into(),
old_and_new_table: tables, old_and_new_table: tables,
world: world.as_unsafe_world_cell(), world: world.as_unsafe_world_cell(),
}) };
if is_new_created {
remover
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
}
Some(remover)
} }
/// This can be passed to [`remove`](Self::remove) as the `pre_remove` function if you don't want to do anything before removing. /// This can be passed to [`remove`](Self::remove) as the `pre_remove` function if you don't want to do anything before removing.
@ -1675,22 +1695,30 @@ impl<'w> BundleSpawner<'w> {
change_tick: Tick, change_tick: Tick,
) -> Self { ) -> Self {
let bundle_info = world.bundles.get_unchecked(bundle_id); let bundle_info = world.bundles.get_unchecked(bundle_id);
let new_archetype_id = bundle_info.insert_bundle_into_archetype( let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype(
&mut world.archetypes, &mut world.archetypes,
&mut world.storages, &mut world.storages,
&world.components, &world.components,
&world.observers, &world.observers,
ArchetypeId::EMPTY, ArchetypeId::EMPTY,
); );
let archetype = &mut world.archetypes[new_archetype_id]; let archetype = &mut world.archetypes[new_archetype_id];
let table = &mut world.storages.tables[archetype.table_id()]; let table = &mut world.storages.tables[archetype.table_id()];
Self { let spawner = Self {
bundle_info: bundle_info.into(), bundle_info: bundle_info.into(),
table: table.into(), table: table.into(),
archetype: archetype.into(), archetype: archetype.into(),
change_tick, change_tick,
world: world.as_unsafe_world_cell(), world: world.as_unsafe_world_cell(),
};
if is_new_created {
spawner
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
} }
spawner
} }
#[inline] #[inline]
@ -2043,7 +2071,9 @@ fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{component::HookContext, prelude::*, world::DeferredWorld}; use crate::{
archetype::ArchetypeCreated, component::HookContext, prelude::*, world::DeferredWorld,
};
use alloc::vec; use alloc::vec;
#[derive(Component)] #[derive(Component)]
@ -2280,4 +2310,23 @@ mod tests {
assert_eq!(a, vec![1]); assert_eq!(a, vec![1]);
} }
#[test]
fn new_archetype_created() {
let mut world = World::new();
#[derive(Resource, Default)]
struct Count(u32);
world.init_resource::<Count>();
world.add_observer(|_t: Trigger<ArchetypeCreated>, mut count: ResMut<Count>| {
count.0 += 1;
});
let mut e = world.spawn((A, B));
e.insert(C);
e.remove::<A>();
e.insert(A);
e.insert(A);
assert_eq!(world.resource::<Count>().0, 3);
}
} }