Skip to content

Commit 50aa40e

Browse files
re0312mgi388
andauthored
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>
1 parent f93e5c5 commit 50aa40e

File tree

2 files changed

+93
-34
lines changed

2 files changed

+93
-34
lines changed

crates/bevy_ecs/src/archetype.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,22 @@ use crate::{
2323
bundle::BundleId,
2424
component::{ComponentId, Components, RequiredComponentConstructor, StorageType},
2525
entity::{Entity, EntityLocation},
26+
event::Event,
2627
observer::Observers,
2728
storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow},
2829
};
2930
use alloc::{boxed::Box, vec::Vec};
30-
use bevy_platform::collections::HashMap;
31+
use bevy_platform::collections::{hash_map::Entry, HashMap};
3132
use core::{
3233
hash::Hash,
3334
ops::{Index, IndexMut, RangeFrom},
3435
};
3536
use nonmax::NonMaxU32;
3637

38+
#[derive(Event)]
39+
#[expect(dead_code, reason = "Prepare for the upcoming Query as Entities")]
40+
pub(crate) struct ArchetypeCreated(pub ArchetypeId);
41+
3742
/// An opaque location within a [`Archetype`].
3843
///
3944
/// This can be used in conjunction with [`ArchetypeId`] to find the exact location
@@ -869,6 +874,10 @@ impl Archetypes {
869874
}
870875

871876
/// Gets the archetype id matching the given inputs or inserts a new one if it doesn't exist.
877+
///
878+
/// Specifically, it returns a tuple where the first element
879+
/// is the [`ArchetypeId`] that the given inputs belong to, and the second element is a boolean indicating whether a new archetype was created.
880+
///
872881
/// `table_components` and `sparse_set_components` must be sorted
873882
///
874883
/// # Safety
@@ -881,22 +890,21 @@ impl Archetypes {
881890
table_id: TableId,
882891
table_components: Vec<ComponentId>,
883892
sparse_set_components: Vec<ComponentId>,
884-
) -> ArchetypeId {
893+
) -> (ArchetypeId, bool) {
885894
let archetype_identity = ArchetypeComponents {
886895
sparse_set_components: sparse_set_components.into_boxed_slice(),
887896
table_components: table_components.into_boxed_slice(),
888897
};
889898

890899
let archetypes = &mut self.archetypes;
891900
let component_index = &mut self.by_component;
892-
*self
893-
.by_components
894-
.entry(archetype_identity)
895-
.or_insert_with_key(move |identity| {
901+
match self.by_components.entry(archetype_identity) {
902+
Entry::Occupied(occupied) => (*occupied.get(), false),
903+
Entry::Vacant(vacant) => {
896904
let ArchetypeComponents {
897905
table_components,
898906
sparse_set_components,
899-
} = identity;
907+
} = vacant.key();
900908
let id = ArchetypeId::new(archetypes.len());
901909
archetypes.push(Archetype::new(
902910
components,
@@ -907,8 +915,10 @@ impl Archetypes {
907915
table_components.iter().copied(),
908916
sparse_set_components.iter().copied(),
909917
));
910-
id
911-
})
918+
vacant.insert(id);
919+
(id, true)
920+
}
921+
}
912922
}
913923

914924
/// Clears all entities from all archetypes.

crates/bevy_ecs/src/bundle.rs

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ pub use bevy_ecs_macros::Bundle;
66

77
use crate::{
88
archetype::{
9-
Archetype, ArchetypeAfterBundleInsert, ArchetypeId, Archetypes, BundleComponentStatus,
10-
ComponentStatus, SpawnBundleStatus,
9+
Archetype, ArchetypeAfterBundleInsert, ArchetypeCreated, ArchetypeId, Archetypes,
10+
BundleComponentStatus, ComponentStatus, SpawnBundleStatus,
1111
},
1212
change_detection::MaybeLocation,
1313
component::{
@@ -732,7 +732,7 @@ impl BundleInfo {
732732
}
733733
}
734734

735-
/// Inserts a bundle into the given archetype and returns the resulting archetype.
735+
/// Inserts a bundle into the given archetype and returns the resulting archetype and whether a new archetype was created.
736736
/// This could be the same [`ArchetypeId`], in the event that inserting the given bundle
737737
/// does not result in an [`Archetype`] change.
738738
///
@@ -747,12 +747,12 @@ impl BundleInfo {
747747
components: &Components,
748748
observers: &Observers,
749749
archetype_id: ArchetypeId,
750-
) -> ArchetypeId {
750+
) -> (ArchetypeId, bool) {
751751
if let Some(archetype_after_insert_id) = archetypes[archetype_id]
752752
.edges()
753753
.get_archetype_after_bundle_insert(self.id)
754754
{
755-
return archetype_after_insert_id;
755+
return (archetype_after_insert_id, false);
756756
}
757757
let mut new_table_components = Vec::new();
758758
let mut new_sparse_set_components = Vec::new();
@@ -806,7 +806,7 @@ impl BundleInfo {
806806
added,
807807
existing,
808808
);
809-
archetype_id
809+
(archetype_id, false)
810810
} else {
811811
let table_id;
812812
let table_components;
@@ -842,13 +842,14 @@ impl BundleInfo {
842842
};
843843
};
844844
// SAFETY: ids in self must be valid
845-
let new_archetype_id = archetypes.get_id_or_insert(
845+
let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert(
846846
components,
847847
observers,
848848
table_id,
849849
table_components,
850850
sparse_set_components,
851851
);
852+
852853
// Add an edge from the old archetype to the new archetype.
853854
archetypes[archetype_id]
854855
.edges_mut()
@@ -860,11 +861,11 @@ impl BundleInfo {
860861
added,
861862
existing,
862863
);
863-
new_archetype_id
864+
(new_archetype_id, is_new_created)
864865
}
865866
}
866867

867-
/// Removes a bundle from the given archetype and returns the resulting archetype
868+
/// Removes a bundle from the given archetype and returns the resulting archetype and whether a new archetype was created.
868869
/// (or `None` if the removal was invalid).
869870
/// This could be the same [`ArchetypeId`], in the event that removing the given bundle
870871
/// does not result in an [`Archetype`] change.
@@ -887,7 +888,7 @@ impl BundleInfo {
887888
observers: &Observers,
888889
archetype_id: ArchetypeId,
889890
intersection: bool,
890-
) -> Option<ArchetypeId> {
891+
) -> (Option<ArchetypeId>, bool) {
891892
// Check the archetype graph to see if the bundle has been
892893
// removed from this archetype in the past.
893894
let archetype_after_remove_result = {
@@ -898,9 +899,9 @@ impl BundleInfo {
898899
edges.get_archetype_after_bundle_take(self.id())
899900
}
900901
};
901-
let result = if let Some(result) = archetype_after_remove_result {
902+
let (result, is_new_created) = if let Some(result) = archetype_after_remove_result {
902903
// This bundle removal result is cached. Just return that!
903-
result
904+
(result, false)
904905
} else {
905906
let mut next_table_components;
906907
let mut next_sparse_set_components;
@@ -925,7 +926,7 @@ impl BundleInfo {
925926
current_archetype
926927
.edges_mut()
927928
.cache_archetype_after_bundle_take(self.id(), None);
928-
return None;
929+
return (None, false);
929930
}
930931
}
931932

@@ -953,14 +954,14 @@ impl BundleInfo {
953954
};
954955
}
955956

956-
let new_archetype_id = archetypes.get_id_or_insert(
957+
let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert(
957958
components,
958959
observers,
959960
next_table_id,
960961
next_table_components,
961962
next_sparse_set_components,
962963
);
963-
Some(new_archetype_id)
964+
(Some(new_archetype_id), is_new_created)
964965
};
965966
let current_archetype = &mut archetypes[archetype_id];
966967
// Cache the result in an edge.
@@ -973,7 +974,7 @@ impl BundleInfo {
973974
.edges_mut()
974975
.cache_archetype_after_bundle_take(self.id(), result);
975976
}
976-
result
977+
(result, is_new_created)
977978
}
978979
}
979980

