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:
parent
f93e5c5622
commit
50aa40e980
@ -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.
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user