Skip to content

Commit 9da65b1

Browse files
authored
Add EntityCommands.retain and EntityWorldMut.retain (#10873)
# Objective Adds `EntityCommands.retain` and `EntityWorldMut.retain` to remove all components except the given bundle from the entity. Fixes #10865. ## Solution I added a private unsafe function in `EntityWorldMut` called `remove_bundle_info` which performs the shared behaviour of `remove` and `retain`, namely taking a `BundleInfo` of components to remove, and removing them from the given entity. Then `retain` simply gets all the components on the entity and filters them by whether they are in the bundle it was passed, before passing this `BundleInfo` into `remove_bundle_info`. `EntityCommands.retain` just creates a new type `Retain` which runs `EntityWorldMut.retain` when run. --- ## Changelog Added `EntityCommands.retain` and `EntityWorldMut.retain`, which remove all components except the given bundle from the entity, they can also be used to remove all components by passing `()` as the bundle.
1 parent 166686e commit 9da65b1

File tree

2 files changed

+218
-32
lines changed

2 files changed

+218
-32
lines changed

crates/bevy_ecs/src/system/commands/mod.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,54 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
908908
self
909909
}
910910

911+
/// Removes all components except the given [`Bundle`] from the entity.
912+
///
913+
/// This can also be used to remove all the components from the entity by passing it an empty Bundle.
914+
///
915+
/// See [`EntityWorldMut::retain`](EntityWorldMut::retain) for more
916+
/// details.
917+
///
918+
/// # Example
919+
///
920+
/// ```
921+
/// # use bevy_ecs::prelude::*;
922+
/// #
923+
/// # #[derive(Resource)]
924+
/// # struct PlayerEntity { entity: Entity }
925+
/// #[derive(Component)]
926+
/// struct Health(u32);
927+
/// #[derive(Component)]
928+
/// struct Strength(u32);
929+
/// #[derive(Component)]
930+
/// struct Defense(u32);
931+
///
932+
/// #[derive(Bundle)]
933+
/// struct CombatBundle {
934+
/// health: Health,
935+
/// strength: Strength,
936+
/// }
937+
///
938+
/// fn remove_combat_stats_system(mut commands: Commands, player: Res<PlayerEntity>) {
939+
/// commands
940+
/// .entity(player.entity)
941+
/// // You can retain a pre-defined Bundle of components,
942+
/// // with this removing only the Defense component
943+
/// .retain::<CombatBundle>()
944+
/// // You can also retain only a single component
945+
/// .retain::<Health>()
946+
/// // And you can remove all the components by passing in an empty Bundle
947+
/// .retain::<()>();
948+
/// }
949+
/// # bevy_ecs::system::assert_is_system(remove_combat_stats_system);
950+
/// ```
951+
pub fn retain<T>(&mut self) -> &mut Self
952+
where
953+
T: Bundle,
954+
{
955+
self.commands.add(Retain::<T>::new(self.entity));
956+
self
957+
}
958+
911959
/// Logs the components of the entity at the info level.
912960
///
913961
/// # Panics
@@ -1097,6 +1145,37 @@ impl<T> Remove<T> {
10971145
}
10981146
}
10991147

