Skip to content

Commit d2965eb

Browse files
committed
fix(bevy_ecs): reborrow archetypes safely in bundle remove path
1 parent aace194 commit d2965eb

File tree

1 file changed

+160
-134
lines changed

1 file changed

+160
-134
lines changed

crates/bevy_ecs/src/bundle/remove.rs

Lines changed: 160 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ pub(crate) struct BundleRemover<'w> {
2626
pub(crate) relationship_hook_mode: RelationshipHookMode,
2727
}
2828

29+
#[inline]
30+
fn explicit_components_in_archetype<'a>(
31+
explicit_components: &'a [ComponentId],
32+
archetype: &'a Archetype,
33+
) -> impl Iterator<Item = ComponentId> + 'a {
34+
explicit_components
35+
.iter()
36+
.copied()
37+
.filter(move |component_id| archetype.contains(*component_id))
38+
}
39+
2940
impl<'w> BundleRemover<'w> {
3041
/// Creates a new [`BundleRemover`], if such a remover would do anything.
3142
///
@@ -136,195 +147,210 @@ impl<'w> BundleRemover<'w> {
136147
&[ComponentId],
137148
) -> (bool, T),
138149
) -> (EntityLocation, T) {
139-
// Hooks
150+
let bundle_info = self.bundle_info.as_ref();
151+
let explicit_components = bundle_info.explicit_components();
152+
let old_archetype_id = location.archetype_id;
153+
// Save flags before mutation invalidates archetype references.
154+
let has_after_remove_hook = self.old_archetype.as_ref().has_after_remove_hook();
155+
let has_after_remove_observer = self.old_archetype.as_ref().has_after_remove_observer();
156+
140157
// SAFETY: all bundle components exist in World
141158
unsafe {
142-
// SAFETY: We only keep access to archetype/bundle data.
159+
let old_archetype = self.old_archetype.as_ref();
160+
let new_archetype = self.new_archetype.as_ref();
161+
let has_discard_observer = old_archetype.has_discard_observer();
162+
let has_remove_observer = old_archetype.has_remove_observer();
163+
let observer_components = (has_discard_observer || has_remove_observer).then(|| {
164+
explicit_components_in_archetype(explicit_components, old_archetype)
165+
.collect::<Vec<_>>()
166+
});
167+
168+
// SAFETY: We only keep access to archetype metadata.
143169
let mut deferred_world = self.world.into_deferred();
144-
let bundle_components_in_archetype = || {
145-
self.bundle_info
146-
.as_ref()
147-
.iter_explicit_components()
148-
.filter(|component_id| self.old_archetype.as_ref().contains(*component_id))
149-
};
150-
if self.old_archetype.as_ref().has_discard_observer() {
151-
let components = bundle_components_in_archetype().collect::<Vec<_>>();
170+
if has_discard_observer {
171+
let components = observer_components.as_ref().unwrap();
152172
// SAFETY: the DISCARD event_key corresponds to the Discard event's type
153173
deferred_world.trigger_raw(
154174
DISCARD,
155175
&mut Discard { entity },
156176
&mut EntityComponentsTrigger {
157-
components: &components,
158-
old_archetype: Some(self.old_archetype.as_ref()),
159-
new_archetype: Some(self.new_archetype.as_ref()),
177+
components,
178+
old_archetype: Some(old_archetype),
179+
new_archetype: Some(new_archetype),
160180
},
161181
caller,
162182
);
163183
}
164184
deferred_world.trigger_on_discard(
165-
self.old_archetype.as_ref(),
185+
old_archetype,
166186
entity,
167-
bundle_components_in_archetype(),
187+
explicit_components_in_archetype(explicit_components, old_archetype),
168188
caller,
169189
self.relationship_hook_mode,
170190
);
171-
if self.old_archetype.as_ref().has_remove_observer() {
172-
let components = bundle_components_in_archetype().collect::<Vec<_>>();
191+
if has_remove_observer {
192+
let components = observer_components.as_ref().unwrap();
173193
// SAFETY: the REMOVE event_key corresponds to the Remove event's type
174194
deferred_world.trigger_raw(
175195
REMOVE,
176196
&mut Remove { entity },
177197
&mut EntityComponentsTrigger {
178-
components: &components,
179-
old_archetype: Some(self.old_archetype.as_ref()),
180-
new_archetype: Some(self.new_archetype.as_ref()),
198+
components,
199+
old_archetype: Some(old_archetype),
200+
new_archetype: Some(new_archetype),
181201
},
182202
caller,
183203
);
184204
}
185205
deferred_world.trigger_on_remove(
186-
self.old_archetype.as_ref(),
206+
old_archetype,
187207
entity,
188-
bundle_components_in_archetype(),
208+
explicit_components_in_archetype(explicit_components, old_archetype),
189209
caller,
190210
);
191211
}
192212

193-
// SAFETY: We still have the cell, so this is unique, it doesn't conflict with other references, and we drop it shortly.
194-
let world = unsafe { self.world.world_mut() };
195-
196-
let (needs_drop, pre_remove_result) = pre_remove(
197-
&mut world.storages.sparse_sets,
198-
self.old_and_new_table
199-
.as_ref()
200-
// SAFETY: There is no conflicting access for this scope.
201-
.map(|(old, _)| unsafe { &mut *old.as_ptr() }),
202-
&world.components,
203-
self.bundle_info.as_ref().explicit_components(),
204-
);
205-
206-
// Handle sparse set removes
207-
for component_id in self.bundle_info.as_ref().iter_explicit_components() {
208-
if self.old_archetype.as_ref().contains(component_id) {
209-
world.removed_components.write(component_id, entity);
210-
211-
// Make sure to drop components stored in sparse sets.
212-
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`.
213-
if let Some(StorageType::SparseSet) =
214-
self.old_archetype.as_ref().get_storage_type(component_id)
215-
{
216-
world
217-
.storages
218-
.sparse_sets
219-
.get_mut(component_id)
220-
// Set exists because the component existed on the entity
221-
.unwrap()
222-
// If it was already forgotten, it would not be in the set.
223-
.remove(entity);
224-
}
225-
}
226-
}
213+
let (new_location, pre_remove_result) = {
214+
// SAFETY: We still have the cell, so this is unique, it doesn't conflict with other references, and we drop it shortly.
215+
let world = unsafe { self.world.world_mut() };
227216

228-
// Handle archetype change
229-
let remove_result = self
230-
.old_archetype
231-
.as_mut()
232-
.swap_remove(location.archetype_row);
233-
// if an entity was moved into this entity's archetype row, update its archetype row
234-
if let Some(swapped_entity) = remove_result.swapped_entity {
235-
let swapped_location = world.entities.get_spawned(swapped_entity).unwrap();
236-
237-
world.entities.update_existing_location(
238-
swapped_entity.index(),
239-
Some(EntityLocation {
240-
archetype_id: swapped_location.archetype_id,
241-
archetype_row: location.archetype_row,
242-
table_id: swapped_location.table_id,
243-
table_row: swapped_location.table_row,
244-
}),
217+
let (needs_drop, pre_remove_result) = pre_remove(
218+
&mut world.storages.sparse_sets,
219+
self.old_and_new_table
220+
.as_ref()
221+
// SAFETY: There is no conflicting access for this scope.
222+
.map(|(old, _)| unsafe { &mut *old.as_ptr() }),
223+
&world.components,
224+
explicit_components,
245225
);
246-
}
247226

248-
// Handle table change
249-
let new_location = if let Some((mut old_table, mut new_table)) = self.old_and_new_table {
250-
let move_result = if needs_drop {
251-
// SAFETY: old_table_row exists
252-
unsafe {
253-
old_table
254-
.as_mut()
255-
.move_to_and_drop_missing_unchecked(location.table_row, new_table.as_mut())
256-
}
257-
} else {
258-
// SAFETY: old_table_row exists
259-
unsafe {
260-
old_table.as_mut().move_to_and_forget_missing_unchecked(
261-
location.table_row,
262-
new_table.as_mut(),
263-
)
227+
{
228+
let old_archetype = self.old_archetype.as_ref();
229+
for &component_id in explicit_components {
230+
if !old_archetype.contains(component_id) {
231+
continue;
232+
}
233+
world.removed_components.write(component_id, entity);
234+
235+
// Make sure to drop components stored in sparse sets.
236+
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`.
237+
if let Some(StorageType::SparseSet) =
238+
old_archetype.get_storage_type(component_id)
239+
{
240+
world
241+
.storages
242+
.sparse_sets
243+
.get_mut(component_id)
244+
// Set exists because the component existed on the entity
245+
.unwrap()
246+
// If it was already forgotten, it would not be in the set.
247+
.remove(entity);
248+
}
264249
}
265-
};
266-
267-
// SAFETY: move_result.new_row is a valid position in new_archetype's table
268-
let new_location = unsafe {
269-
self.new_archetype
270-
.as_mut()
271-
.allocate(entity, move_result.new_row)
272-
};
250+
}
273251

274-
// if an entity was moved into this entity's table row, update its table row
275-
if let Some(swapped_entity) = move_result.swapped_entity {
252+
let remove_result = self
253+
.old_archetype
254+
.as_mut()
255+
.swap_remove(location.archetype_row);
256+
if let Some(swapped_entity) = remove_result.swapped_entity {
276257
let swapped_location = world.entities.get_spawned(swapped_entity).unwrap();
277258

278259
world.entities.update_existing_location(
279260
swapped_entity.index(),
280261
Some(EntityLocation {
281262
archetype_id: swapped_location.archetype_id,
282-
archetype_row: swapped_location.archetype_row,
263+
archetype_row: location.archetype_row,
283264
table_id: swapped_location.table_id,
284-
table_row: location.table_row,
265+
table_row: swapped_location.table_row,
285266
}),
286267
);
287-
world.archetypes[swapped_location.archetype_id]
288-
.set_entity_table_row(swapped_location.archetype_row, location.table_row);
289268
}
290269

291-
new_location
292-
} else {
293-
// The tables are the same
294-
self.new_archetype
295-
.as_mut()
296-
.allocate(entity, location.table_row)
297-
};
298-
299-
// SAFETY: The entity is valid and has been moved to the new location already.
300-
unsafe {
301-
world
302-
.entities
303-
.update_existing_location(entity.index(), Some(new_location));
304-
}
270+
let new_location = if let Some((mut old_table, mut new_table)) = self.old_and_new_table
271+
{
272+
let move_result = if needs_drop {
273+
// SAFETY: old_table_row exists
274+
unsafe {
275+
old_table.as_mut().move_to_and_drop_missing_unchecked(
276+
location.table_row,
277+
new_table.as_mut(),
278+
)
279+
}
280+
} else {
281+
// SAFETY: old_table_row exists
282+
unsafe {
283+
old_table.as_mut().move_to_and_forget_missing_unchecked(
284+
location.table_row,
285+
new_table.as_mut(),
286+
)
287+
}
288+
};
305289

306-
// Trigger AfterRemove for removed components.
307-
// The entity is now in the new archetype; component data has been dropped.
308-
// SAFETY: all bundle components exist in World
309-
unsafe {
310-
let old_archetype = self.old_archetype.as_ref();
311-
if old_archetype.has_after_remove_hook() || old_archetype.has_after_remove_observer() {
312-
let mut deferred_world = self.world.into_deferred();
313-
let bundle_components_in_archetype = || {
314-
self.bundle_info
315-
.as_ref()
316-
.iter_explicit_components()
317-
.filter(|component_id| old_archetype.contains(*component_id))
290+
// SAFETY: move_result.new_row is a valid position in new_archetype's table
291+
let allocated_location = unsafe {
292+
self.new_archetype
293+
.as_mut()
294+
.allocate(entity, move_result.new_row)
318295
};
296+
297+
if let Some(swapped_entity) = move_result.swapped_entity {
298+
let swapped_location = world.entities.get_spawned(swapped_entity).unwrap();
299+
300+
world.entities.update_existing_location(
301+
swapped_entity.index(),
302+
Some(EntityLocation {
303+
archetype_id: swapped_location.archetype_id,
304+
archetype_row: swapped_location.archetype_row,
305+
table_id: swapped_location.table_id,
306+
table_row: location.table_row,
307+
}),
308+
);
309+
world.archetypes[swapped_location.archetype_id]
310+
.set_entity_table_row(swapped_location.archetype_row, location.table_row);
311+
}
312+
313+
allocated_location
314+
} else {
315+
self.new_archetype
316+
.as_mut()
317+
.allocate(entity, location.table_row)
318+
};
319+
320+
// SAFETY: The entity is valid and has been moved to the new location already.
321+
unsafe {
322+
world
323+
.entities
324+
.update_existing_location(entity.index(), Some(new_location));
325+
}
326+
327+
(new_location, pre_remove_result)
328+
};
329+
330+
// Trigger AfterRemove after the entity has moved and removed data is gone.
331+
// SAFETY:
332+
// - all bundle components exist in World
333+
// - only type metadata is read from archetypes during these hooks
334+
if has_after_remove_hook || has_after_remove_observer {
335+
// SAFETY:
336+
// - `old_archetype_id` and `new_location.archetype_id` are valid IDs
337+
// - only type metadata is read from archetypes while triggering hooks/observers
338+
unsafe {
339+
let world = self.world;
340+
let archetypes = world.archetypes();
341+
let old_archetype = &archetypes[old_archetype_id];
342+
let mut deferred_world = world.into_deferred();
319343
deferred_world.trigger_after_remove(
320344
old_archetype,
321345
entity,
322-
bundle_components_in_archetype(),
346+
explicit_components_in_archetype(explicit_components, old_archetype),
323347
caller,
324348
);
325-
if old_archetype.has_after_remove_observer() {
326-
let removed_components = bundle_components_in_archetype().collect::<Vec<_>>();
327-
let new_archetype = self.new_archetype.as_ref();
349+
if has_after_remove_observer {
350+
let new_archetype = &archetypes[new_location.archetype_id];
351+
let removed_components =
352+
explicit_components_in_archetype(explicit_components, old_archetype)
353+
.collect::<Vec<_>>();
328354
deferred_world.trigger_raw(
329355
AFTER_REMOVE,
330356
&mut AfterRemove { entity },

0 commit comments

Comments
 (0)