@@ -1036,14 +1037,15 @@ impl<'w> BundleInserter<'w> {
10361037
// SAFETY: We will not make any accesses to the command queue, component or resource data of this world
10371038
let bundle_info = world.bundles.get_unchecked(bundle_id);
10381039
let bundle_id = bundle_info.id();
1039-
let new_archetype_id = bundle_info.insert_bundle_into_archetype(
1040+
let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype(
10401041
&mut world.archetypes,
10411042
&mut world.storages,
10421043
&world.components,
10431044
&world.observers,
10441045
archetype_id,
10451046
);
1046-
if new_archetype_id == archetype_id {
1047+
1048+
let inserter = if new_archetype_id == archetype_id {
10471049
let archetype = &mut world.archetypes[archetype_id];
10481050
// SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype
10491051
let archetype_after_insert = unsafe {
@@ -1103,7 +1105,15 @@ impl<'w> BundleInserter<'w> {
11031105
world: world.as_unsafe_world_cell(),
11041106
}
11051107
}
1108+
};
1109+
1110+
if is_new_created {
1111+
inserter
1112+
.world
1113+
.into_deferred()
1114+
.trigger(ArchetypeCreated(new_archetype_id));
11061115
}
1116+
inserter
11071117
}
11081118

11091119
/// # Safety
@@ -1421,19 +1431,22 @@ impl<'w> BundleRemover<'w> {
14211431
) -> Option<Self> {
14221432
let bundle_info = world.bundles.get_unchecked(bundle_id);
14231433
// SAFETY: Caller ensures archetype and bundle ids are correct.
1424-
let new_archetype_id = unsafe {
1434+
let (new_archetype_id, is_new_created) = unsafe {
14251435
bundle_info.remove_bundle_from_archetype(
14261436
&mut world.archetypes,
14271437
&mut world.storages,
14281438
&world.components,
14291439
&world.observers,
14301440
archetype_id,
14311441
!require_all,
1432-
)?
1442+
)
14331443
};
1444+
let new_archetype_id = new_archetype_id?;
1445+
14341446
if new_archetype_id == archetype_id {
14351447
return None;
14361448
}
1449+
14371450
let (old_archetype, new_archetype) =
14381451
world.archetypes.get_2_mut(archetype_id, new_archetype_id);
14391452

@@ -1447,13 +1460,20 @@ impl<'w> BundleRemover<'w> {
14471460
Some((old.into(), new.into()))
14481461
};
14491462

1450-
Some(Self {
1463+
let remover = Self {
14511464
bundle_info: bundle_info.into(),
14521465
new_archetype: new_archetype.into(),
14531466
old_archetype: old_archetype.into(),
14541467
old_and_new_table: tables,
14551468
world: world.as_unsafe_world_cell(),
1456-
})
1469+
};
1470+
if is_new_created {
1471+
remover
1472+
.world
1473+
.into_deferred()
1474+
.trigger(ArchetypeCreated(new_archetype_id));
1475+
}
1476+
Some(remover)
14571477
}
14581478

14591479
/// 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> {
16751695
change_tick: Tick,
16761696
) -> Self {
16771697
let bundle_info = world.bundles.get_unchecked(bundle_id);
1678-
let new_archetype_id = bundle_info.insert_bundle_into_archetype(
1698+
let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype(
16791699
&mut world.archetypes,
16801700
&mut world.storages,
16811701
&world.components,
16821702
&world.observers,
16831703
ArchetypeId::EMPTY,
16841704
);
1705+
16851706
let archetype = &mut world.archetypes[new_archetype_id];
16861707
let table = &mut world.storages.tables[archetype.table_id()];
1687-
Self {
1708+
let spawner = Self {
16881709
bundle_info: bundle_info.into(),
16891710
table: table.into(),
16901711
archetype: archetype.into(),
16911712
change_tick,
16921713
world: world.as_unsafe_world_cell(),
1714+
};
1715+
if is_new_created {
1716+
spawner
1717+
.world
1718+
.into_deferred()
1719+
.trigger(ArchetypeCreated(new_archetype_id));
16931720
}
1721+
spawner
16941722
}
16951723

16961724
#[inline]
@@ -2043,7 +2071,9 @@ fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) {
20432071

20442072
#[cfg(test)]
20452073
mod tests {
2046-
use crate::{component::HookContext, prelude::*, world::DeferredWorld};
2074+
use crate::{
2075+
archetype::ArchetypeCreated, component::HookContext, prelude::*, world::DeferredWorld,
2076+
};
20472077
use alloc::vec;
20482078

20492079
#[derive(Component)]
@@ -2280,4 +2310,23 @@ mod tests {
22802310

22812311
assert_eq!(a, vec![1]);
22822312
}
2313+
2314+
#[test]
2315+
fn new_archetype_created() {
2316+
let mut world = World::new();
2317+
#[derive(Resource, Default)]
2318+
struct Count(u32);
2319+
world.init_resource::<Count>();
2320+
world.add_observer(|_t: Trigger<ArchetypeCreated>, mut count: ResMut<Count>| {
2321+
count.0 += 1;
2322+
});
2323+
2324+
let mut e = world.spawn((A, B));
2325+
e.insert(C);
2326+
e.remove::<A>();
2327+
e.insert(A);
2328+
e.insert(A);
2329+
2330+
assert_eq!(world.resource::<Count>().0, 3);
2331+
}
22832332
}

0 commit comments

Comments
 (0)