1148+
/// A [`Command`] that removes components from an entity.
1149+
/// For a [`Bundle`] type `T`, this will remove all components except those in the bundle.
1150+
/// Any components in the bundle that aren't found on the entity will be ignored.
1151+
#[derive(Debug)]
1152+
pub struct Retain<T> {
1153+
/// The entity from which the components will be removed.
1154+
pub entity: Entity,
1155+
_marker: PhantomData<T>,
1156+
}
1157+
1158+
impl<T> Command for Retain<T>
1159+
where
1160+
T: Bundle,
1161+
{
1162+
fn apply(self, world: &mut World) {
1163+
if let Some(mut entity_mut) = world.get_entity_mut(self.entity) {
1164+
entity_mut.retain::<T>();
1165+
}
1166+
}
1167+
}
1168+
1169+
impl<T> Retain<T> {
1170+
/// Creates a [`Command`] which will remove all but the specified components when applied.
1171+
pub const fn new(entity: Entity) -> Self {
1172+
Self {
1173+
entity,
1174+
_marker: PhantomData,
1175+
}
1176+
}
1177+
}
1178+
11001179
/// A [`Command`] that inserts a [`Resource`] into the world using a value
11011180
/// created with the [`FromWorld`] trait.
11021181
pub struct InitResource<R: Resource + FromWorld> {

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 139 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -825,38 +825,39 @@ impl<'w> EntityWorldMut<'w> {
825825
entities.set(entity.index(), new_location);
826826
}
827827

828-
/// Removes any components in the [`Bundle`] from the entity.
829-
// TODO: BundleRemover?
830-
pub fn remove<T: Bundle>(&mut self) -> &mut Self {
831-
let archetypes = &mut self.world.archetypes;
832-
let storages = &mut self.world.storages;
833-
let components = &mut self.world.components;
834-
let entities = &mut self.world.entities;
835-
let removed_components = &mut self.world.removed_components;
836-
837-
let bundle_info = self.world.bundles.init_info::<T>(components, storages);
838-
let old_location = self.location;
839-
840-
// SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid,
841-
// components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T`
842-
let new_archetype_id = unsafe {
843-
remove_bundle_from_archetype(
844-
archetypes,
845-
storages,
846-
components,
847-
old_location.archetype_id,
848-
bundle_info,
849-
true,
850-
)
851-
.expect("intersections should always return a result")
852-
};
828+
/// Remove the components of `bundle_info` from `entity`, where `self_location` and `old_location`
829+
/// are the location of this entity, and `self_location` is updated to the new location.
830+
///
831+
/// SAFETY: `old_location` must be valid and the components in `bundle_info` must exist.
832+
#[allow(clippy::too_many_arguments)]
833+
unsafe fn remove_bundle_info(
834+
entity: Entity,
835+
self_location: &mut EntityLocation,
836+
old_location: EntityLocation,
837+
bundle_info: &BundleInfo,
838+
archetypes: &mut Archetypes,
839+
storages: &mut Storages,
840+
components: &Components,
841+
entities: &mut Entities,
842+
removed_components: &mut RemovedComponentEvents,
843+
) {
844+
// SAFETY: `archetype_id` exists because it is referenced in `old_location` which is valid
845+
// and components in `bundle_info` must exist due to this functions safety invariants.
846+
let new_archetype_id = remove_bundle_from_archetype(
847+
archetypes,
848+
storages,
849+
components,
850+
old_location.archetype_id,
851+
bundle_info,
852+
true,
853+
)
854+
.expect("intersections should always return a result");
853855

854856
if new_archetype_id == old_location.archetype_id {
855-
return self;
857+
return;
856858
}
857859

858860
let old_archetype = &mut archetypes[old_location.archetype_id];
859-
let entity = self.entity;
860861
for component_id in bundle_info.components().iter().cloned() {
861862
if old_archetype.contains(component_id) {
862863
removed_components.send(component_id, entity);
@@ -873,17 +874,86 @@ impl<'w> EntityWorldMut<'w> {
873874
}
874875
}
875876

876-
#[allow(clippy::undocumented_unsafe_blocks)] // TODO: document why this is safe
877+
// SAFETY: `new_archetype_id` is a subset of the components in `old_location.archetype_id`
878+
// because it is created by removing a bundle from these components.
879+
Self::move_entity_from_remove::<true>(
880+
entity,
881+
self_location,
882+
old_location.archetype_id,
883+
old_location,
884+
entities,
885+
archetypes,
886+
storages,
887+
new_archetype_id,
888+
);
889+
}
890+
891+
/// Removes any components in the [`Bundle`] from the entity.
892+
// TODO: BundleRemover?
893+
pub fn remove<T: Bundle>(&mut self) -> &mut Self {
894+
let archetypes = &mut self.world.archetypes;
895+
let storages = &mut self.world.storages;
896+
let components = &mut self.world.components;
897+
let entities = &mut self.world.entities;
898+
let removed_components = &mut self.world.removed_components;
899+
900+
let bundle_info = self.world.bundles.init_info::<T>(components, storages);
901+
let old_location = self.location;
902+
903+
// SAFETY: Components exist in `bundle_info` because `Bundles::init_info`
904+
// initializes a `BundleInfo` containing all components of the bundle type `T`.
877905
unsafe {
878-
Self::move_entity_from_remove::<true>(
879-
entity,
906+
Self::remove_bundle_info(
907+
self.entity,
880908
&mut self.location,
881-
old_location.archetype_id,
882909
old_location,
910+
bundle_info,
911+
archetypes,
912+
storages,
913+
components,
883914
entities,
915+
removed_components,
916+
);
917+
}
918+
919+
self
920+
}
921+
922+
/// Removes any components except those in the [`Bundle`] from the entity.
923+
pub fn retain<T: Bundle>(&mut self) -> &mut Self {
924+
let archetypes = &mut self.world.archetypes;
925+
let storages = &mut self.world.storages;
926+
let components = &mut self.world.components;
927+
let entities = &mut self.world.entities;
928+
let removed_components = &mut self.world.removed_components;
929+
930+
let retained_bundle_info = self.world.bundles.init_info::<T>(components, storages);
931+
let old_location = self.location;
932+
let old_archetype = &mut archetypes[old_location.archetype_id];
933+
934+
let to_remove = &old_archetype
935+
.components()
936+
.filter(|c| !retained_bundle_info.components().contains(c))
937+
.collect::<Vec<_>>();
938+
let remove_bundle_info = self
939+
.world
940+
.bundles
941+
.init_dynamic_info(components, to_remove)
942+
.0;
943+
944+
// SAFETY: Components exist in `remove_bundle_info` because `Bundles::init_dynamic_info`
945+
// initializes a `BundleInfo` containing all components in the to_remove Bundle.
946+
unsafe {
947+
Self::remove_bundle_info(
948+
self.entity,
949+
&mut self.location,
950+
old_location,
951+
remove_bundle_info,
884952
archetypes,
885953
storages,
886-
new_archetype_id,
954+
components,
955+
entities,
956+
removed_components,
887957
);
888958
}
889959

@@ -1775,6 +1845,43 @@ mod tests {
17751845
assert_eq!(world.entity(e2).get::<Dense>().unwrap(), &Dense(1));
17761846
}
17771847

1848+
// Test that calling retain with `()` removes all components.
1849+
#[test]
1850+
fn retain_nothing() {
1851+
#[derive(Component)]
1852+
struct Marker<const N: usize>;
1853+
1854+
let mut world = World::new();
1855+
let ent = world.spawn((Marker::<1>, Marker::<2>, Marker::<3>)).id();
1856+
1857+
world.entity_mut(ent).retain::<()>();
1858+
assert_eq!(world.entity(ent).archetype().components().next(), None);
1859+
}
1860+
1861+
// Test removing some components with `retain`, including components not on the entity.
1862+
#[test]
1863+
fn retain_some_components() {
1864+
#[derive(Component)]
1865+
struct Marker<const N: usize>;
1866+
1867+
let mut world = World::new();
1868+
let ent = world.spawn((Marker::<1>, Marker::<2>, Marker::<3>)).id();
1869+
1870+
world.entity_mut(ent).retain::<(Marker<2>, Marker<4>)>();
1871+
// Check that marker 2 was retained.
1872+
assert!(world.entity(ent).get::<Marker<2>>().is_some());
1873+
// Check that only marker 2 was retained.
1874+
assert_eq!(
1875+
world
1876+
.entity(ent)
1877+
.archetype()
1878+
.components()
1879+
.collect::<Vec<_>>()
1880+
.len(),
1881+
1
1882+
);
1883+
}
1884+
17781885
// regression test for https://github.com/bevyengine/bevy/pull/7805
17791886
#[test]
17801887
fn inserting_sparse_updates_archetype_row() {

0 commit comments

Comments
 (0)