@@ -8,7 +8,6 @@ use crate::hash::SpatialHashFilter;
88use crate :: hash:: SpatialHashSystems ;
99use crate :: partition:: map:: PartitionLookup ;
1010use crate :: partition:: PartitionId ;
11- use alloc:: vec:: Vec ;
1211use bevy_app:: prelude:: * ;
1312use bevy_ecs:: entity:: EntityHashMap ;
1413use bevy_ecs:: prelude:: * ;
@@ -20,7 +19,7 @@ use core::marker::PhantomData;
2019pub struct PartitionChangePlugin < F : SpatialHashFilter = ( ) > ( PhantomData < F > ) ;
2120
2221impl < F : SpatialHashFilter > PartitionChangePlugin < F > {
23- /// Create a new instance of [`crate::prelude:: PartitionChangePlugin`].
22+ /// Create a new instance of [`PartitionChangePlugin`].
2423 pub fn new ( ) -> Self {
2524 Self ( PhantomData )
2625 }
@@ -68,88 +67,87 @@ impl<F: SpatialHashFilter> FromWorld for PartitionEntities<F> {
6867
6968impl < F : SpatialHashFilter > PartitionEntities < F > {
7069 fn update (
71- mut entity_partitions : ResMut < Self > ,
70+ mut this : ResMut < Self > ,
7271 cells : Res < CellLookup < F > > ,
7372 changed_cells : Res < ChangedCells < F > > ,
7473 all_hashes : Query < ( Entity , & CellId ) , F > ,
7574 mut old_reverse : Local < CellHashMap < PartitionId > > ,
7675 partitions : Res < PartitionLookup < F > > ,
7776 ) {
78- entity_partitions. changed . clear ( ) ;
77+ // 1. Clear the list of entities that have changed partitions
78+ this. changed . clear ( ) ;
7979
80- // Compute cell-level partition changes: cells that remained occupied but changed partitions.
81- for ( cell_hash, entry) in cells. all_entries ( ) {
82- if let Some ( & new_pid) = partitions. get ( cell_hash) {
83- // Only mark entities whose previous recorded partition differs from the new one
84- for entity in entry. entities . iter ( ) . copied ( ) {
85- if let Some ( prev_pid) = entity_partitions. map . get ( & entity) . copied ( ) {
86- if prev_pid != new_pid {
87- if let Some ( ( _from, to) ) = entity_partitions. changed . get_mut ( & entity) {
88- * to = Some ( new_pid) ;
89- } else {
90- entity_partitions
91- . changed
92- . insert ( entity, ( Some ( prev_pid) , Some ( new_pid) ) ) ;
93- }
94- }
95- }
96- }
97- }
98- }
99-
100- // Compute entity-level partition changes for entities that moved cells this frame,
101- // were newly spawned (added cell), or had cell removed (despawned/removed component).
80+ // 2. Iterate through all entities that have moved between cells, using the already
81+ // optimized `ChangedCells` resource used for grid cells. Check these moved entities to see
82+ // if they have also changed partitions. This should also include spawned/despawned.
10283 for entity in changed_cells. iter ( ) {
10384 match all_hashes. get ( * entity) {
104- // Entity currently has a CellId
10585 Ok ( ( entity_id, cell_hash) ) => {
10686 let new_pid = partitions. get ( cell_hash) . copied ( ) ;
107- let old_pid = entity_partitions . map . get ( & entity_id) . copied ( ) ;
108- let record = match ( old_pid, new_pid) {
87+ let old_pid = this . map . get ( & entity_id) . copied ( ) ;
88+ let partition_change = match ( old_pid, new_pid) {
10989 ( Some ( o) , Some ( n) ) if o == n => None , // Partition unchanged
11090 ( None , None ) => None , // Nonsensical
111- other => Some ( other) ,
91+ other => Some ( other) , // Valid change
11292 } ;
113- if let Some ( ( from, to) ) = record {
114- if let Some ( ( existing_from, existing_to) ) =
115- entity_partitions. changed . get_mut ( entity)
116- {
93+ if let Some ( ( from, to) ) = partition_change {
94+ if let Some ( ( existing_from, existing_to) ) = this. changed . get_mut ( entity) {
11795 // Preserve the earliest known source if we already have one
11896 if existing_from. is_none ( ) {
11997 * existing_from = from;
12098 }
12199 * existing_to = to;
122100 } else {
123- entity_partitions . changed . insert ( * entity, ( from, to) ) ;
101+ this . changed . insert ( * entity, ( from, to) ) ;
124102 }
125103 }
126104 }
127- // Entity no longer has a CellId -> removed/despawned
105+ // If the query fails, the entity no longer has a `CellId` because it was removed
106+ // or the entity was despawned.
128107 Err ( _) => {
129- if let Some ( prev_pid) = entity_partitions. map . get ( entity) . copied ( ) {
130- entity_partitions
131- . changed
132- . insert ( * entity, ( Some ( prev_pid) , None ) ) ;
108+ if let Some ( prev_pid) = this. map . get ( entity) . copied ( ) {
109+ this. changed . insert ( * entity, ( Some ( prev_pid) , None ) ) ;
133110 }
134111 }
135112 }
136113 }
137114
138- // Apply the delta only after all changes have been collected.
139- let mut apply_list: Vec < ( Entity , Option < PartitionId > ) > =
140- Vec :: with_capacity ( entity_partitions. changed . len ( ) ) ;
141- for ( entity, ( _from, to) ) in entity_partitions. changed . iter ( ) {
142- apply_list. push ( ( * entity, * to) ) ;
115+ // 3. Consider entities that have not moved, but their partition has changed out from
116+ // underneath them. This can happen when partitions merge and split - the entity did not
117+ // move but is now in a new partition.
118+ //
119+ // Check these changes at the cell level so we scale with the number of cells, not the
120+ // number of entities, and additionally avoid many lookups.
121+ //
122+ // It's important this is run after the moved-entity checks in step 2, because the entities
123+ // found from cell changes may *also* have moved, and we would miss that information if we
124+ // only used cell-level tracking.
125+ for ( cell_id, entry) in cells. all_entries ( ) {
126+ if let Some ( & new_pid) = partitions. get ( cell_id) {
127+ if let Some ( & old_pid) = old_reverse. get ( cell_id) {
128+ if new_pid != old_pid {
129+ for entity in entry. entities . iter ( ) . copied ( ) {
130+ this. changed
131+ . entry ( entity)
132+ // Don't overwrite entities that moved cells, they have already been tracked.
133+ . or_insert ( ( Some ( old_pid) , Some ( new_pid) ) ) ;
134+ }
135+ }
136+ }
137+ }
143138 }
144- for ( entity, to) in apply_list {
145- match to {
139+
140+ // 4. Apply the changes to the entity partition map after all changes have been collected.
141+ let PartitionEntities { map, changed, .. } = this. as_mut ( ) ;
142+ for ( entity, ( _source, destination) ) in changed. iter ( ) {
143+ match destination {
146144 Some ( pid) => {
147- entity_partitions . map . insert ( entity, pid) ;
145+ map. insert ( * entity, * pid) ;
148146 }
149147 None => {
150- entity_partitions . map . remove ( & entity) ;
148+ map. remove ( entity) ;
151149 }
152- }
150+ } ;
153151 }
154152
155153 // Snapshot the cell->partition mapping to compute deltas next update
0 commit comments