Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add method to remove component and all required components for removed component #15026

Merged
merged 31 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cf44495
Remove with required
rewin123 Sep 3, 2024
8c0dde9
fmt
rewin123 Sep 3, 2024
bcd8c33
comments
rewin123 Sep 3, 2024
404ede4
comments
rewin123 Sep 3, 2024
4819ccf
Safe removing with requirements
rewin123 Sep 4, 2024
48065c8
docs for entity_ref remove_with_required
rewin123 Sep 4, 2024
665f0a1
Comments for remove_with_required in EntityCommands
rewin123 Sep 4, 2024
282b46f
Fix typos and clippy
rewin123 Sep 4, 2024
88a168e
Merge remote-tracking branch 'upstream/main' into remove_with_required
rewin123 Sep 4, 2024
98a8ffc
fix typos
rewin123 Sep 4, 2024
febd971
add recursive and descendal strategy for remove
rewin123 Sep 4, 2024
aa9ae94
Fix clippy
rewin123 Sep 4, 2024
0796471
Merge remote-tracking branch 'upstream/main' into remove_with_required
ayamaev-se Oct 2, 2024
4359dbe
remove safe remove
ayamaev-se Oct 2, 2024
79bbf0a
restore footgun solution
rewin123 Oct 2, 2024
cef2fcb
Merge remote-tracking branch 'upstream/main' into remove_with_required
rewin123 Oct 2, 2024
ea4a152
Update to latest main api
rewin123 Oct 2, 2024
7695016
rename remove_with_required into remove_with_requires
rewin123 Oct 2, 2024
fd2ea81
fix docs and namings
rewin123 Oct 2, 2024
b7caaa8
cache system for required components
rewin123 Oct 2, 2024
c6a953c
fix caching
rewin123 Oct 2, 2024
881c0f7
Merge remote-tracking branch 'upstream/main' into remove_with_required
rewin123 Oct 2, 2024
627a4e0
change code to last api
rewin123 Oct 2, 2024
1d3a205
Some more docs
rewin123 Oct 2, 2024
e3dcbe2
fix typos
rewin123 Oct 2, 2024
849e2c7
Merge branch 'main' into remove_with_required
rewin123 Oct 2, 2024
644f8ec
Optimizing from cart
rewin123 Oct 2, 2024
8aa10c4
Merge remote-tracking branch 'upstream/main' into remove_with_required
rewin123 Oct 2, 2024
567c23e
Fix warnings
rewin123 Oct 2, 2024
73b73c2
fix typos
rewin123 Oct 2, 2024
829e849
fix weird std to core replacement error
rewin123 Oct 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,8 @@ pub struct Bundles {
bundle_infos: Vec<BundleInfo>,
/// Cache static [`BundleId`]
bundle_ids: TypeIdMap<BundleId>,
/// Cache bundles, which contains both explicit and required components of [`Bundle`]
contributed_bundle_ids: TypeIdMap<BundleId>,
/// Cache dynamic [`BundleId`] with multiple components
dynamic_bundle_ids: HashMap<Box<[ComponentId]>, BundleId>,
dynamic_bundle_storages: HashMap<BundleId, Vec<StorageType>>,
Expand Down Expand Up @@ -1335,6 +1337,7 @@ impl Bundles {
storages: &mut Storages,
) -> BundleId {
let bundle_infos = &mut self.bundle_infos;
let contributed_bundle_ids = &mut self.contributed_bundle_ids;
let id = *self.bundle_ids.entry(TypeId::of::<T>()).or_insert_with(|| {
let mut component_ids= Vec::new();
T::component_ids(components, storages, &mut |id| component_ids.push(id));
Expand All @@ -1346,11 +1349,39 @@ impl Bundles {
// - it was created in the same order as the components in T
unsafe { BundleInfo::new(core::any::type_name::<T>(), components, component_ids, id) };
bundle_infos.push(bundle_info);

// Be sure to update component bundle with requires
contributed_bundle_ids.remove(&TypeId::of::<T>());

rewin123 marked this conversation as resolved.
Show resolved Hide resolved
id
});
id
}

/// Registers a new [`BundleInfo`], which contains both explicit and required components for a statically known type.
///
/// Also registers all the components in the bundle.
pub(crate) fn register_contributed_bundle_info<T: Bundle>(
&mut self,
components: &mut Components,
storages: &mut Storages,
) -> BundleId {
if let Some(id) = self.contributed_bundle_ids.get(&TypeId::of::<T>()).cloned() {
id
} else {
let explicit_bundle_id = self.register_info::<T>(components, storages);
// SAFETY: `explicit_bundle_id` is valid and defined above
let contributed_components = unsafe {
self.get_unchecked(explicit_bundle_id)
.contributed_components()
.to_vec()
};
let id = self.init_dynamic_info(components, &contributed_components);
rewin123 marked this conversation as resolved.
Show resolved Hide resolved
self.contributed_bundle_ids.insert(TypeId::of::<T>(), id);
id
}
}

/// # Safety
/// A [`BundleInfo`] with the given [`BundleId`] must have been initialized for this instance of `Bundles`.
pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo {
Expand Down
132 changes: 132 additions & 0 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2029,6 +2029,138 @@ mod tests {
assert!(e.contains::<Y>());
}

#[test]
fn remove_component_and_his_runtime_required_components() {
#[derive(Component)]
struct X;

#[derive(Component, Default)]
struct Y;

#[derive(Component, Default)]
struct Z;

#[derive(Component)]
struct V;

let mut world = World::new();
world.register_required_components::<X, Y>();
world.register_required_components::<Y, Z>();

let e = world.spawn((X, V)).id();
assert!(world.entity(e).contains::<X>());
assert!(world.entity(e).contains::<Y>());
assert!(world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<V>());

//check that `remove` works as expected
world.entity_mut(e).remove::<X>();
assert!(!world.entity(e).contains::<X>());
assert!(world.entity(e).contains::<Y>());
assert!(world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<V>());

world.entity_mut(e).insert(X);
assert!(world.entity(e).contains::<X>());
assert!(world.entity(e).contains::<Y>());
assert!(world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<V>());

//remove `X` again and ensure that `Y` and `Z` was removed too
world.entity_mut(e).remove_with_requires::<X>();
assert!(!world.entity(e).contains::<X>());
assert!(!world.entity(e).contains::<Y>());
assert!(!world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<V>());
}

#[test]
fn remove_component_and_his_required_components() {
#[derive(Component)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
#[require(Z)]
struct Y;

#[derive(Component, Default)]
struct Z;

#[derive(Component)]
struct V;

let mut world = World::new();

let e = world.spawn((X, V)).id();
assert!(world.entity(e).contains::<X>());
assert!(world.entity(e).contains::<Y>());
assert!(world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<V>());

//check that `remove` works as expected
world.entity_mut(e).remove::<X>();
assert!(!world.entity(e).contains::<X>());
assert!(world.entity(e).contains::<Y>());
assert!(world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<V>());

world.entity_mut(e).insert(X);
assert!(world.entity(e).contains::<X>());
assert!(world.entity(e).contains::<Y>());
assert!(world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<V>());

//remove `X` again and ensure that `Y` and `Z` was removed too
world.entity_mut(e).remove_with_requires::<X>();
assert!(!world.entity(e).contains::<X>());
assert!(!world.entity(e).contains::<Y>());
assert!(!world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<V>());
}

#[test]
fn remove_bundle_and_his_required_components() {
#[derive(Component, Default)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
struct Y;

#[derive(Component, Default)]
#[require(W)]
struct Z;

#[derive(Component, Default)]
struct W;

#[derive(Component)]
struct V;

#[derive(Bundle, Default)]
struct TestBundle {
x: X,
z: Z,
}

let mut world = World::new();
let e = world.spawn((TestBundle::default(), V)).id();

assert!(world.entity(e).contains::<X>());
assert!(world.entity(e).contains::<Y>());
assert!(world.entity(e).contains::<Z>());
assert!(world.entity(e).contains::<W>());
assert!(world.entity(e).contains::<V>());

world.entity_mut(e).remove_with_requires::<TestBundle>();
assert!(!world.entity(e).contains::<X>());
assert!(!world.entity(e).contains::<Y>());
assert!(!world.entity(e).contains::<Z>());
assert!(!world.entity(e).contains::<W>());
assert!(world.entity(e).contains::<V>());
}

#[test]
fn runtime_required_components() {
// Same as `required_components` test but with runtime registration
Expand Down
71 changes: 71 additions & 0 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,34 @@ impl EntityCommands<'_> {
self.queue(remove::<T>)
}

/// Removes all components in the [`Bundle`] components and remove all required components for each component in the [`Bundle`] from entity.
///
/// # Example
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// #[derive(Component)]
/// #[require(B)]
/// struct A;
/// #[derive(Component, Default)]
/// struct B;
///
/// #[derive(Resource)]
/// struct PlayerEntity { entity: Entity }
///
/// fn remove_with_requires_system(mut commands: Commands, player: Res<PlayerEntity>) {
/// commands
/// .entity(player.entity)
/// // Remove both A and B components from the entity, because B is required by A
/// .remove_with_requires::<A>();
/// }
/// # bevy_ecs::system::assert_is_system(remove_with_requires_system);
/// ```
pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self {
self.queue(remove_with_requires::<T>)
}

/// Removes a component from the entity.
pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self {
self.queue(remove_by_id(component_id))
Expand Down Expand Up @@ -1803,6 +1831,13 @@ fn remove_by_id(component_id: ComponentId) -> impl EntityCommand {
}
}

/// An [`EntityCommand`] that remove all components in the bundle and remove all required components for each component in the bundle.
fn remove_with_requires<T: Bundle>(entity: Entity, world: &mut World) {
if let Some(mut entity) = world.get_entity_mut(entity) {
entity.remove_with_requires::<T>();
}
}

/// An [`EntityCommand`] that removes all components associated with a provided entity.
fn clear() -> impl EntityCommand {
move |entity: Entity, world: &mut World| {
Expand Down Expand Up @@ -2167,6 +2202,42 @@ mod tests {
assert!(world.contains_resource::<W<f64>>());
}

#[test]
fn remove_component_with_required_components() {
#[derive(Component)]
#[require(Y)]
struct X;

#[derive(Component, Default)]
struct Y;

#[derive(Component)]
struct Z;

let mut world = World::default();
let mut queue = CommandQueue::default();
let e = {
let mut commands = Commands::new(&mut queue, &world);
commands.spawn((X, Z)).id()
};
queue.apply(&mut world);

assert!(world.get::<Y>(e).is_some());
assert!(world.get::<X>(e).is_some());
assert!(world.get::<Z>(e).is_some());

{
let mut commands = Commands::new(&mut queue, &world);
commands.entity(e).remove_with_requires::<X>();
}
queue.apply(&mut world);

assert!(world.get::<Y>(e).is_none());
assert!(world.get::<X>(e).is_none());

assert!(world.get::<Z>(e).is_some());
}

fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}

Expand Down
14 changes: 14 additions & 0 deletions crates/bevy_ecs/src/world/entity_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,20 @@ impl<'w> EntityWorldMut<'w> {
self
}

/// Removes all components in the [`Bundle`] and remove all required components for each component in the bundle
pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self {
let storages = &mut self.world.storages;
let components = &mut self.world.components;
let bundles = &mut self.world.bundles;

let bundle_id = bundles.register_contributed_bundle_info::<T>(components, storages);

// SAFETY: the dynamic `BundleInfo` is initialized above
self.location = unsafe { self.remove_bundle(bundle_id) };

self
}

/// Removes any components except those in the [`Bundle`] (and its Required Components) from the entity.
///
/// See [`EntityCommands::retain`](crate::system::EntityCommands::retain) for more details.
Expand Down