mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +00:00
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.
This commit is contained in:
parent
166686e0f2
commit
9da65b10b4
2 changed files with 219 additions and 33 deletions
|
@ -908,6 +908,54 @@ impl<'w, 's, 'a> EntityCommands<'w, 's, 'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes all components except the given [`Bundle`] from the entity.
|
||||||
|
///
|
||||||
|
/// This can also be used to remove all the components from the entity by passing it an empty Bundle.
|
||||||
|
///
|
||||||
|
/// See [`EntityWorldMut::retain`](EntityWorldMut::retain) for more
|
||||||
|
/// details.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// #
|
||||||
|
/// # #[derive(Resource)]
|
||||||
|
/// # struct PlayerEntity { entity: Entity }
|
||||||
|
/// #[derive(Component)]
|
||||||
|
/// struct Health(u32);
|
||||||
|
/// #[derive(Component)]
|
||||||
|
/// struct Strength(u32);
|
||||||
|
/// #[derive(Component)]
|
||||||
|
/// struct Defense(u32);
|
||||||
|
///
|
||||||
|
/// #[derive(Bundle)]
|
||||||
|
/// struct CombatBundle {
|
||||||
|
/// health: Health,
|
||||||
|
/// strength: Strength,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn remove_combat_stats_system(mut commands: Commands, player: Res<PlayerEntity>) {
|
||||||
|
/// commands
|
||||||
|
/// .entity(player.entity)
|
||||||
|
/// // You can retain a pre-defined Bundle of components,
|
||||||
|
/// // with this removing only the Defense component
|
||||||
|
/// .retain::<CombatBundle>()
|
||||||
|
/// // You can also retain only a single component
|
||||||
|
/// .retain::<Health>()
|
||||||
|
/// // And you can remove all the components by passing in an empty Bundle
|
||||||
|
/// .retain::<()>();
|
||||||
|
/// }
|
||||||
|
/// # bevy_ecs::system::assert_is_system(remove_combat_stats_system);
|
||||||
|
/// ```
|
||||||
|
pub fn retain<T>(&mut self) -> &mut Self
|
||||||
|
where
|
||||||
|
T: Bundle,
|
||||||
|
{
|
||||||
|
self.commands.add(Retain::<T>::new(self.entity));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Logs the components of the entity at the info level.
|
/// Logs the components of the entity at the info level.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
@ -1097,6 +1145,37 @@ impl<T> Remove<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [`Command`] that removes components from an entity.
|
||||||
|
/// For a [`Bundle`] type `T`, this will remove all components except those in the bundle.
|
||||||
|
/// Any components in the bundle that aren't found on the entity will be ignored.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Retain<T> {
|
||||||
|
/// The entity from which the components will be removed.
|
||||||
|
pub entity: Entity,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Command for Retain<T>
|
||||||
|
where
|
||||||
|
T: Bundle,
|
||||||
|
{
|
||||||
|
fn apply(self, world: &mut World) {
|
||||||
|
if let Some(mut entity_mut) = world.get_entity_mut(self.entity) {
|
||||||
|
entity_mut.retain::<T>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Retain<T> {
|
||||||
|
/// Creates a [`Command`] which will remove all but the specified components when applied.
|
||||||
|
pub const fn new(entity: Entity) -> Self {
|
||||||
|
Self {
|
||||||
|
entity,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A [`Command`] that inserts a [`Resource`] into the world using a value
|
/// A [`Command`] that inserts a [`Resource`] into the world using a value
|
||||||
/// created with the [`FromWorld`] trait.
|
/// created with the [`FromWorld`] trait.
|
||||||
pub struct InitResource<R: Resource + FromWorld> {
|
pub struct InitResource<R: Resource + FromWorld> {
|
||||||
|
|
|
@ -825,38 +825,39 @@ impl<'w> EntityWorldMut<'w> {
|
||||||
entities.set(entity.index(), new_location);
|
entities.set(entity.index(), new_location);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes any components in the [`Bundle`] from the entity.
|
/// Remove the components of `bundle_info` from `entity`, where `self_location` and `old_location`
|
||||||
// TODO: BundleRemover?
|
/// are the location of this entity, and `self_location` is updated to the new location.
|
||||||
pub fn remove<T: Bundle>(&mut self) -> &mut Self {
|
///
|
||||||
let archetypes = &mut self.world.archetypes;
|
/// SAFETY: `old_location` must be valid and the components in `bundle_info` must exist.
|
||||||
let storages = &mut self.world.storages;
|
#[allow(clippy::too_many_arguments)]
|
||||||
let components = &mut self.world.components;
|
unsafe fn remove_bundle_info(
|
||||||
let entities = &mut self.world.entities;
|
entity: Entity,
|
||||||
let removed_components = &mut self.world.removed_components;
|
self_location: &mut EntityLocation,
|
||||||
|
old_location: EntityLocation,
|
||||||
let bundle_info = self.world.bundles.init_info::<T>(components, storages);
|
bundle_info: &BundleInfo,
|
||||||
let old_location = self.location;
|
archetypes: &mut Archetypes,
|
||||||
|
storages: &mut Storages,
|
||||||
// SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid,
|
components: &Components,
|
||||||
// components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T`
|
entities: &mut Entities,
|
||||||
let new_archetype_id = unsafe {
|
removed_components: &mut RemovedComponentEvents,
|
||||||
remove_bundle_from_archetype(
|
) {
|
||||||
archetypes,
|
// SAFETY: `archetype_id` exists because it is referenced in `old_location` which is valid
|
||||||
storages,
|
// and components in `bundle_info` must exist due to this functions safety invariants.
|
||||||
components,
|
let new_archetype_id = remove_bundle_from_archetype(
|
||||||
old_location.archetype_id,
|
archetypes,
|
||||||
bundle_info,
|
storages,
|
||||||
true,
|
components,
|
||||||
)
|
old_location.archetype_id,
|
||||||
.expect("intersections should always return a result")
|
bundle_info,
|
||||||
};
|
true,
|
||||||
|
)
|
||||||
|
.expect("intersections should always return a result");
|
||||||
|
|
||||||
if new_archetype_id == old_location.archetype_id {
|
if new_archetype_id == old_location.archetype_id {
|
||||||
return self;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_archetype = &mut archetypes[old_location.archetype_id];
|
let old_archetype = &mut archetypes[old_location.archetype_id];
|
||||||
let entity = self.entity;
|
|
||||||
for component_id in bundle_info.components().iter().cloned() {
|
for component_id in bundle_info.components().iter().cloned() {
|
||||||
if old_archetype.contains(component_id) {
|
if old_archetype.contains(component_id) {
|
||||||
removed_components.send(component_id, entity);
|
removed_components.send(component_id, entity);
|
||||||
|
@ -873,17 +874,86 @@ impl<'w> EntityWorldMut<'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::undocumented_unsafe_blocks)] // TODO: document why this is safe
|
// SAFETY: `new_archetype_id` is a subset of the components in `old_location.archetype_id`
|
||||||
|
// because it is created by removing a bundle from these components.
|
||||||
|
Self::move_entity_from_remove::<true>(
|
||||||
|
entity,
|
||||||
|
self_location,
|
||||||
|
old_location.archetype_id,
|
||||||
|
old_location,
|
||||||
|
entities,
|
||||||
|
archetypes,
|
||||||
|
storages,
|
||||||
|
new_archetype_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes any components in the [`Bundle`] from the entity.
|
||||||
|
// TODO: BundleRemover?
|
||||||
|
pub fn remove<T: Bundle>(&mut self) -> &mut Self {
|
||||||
|
let archetypes = &mut self.world.archetypes;
|
||||||
|
let storages = &mut self.world.storages;
|
||||||
|
let components = &mut self.world.components;
|
||||||
|
let entities = &mut self.world.entities;
|
||||||
|
let removed_components = &mut self.world.removed_components;
|
||||||
|
|
||||||
|
let bundle_info = self.world.bundles.init_info::<T>(components, storages);
|
||||||
|
let old_location = self.location;
|
||||||
|
|
||||||
|
// SAFETY: Components exist in `bundle_info` because `Bundles::init_info`
|
||||||
|
// initializes a `BundleInfo` containing all components of the bundle type `T`.
|
||||||
unsafe {
|
unsafe {
|
||||||
Self::move_entity_from_remove::<true>(
|
Self::remove_bundle_info(
|
||||||
entity,
|
self.entity,
|
||||||
&mut self.location,
|
&mut self.location,
|
||||||
old_location.archetype_id,
|
|
||||||
old_location,
|
old_location,
|
||||||
entities,
|
bundle_info,
|
||||||
archetypes,
|
archetypes,
|
||||||
storages,
|
storages,
|
||||||
new_archetype_id,
|
components,
|
||||||
|
entities,
|
||||||
|
removed_components,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes any components except those in the [`Bundle`] from the entity.
|
||||||
|
pub fn retain<T: Bundle>(&mut self) -> &mut Self {
|
||||||
|
let archetypes = &mut self.world.archetypes;
|
||||||
|
let storages = &mut self.world.storages;
|
||||||
|
let components = &mut self.world.components;
|
||||||
|
let entities = &mut self.world.entities;
|
||||||
|
let removed_components = &mut self.world.removed_components;
|
||||||
|
|
||||||
|
let retained_bundle_info = self.world.bundles.init_info::<T>(components, storages);
|
||||||
|
let old_location = self.location;
|
||||||
|
let old_archetype = &mut archetypes[old_location.archetype_id];
|
||||||
|
|
||||||
|
let to_remove = &old_archetype
|
||||||
|
.components()
|
||||||
|
.filter(|c| !retained_bundle_info.components().contains(c))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let remove_bundle_info = self
|
||||||
|
.world
|
||||||
|
.bundles
|
||||||
|
.init_dynamic_info(components, to_remove)
|
||||||
|
.0;
|
||||||
|
|
||||||
|
// SAFETY: Components exist in `remove_bundle_info` because `Bundles::init_dynamic_info`
|
||||||
|
// initializes a `BundleInfo` containing all components in the to_remove Bundle.
|
||||||
|
unsafe {
|
||||||
|
Self::remove_bundle_info(
|
||||||
|
self.entity,
|
||||||
|
&mut self.location,
|
||||||
|
old_location,
|
||||||
|
remove_bundle_info,
|
||||||
|
archetypes,
|
||||||
|
storages,
|
||||||
|
components,
|
||||||
|
entities,
|
||||||
|
removed_components,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1775,6 +1845,43 @@ mod tests {
|
||||||
assert_eq!(world.entity(e2).get::<Dense>().unwrap(), &Dense(1));
|
assert_eq!(world.entity(e2).get::<Dense>().unwrap(), &Dense(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test that calling retain with `()` removes all components.
|
||||||
|
#[test]
|
||||||
|
fn retain_nothing() {
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Marker<const N: usize>;
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
let ent = world.spawn((Marker::<1>, Marker::<2>, Marker::<3>)).id();
|
||||||
|
|
||||||
|
world.entity_mut(ent).retain::<()>();
|
||||||
|
assert_eq!(world.entity(ent).archetype().components().next(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing some components with `retain`, including components not on the entity.
|
||||||
|
#[test]
|
||||||
|
fn retain_some_components() {
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Marker<const N: usize>;
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
let ent = world.spawn((Marker::<1>, Marker::<2>, Marker::<3>)).id();
|
||||||
|
|
||||||
|
world.entity_mut(ent).retain::<(Marker<2>, Marker<4>)>();
|
||||||
|
// Check that marker 2 was retained.
|
||||||
|
assert!(world.entity(ent).get::<Marker<2>>().is_some());
|
||||||
|
// Check that only marker 2 was retained.
|
||||||
|
assert_eq!(
|
||||||
|
world
|
||||||
|
.entity(ent)
|
||||||
|
.archetype()
|
||||||
|
.components()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.len(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// regression test for https://github.com/bevyengine/bevy/pull/7805
|
// regression test for https://github.com/bevyengine/bevy/pull/7805
|
||||||
#[test]
|
#[test]
|
||||||
fn inserting_sparse_updates_archetype_row() {
|
fn inserting_sparse_updates_archetype_row() {
|
||||||
|
|
Loading…
Reference in a new issue