Skip to content

Commit 073f381

Browse files
Removal detection cleanup (#3010)
# Objective - Fixes #1920. - Users often want to know how to get the values of removed components (#1655). - Stand-alone `bevy_ecs` behavior is very unintuitive, as `World::clear_trackers()` must be manually called. - Fixes #2999 by extending the existing test (thanks @hymm for pointing me to it) to be clearer and check for component removal as well. ## Solution - Better docs! - Better tests!
1 parent b147601 commit 073f381

File tree

2 files changed

+63
-12
lines changed

2 files changed

+63
-12
lines changed

crates/bevy_ecs/src/system/mod.rs

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -450,32 +450,71 @@ mod tests {
450450
}
451451

452452
#[test]
453-
fn remove_tracking() {
453+
fn removal_tracking() {
454454
let mut world = World::new();
455+
456+
let entity_to_despawn = world.spawn().insert(W(1)).id();
457+
let entity_to_remove_w_from = world.spawn().insert(W(2)).id();
458+
let spurious_entity = world.spawn().id();
459+
460+
// Track which entities we want to operate on
455461
struct Despawned(Entity);
456-
let a = world.spawn().insert_bundle((W("abc"), W(123))).id();
457-
world.spawn().insert_bundle((W("abc"), W(123)));
458-
world.insert_resource(false);
459-
world.insert_resource(Despawned(a));
462+
world.insert_resource(Despawned(entity_to_despawn));
463+
struct Removed(Entity);
464+
world.insert_resource(Removed(entity_to_remove_w_from));
460465

461-
world.entity_mut(a).despawn();
466+
// Verify that all the systems actually ran
467+
#[derive(Default)]
468+
struct NSystems(usize);
469+
world.insert_resource(NSystems::default());
462470

463-
fn validate_removed(
471+
// First, check that removal detection is triggered if and only if we despawn an entity with the correct component
472+
world.entity_mut(entity_to_despawn).despawn();
473+
world.entity_mut(spurious_entity).despawn();
474+
475+
fn validate_despawn(
464476
removed_i32: RemovedComponents<W<i32>>,
465477
despawned: Res<Despawned>,
466-
mut ran: ResMut<bool>,
478+
mut n_systems: ResMut<NSystems>,
467479
) {
468480
assert_eq!(
469481
removed_i32.iter().collect::<Vec<_>>(),
470482
&[despawned.0],
471-
"despawning results in 'removed component' state"
483+
"despawning causes the correct entity to show up in the 'RemovedComponent' system parameter."
472484
);
473485

474-
*ran = true;
486+
n_systems.0 += 1;
475487
}
476488

477-
run_system(&mut world, validate_removed);
478-
assert!(*world.get_resource::<bool>().unwrap(), "system ran");
489+
run_system(&mut world, validate_despawn);
490+
491+
// Reset the trackers to clear the buffer of removed components
492+
// Ordinarily, this is done in a system added by MinimalPlugins
493+
world.clear_trackers();
494+
495+
// Then, try removing a component
496+
world.spawn().insert(W(3)).id();
497+
world.spawn().insert(W(4)).id();
498+
world.entity_mut(entity_to_remove_w_from).remove::<W<i32>>();
499+
500+
fn validate_remove(
501+
removed_i32: RemovedComponents<W<i32>>,
502+
removed: Res<Removed>,
503+
mut n_systems: ResMut<NSystems>,
504+
) {
505+
assert_eq!(
506+
removed_i32.iter().collect::<Vec<_>>(),
507+
&[removed.0],
508+
"removing a component causes the correct entity to show up in the 'RemovedComponent' system parameter."
509+
);
510+
511+
n_systems.0 += 1;
512+
}
513+
514+
run_system(&mut world, validate_remove);
515+
516+
// Verify that both systems actually ran
517+
assert_eq!(world.get_resource::<NSystems>().unwrap().0, 2);
479518
}
480519

481520
#[test]

crates/bevy_ecs/src/system/system_param.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,18 @@ impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's> for LocalState<T>
624624

625625
/// A [`SystemParam`] that grants access to the entities that had their `T` [`Component`] removed.
626626
///
627+
/// Note that this does not allow you to see which data existed before removal.
628+
/// If you need this, you will need to track the component data value on your own,
629+
/// using a regularly scheduled system that requests `Query<(Entity, &T), Changed<T>>`
630+
/// and stores the data somewhere safe to later cross-reference.
631+
///
632+
/// If you are using `bevy_ecs` as a standalone crate,
633+
/// note that the `RemovedComponents` list will not be automatically cleared for you,
634+
/// and will need to be manually flushed using [`World::clear_trackers`]
635+
///
636+
/// For users of `bevy` itself, this is automatically done in a system added by `MinimalPlugins`
637+
/// or `DefaultPlugins` at the end of each pass of the game loop.
638+
///
627639
/// # Examples
628640
///
629641
/// Basic usage:

0 commit comments

Comments
 (0)