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,
component::{ComponentId, Components, RequiredComponentConstructor, StorageType},
entity::{Entity, EntityLocation},
event::Event,
observer::Observers,
storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow},
};
use alloc::{boxed::Box, vec::Vec};
use bevy_platform::collections::HashMap;
use bevy_platform::collections::{hash_map::Entry, HashMap};
use core::{
hash::Hash,
ops::{Index, IndexMut, RangeFrom},
};
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`].
///
/// 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.
///
/// 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
///
/// # Safety
@ -881,7 +890,7 @@ impl Archetypes {
table_id: TableId,
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
) -> ArchetypeId {
) -> (ArchetypeId, bool) {
let archetype_identity = ArchetypeComponents {
sparse_set_components: sparse_set_components.into_boxed_slice(),
table_components: table_components.into_boxed_slice(),
@ -889,14 +898,13 @@ impl Archetypes {
let archetypes = &mut self.archetypes;
let component_index = &mut self.by_component;
*self
.by_components
.entry(archetype_identity)
.or_insert_with_key(move |identity| {
match self.by_components.entry(archetype_identity) {
Entry::Occupied(occupied) => (*occupied.get(), false),
Entry::Vacant(vacant) => {
let ArchetypeComponents {
table_components,
sparse_set_components,
} = identity;
} = vacant.key();
let id = ArchetypeId::new(archetypes.len());
archetypes.push(Archetype::new(
components,
@ -907,8 +915,10 @@ impl Archetypes {
table_components.iter().copied(),
sparse_set_components.iter().copied(),
));
id
})
vacant.insert(id);
(id, true)
}
}
}
/// Clears all entities from all archetypes.

View File

@ -6,8 +6,8 @@ pub use bevy_ecs_macros::Bundle;
use crate::{
archetype::{
Archetype, ArchetypeAfterBundleInsert, ArchetypeId, Archetypes, BundleComponentStatus,
ComponentStatus, SpawnBundleStatus,
Archetype, ArchetypeAfterBundleInsert, ArchetypeCreated, ArchetypeId, Archetypes,
BundleComponentStatus, ComponentStatus, SpawnBundleStatus,
},
change_detection::MaybeLocation,
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
/// does not result in an [`Archetype`] change.
///
@ -747,12 +747,12 @@ impl BundleInfo {
components: &Components,
observers: &Observers,
archetype_id: ArchetypeId,
) -> ArchetypeId {
) -> (ArchetypeId, bool) {
if let Some(archetype_after_insert_id) = archetypes[archetype_id]
.edges()
.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_sparse_set_components = Vec::new();
@ -806,7 +806,7 @@ impl BundleInfo {
added,
existing,
);
archetype_id
(archetype_id, false)
} else {
let table_id;
let table_components;
@ -842,13 +842,14 @@ impl BundleInfo {
};
};
// 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,
observers,
table_id,
table_components,
sparse_set_components,
);
// Add an edge from the old archetype to the new archetype.
archetypes[archetype_id]
.edges_mut()
@ -860,11 +861,11 @@ impl BundleInfo {
added,
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).
/// This could be the same [`ArchetypeId`], in the event that removing the given bundle
/// does not result in an [`Archetype`] change.
@ -887,7 +888,7 @@ impl BundleInfo {
observers: &Observers,
archetype_id: ArchetypeId,
intersection: bool,
) -> Option<ArchetypeId> {
) -> (Option<ArchetypeId>, bool) {
// Check the archetype graph to see if the bundle has been
// removed from this archetype in the past.
let archetype_after_remove_result = {
@ -898,9 +899,9 @@ impl BundleInfo {
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!
result
(result, false)
} else {
let mut next_table_components;
let mut next_sparse_set_components;
@ -925,7 +926,7 @@ impl BundleInfo {
current_archetype
.edges_mut()
.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,
observers,
next_table_id,
next_table_components,
next_sparse_set_components,
);
Some(new_archetype_id)
(Some(new_archetype_id), is_new_created)
};
let current_archetype = &mut archetypes[archetype_id];
// Cache the result in an edge.
@ -973,7 +974,7 @@ impl BundleInfo {
.edges_mut()
.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
let bundle_info = world.bundles.get_unchecked(bundle_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.storages,
&world.components,
&world.observers,
archetype_id,
);
if new_archetype_id == archetype_id {
let inserter = if new_archetype_id == archetype_id {
let archetype = &mut world.archetypes[archetype_id];
// SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype
let archetype_after_insert = unsafe {
@ -1103,7 +1105,15 @@ impl<'w> BundleInserter<'w> {
world: world.as_unsafe_world_cell(),
}
}
};
if is_new_created {
inserter
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
}
inserter
}
/// # Safety
@ -1421,7 +1431,7 @@ impl<'w> BundleRemover<'w> {
) -> Option<Self> {
let bundle_info = world.bundles.get_unchecked(bundle_id);
// 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(
&mut world.archetypes,
&mut world.storages,
@ -1429,11 +1439,14 @@ impl<'w> BundleRemover<'w> {
&world.observers,
archetype_id,
!require_all,
)?
)
};
let new_archetype_id = new_archetype_id?;
if new_archetype_id == archetype_id {
return None;
}
let (old_archetype, new_archetype) =
world.archetypes.get_2_mut(archetype_id, new_archetype_id);
@ -1447,13 +1460,20 @@ impl<'w> BundleRemover<'w> {
Some((old.into(), new.into()))
};
Some(Self {
let remover = Self {
bundle_info: bundle_info.into(),
new_archetype: new_archetype.into(),
old_archetype: old_archetype.into(),
old_and_new_table: tables,
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.
@ -1675,22 +1695,30 @@ impl<'w> BundleSpawner<'w> {
change_tick: Tick,
) -> Self {
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.storages,
&world.components,
&world.observers,
ArchetypeId::EMPTY,
);
let archetype = &mut world.archetypes[new_archetype_id];
let table = &mut world.storages.tables[archetype.table_id()];
Self {
let spawner = Self {
bundle_info: bundle_info.into(),
table: table.into(),
archetype: archetype.into(),
change_tick,
world: world.as_unsafe_world_cell(),
};
if is_new_created {
spawner
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
}
spawner
}
#[inline]
@ -2043,7 +2071,9 @@ fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) {
#[cfg(test)]
mod tests {
use crate::{component::HookContext, prelude::*, world::DeferredWorld};
use crate::{
archetype::ArchetypeCreated, component::HookContext, prelude::*, world::DeferredWorld,
};
use alloc::vec;
#[derive(Component)]
@ -2280,4 +2310,23 @@ mod tests {
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);
}
}