diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index e17a9f78e0b61..a5b4c68bce010 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -450,32 +450,71 @@ mod tests { } #[test] - fn remove_tracking() { + fn removal_tracking() { let mut world = World::new(); + + let entity_to_despawn = world.spawn().insert(W(1)).id(); + let entity_to_remove_w_from = world.spawn().insert(W(2)).id(); + let spurious_entity = world.spawn().id(); + + // Track which entities we want to operate on struct Despawned(Entity); - let a = world.spawn().insert_bundle((W("abc"), W(123))).id(); - world.spawn().insert_bundle((W("abc"), W(123))); - world.insert_resource(false); - world.insert_resource(Despawned(a)); + world.insert_resource(Despawned(entity_to_despawn)); + struct Removed(Entity); + world.insert_resource(Removed(entity_to_remove_w_from)); - world.entity_mut(a).despawn(); + // Verify that all the systems actually ran + #[derive(Default)] + struct NSystems(usize); + world.insert_resource(NSystems::default()); - fn validate_removed( + // First, check that removal detection is triggered if and only if we despawn an entity with the correct component + world.entity_mut(entity_to_despawn).despawn(); + world.entity_mut(spurious_entity).despawn(); + + fn validate_despawn( removed_i32: RemovedComponents>, despawned: Res, - mut ran: ResMut, + mut n_systems: ResMut, ) { assert_eq!( removed_i32.iter().collect::>(), &[despawned.0], - "despawning results in 'removed component' state" + "despawning causes the correct entity to show up in the 'RemovedComponent' system parameter." ); - *ran = true; + n_systems.0 += 1; } - run_system(&mut world, validate_removed); - assert!(*world.get_resource::().unwrap(), "system ran"); + run_system(&mut world, validate_despawn); + + // Reset the trackers to clear the buffer of removed components + // Ordinarily, this is done in a system added by MinimalPlugins + world.clear_trackers(); + + // Then, try removing a component + world.spawn().insert(W(3)).id(); + world.spawn().insert(W(4)).id(); + world.entity_mut(entity_to_remove_w_from).remove::>(); + + fn validate_remove( + removed_i32: RemovedComponents>, + removed: Res, + mut n_systems: ResMut, + ) { + assert_eq!( + removed_i32.iter().collect::>(), + &[removed.0], + "removing a component causes the correct entity to show up in the 'RemovedComponent' system parameter." + ); + + n_systems.0 += 1; + } + + run_system(&mut world, validate_remove); + + // Verify that both systems actually ran + assert_eq!(world.get_resource::().unwrap().0, 2); } #[test] diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index af52e24d7bec4..77022072e61a7 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -624,6 +624,18 @@ impl<'w, 's, T: Resource + FromWorld> SystemParamFetch<'w, 's> for LocalState /// A [`SystemParam`] that grants access to the entities that had their `T` [`Component`] removed. /// +/// Note that this does not allow you to see which data existed before removal. +/// If you need this, you will need to track the component data value on your own, +/// using a regularly scheduled system that requests `Query<(Entity, &T), Changed>` +/// and stores the data somewhere safe to later cross-reference. +/// +/// If you are using `bevy_ecs` as a standalone crate, +/// note that the `RemovedComponents` list will not be automatically cleared for you, +/// and will need to be manually flushed using [`World::clear_trackers`] +/// +/// For users of `bevy` itself, this is automatically done in a system added by `MinimalPlugins` +/// or `DefaultPlugins` at the end of each pass of the game loop. +/// /// # Examples /// /// Basic usage: