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,
|
||||
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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user