-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
Support removing a removed Component's required components #15580
Comments
Adding to the milestone because this is necessary for parity with "bundle removals". |
There is an implementation of this here: #15026 |
For a naming suggestion, maybe |
Should this only remove required components that aren't required by other components on the entity? Maybe there should be two versions, a |
…d component (#15026) ## Objective The new Required Components feature (#14791) in Bevy allows spawning a fixed set of components with a single method with cool require macro. However, there's currently no corresponding method to remove all those components together. This makes it challenging to keep insertion and removal code in sync, especially for simple using cases. ```rust #[derive(Component)] #[require(Y)] struct X; #[derive(Component, Default)] struct Y; world.entity_mut(e).insert(X); // Spawns both X and Y world.entity_mut(e).remove::<X>(); world.entity_mut(e).remove::<Y>(); // We need to manually remove dependencies without any sync with the `require` macro ``` ## Solution Simplifies component management by providing operations for removal required components. This PR introduces simple 'footgun' methods to removes all components of this bundle and its required components. Two new methods are introduced: For Commands: ```rust commands.entity(e).remove_with_requires::<B>(); ``` For World: ```rust world.entity_mut(e).remove_with_requires::<B>(); ``` For performance I created new field in Bundels struct. This new field "contributed_bundle_ids" contains cached ids for dynamic bundles constructed from bundle_info.cintributed_components() ## Testing The PR includes three test cases: 1. Removing a single component with requirements using World. 2. Removing a bundle with requirements using World. 3. Removing a single component with requirements using Commands. 4. Removing a single component with **runtime** requirements using Commands These tests ensure the feature works as expected across different scenarios. ## Showcase Example: ```rust use bevy_ecs::prelude::*; #[derive(Component)] #[require(Y)] struct X; #[derive(Component, Default)] #[require(Z)] struct Y; #[derive(Component, Default)] struct Z; #[derive(Component)] struct W; let mut world = World::new(); // Spawn an entity with X, Y, Z, and W components let entity = world.spawn((X, W)).id(); assert!(world.entity(entity).contains::<X>()); assert!(world.entity(entity).contains::<Y>()); assert!(world.entity(entity).contains::<Z>()); assert!(world.entity(entity).contains::<W>()); // Remove X and required components Y, Z world.entity_mut(entity).remove_with_requires::<X>(); assert!(!world.entity(entity).contains::<X>()); assert!(!world.entity(entity).contains::<Y>()); assert!(!world.entity(entity).contains::<Z>()); assert!(world.entity(entity).contains::<W>()); ``` ## Motivation for PR #15580 ## Performance I made simple benchmark ```rust let mut world = World::default(); let entity = world.spawn_empty().id(); let steps = 100_000_000; let start = std::time::Instant::now(); for _ in 0..steps { world.entity_mut(entity).insert(X); world.entity_mut(entity).remove::<(X, Y, Z, W)>(); } let end = std::time::Instant::now(); println!("normal remove: {:?} ", (end - start).as_secs_f32()); println!("one remove: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0); let start = std::time::Instant::now(); for _ in 0..steps { world.entity_mut(entity).insert(X); world.entity_mut(entity).remove_with_requires::<X>(); } let end = std::time::Instant::now(); println!("remove_with_requires: {:?} ", (end - start).as_secs_f32()); println!("one remove_with_requires: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0); ``` Output: CPU: Amd Ryzen 7 2700x ```bash normal remove: 17.36135 one remove: 0.17361348299999999 micros remove_with_requires: 17.534006 one remove_with_requires: 0.17534005400000002 micros ``` NOTE: I didn't find any tests or mechanism in the repository to update BundleInfo after creating new runtime requirements with an existing BundleInfo. So this PR also does not contain such logic. ## Future work (outside this PR) Create cache system for fast removing components in "safe" mode, where "safe" mode is remove only required components that will be no longer required after removing root component. --------- Co-authored-by: a.yamaev <[email protected]> Co-authored-by: Carter Anderson <[email protected]>
…d component (bevyengine#15026) ## Objective The new Required Components feature (bevyengine#14791) in Bevy allows spawning a fixed set of components with a single method with cool require macro. However, there's currently no corresponding method to remove all those components together. This makes it challenging to keep insertion and removal code in sync, especially for simple using cases. ```rust #[derive(Component)] #[require(Y)] struct X; #[derive(Component, Default)] struct Y; world.entity_mut(e).insert(X); // Spawns both X and Y world.entity_mut(e).remove::<X>(); world.entity_mut(e).remove::<Y>(); // We need to manually remove dependencies without any sync with the `require` macro ``` ## Solution Simplifies component management by providing operations for removal required components. This PR introduces simple 'footgun' methods to removes all components of this bundle and its required components. Two new methods are introduced: For Commands: ```rust commands.entity(e).remove_with_requires::<B>(); ``` For World: ```rust world.entity_mut(e).remove_with_requires::<B>(); ``` For performance I created new field in Bundels struct. This new field "contributed_bundle_ids" contains cached ids for dynamic bundles constructed from bundle_info.cintributed_components() ## Testing The PR includes three test cases: 1. Removing a single component with requirements using World. 2. Removing a bundle with requirements using World. 3. Removing a single component with requirements using Commands. 4. Removing a single component with **runtime** requirements using Commands These tests ensure the feature works as expected across different scenarios. ## Showcase Example: ```rust use bevy_ecs::prelude::*; #[derive(Component)] #[require(Y)] struct X; #[derive(Component, Default)] #[require(Z)] struct Y; #[derive(Component, Default)] struct Z; #[derive(Component)] struct W; let mut world = World::new(); // Spawn an entity with X, Y, Z, and W components let entity = world.spawn((X, W)).id(); assert!(world.entity(entity).contains::<X>()); assert!(world.entity(entity).contains::<Y>()); assert!(world.entity(entity).contains::<Z>()); assert!(world.entity(entity).contains::<W>()); // Remove X and required components Y, Z world.entity_mut(entity).remove_with_requires::<X>(); assert!(!world.entity(entity).contains::<X>()); assert!(!world.entity(entity).contains::<Y>()); assert!(!world.entity(entity).contains::<Z>()); assert!(world.entity(entity).contains::<W>()); ``` ## Motivation for PR bevyengine#15580 ## Performance I made simple benchmark ```rust let mut world = World::default(); let entity = world.spawn_empty().id(); let steps = 100_000_000; let start = std::time::Instant::now(); for _ in 0..steps { world.entity_mut(entity).insert(X); world.entity_mut(entity).remove::<(X, Y, Z, W)>(); } let end = std::time::Instant::now(); println!("normal remove: {:?} ", (end - start).as_secs_f32()); println!("one remove: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0); let start = std::time::Instant::now(); for _ in 0..steps { world.entity_mut(entity).insert(X); world.entity_mut(entity).remove_with_requires::<X>(); } let end = std::time::Instant::now(); println!("remove_with_requires: {:?} ", (end - start).as_secs_f32()); println!("one remove_with_requires: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0); ``` Output: CPU: Amd Ryzen 7 2700x ```bash normal remove: 17.36135 one remove: 0.17361348299999999 micros remove_with_requires: 17.534006 one remove_with_requires: 0.17534005400000002 micros ``` NOTE: I didn't find any tests or mechanism in the repository to update BundleInfo after creating new runtime requirements with an existing BundleInfo. So this PR also does not contain such logic. ## Future work (outside this PR) Create cache system for fast removing components in "safe" mode, where "safe" mode is remove only required components that will be no longer required after removing root component. --------- Co-authored-by: a.yamaev <[email protected]> Co-authored-by: Carter Anderson <[email protected]>
…d component (bevyengine#15026) ## Objective The new Required Components feature (bevyengine#14791) in Bevy allows spawning a fixed set of components with a single method with cool require macro. However, there's currently no corresponding method to remove all those components together. This makes it challenging to keep insertion and removal code in sync, especially for simple using cases. ```rust #[derive(Component)] #[require(Y)] struct X; #[derive(Component, Default)] struct Y; world.entity_mut(e).insert(X); // Spawns both X and Y world.entity_mut(e).remove::<X>(); world.entity_mut(e).remove::<Y>(); // We need to manually remove dependencies without any sync with the `require` macro ``` ## Solution Simplifies component management by providing operations for removal required components. This PR introduces simple 'footgun' methods to removes all components of this bundle and its required components. Two new methods are introduced: For Commands: ```rust commands.entity(e).remove_with_requires::<B>(); ``` For World: ```rust world.entity_mut(e).remove_with_requires::<B>(); ``` For performance I created new field in Bundels struct. This new field "contributed_bundle_ids" contains cached ids for dynamic bundles constructed from bundle_info.cintributed_components() ## Testing The PR includes three test cases: 1. Removing a single component with requirements using World. 2. Removing a bundle with requirements using World. 3. Removing a single component with requirements using Commands. 4. Removing a single component with **runtime** requirements using Commands These tests ensure the feature works as expected across different scenarios. ## Showcase Example: ```rust use bevy_ecs::prelude::*; #[derive(Component)] #[require(Y)] struct X; #[derive(Component, Default)] #[require(Z)] struct Y; #[derive(Component, Default)] struct Z; #[derive(Component)] struct W; let mut world = World::new(); // Spawn an entity with X, Y, Z, and W components let entity = world.spawn((X, W)).id(); assert!(world.entity(entity).contains::<X>()); assert!(world.entity(entity).contains::<Y>()); assert!(world.entity(entity).contains::<Z>()); assert!(world.entity(entity).contains::<W>()); // Remove X and required components Y, Z world.entity_mut(entity).remove_with_requires::<X>(); assert!(!world.entity(entity).contains::<X>()); assert!(!world.entity(entity).contains::<Y>()); assert!(!world.entity(entity).contains::<Z>()); assert!(world.entity(entity).contains::<W>()); ``` ## Motivation for PR bevyengine#15580 ## Performance I made simple benchmark ```rust let mut world = World::default(); let entity = world.spawn_empty().id(); let steps = 100_000_000; let start = std::time::Instant::now(); for _ in 0..steps { world.entity_mut(entity).insert(X); world.entity_mut(entity).remove::<(X, Y, Z, W)>(); } let end = std::time::Instant::now(); println!("normal remove: {:?} ", (end - start).as_secs_f32()); println!("one remove: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0); let start = std::time::Instant::now(); for _ in 0..steps { world.entity_mut(entity).insert(X); world.entity_mut(entity).remove_with_requires::<X>(); } let end = std::time::Instant::now(); println!("remove_with_requires: {:?} ", (end - start).as_secs_f32()); println!("one remove_with_requires: {:?} micros", (end - start).as_secs_f64() / steps as f64 * 1_000_000.0); ``` Output: CPU: Amd Ryzen 7 2700x ```bash normal remove: 17.36135 one remove: 0.17361348299999999 micros remove_with_requires: 17.534006 one remove_with_requires: 0.17534005400000002 micros ``` NOTE: I didn't find any tests or mechanism in the repository to update BundleInfo after creating new runtime requirements with an existing BundleInfo. So this PR also does not contain such logic. ## Future work (outside this PR) Create cache system for fast removing components in "safe" mode, where "safe" mode is remove only required components that will be no longer required after removing root component. --------- Co-authored-by: a.yamaev <[email protected]> Co-authored-by: Carter Anderson <[email protected]>
#15026 Merged, closing this as completed. |
Re-opening: I'm hoping to add support for removing required components (well, included components) for some of these relationships in #16267. |
#16267 is much more involved than I anticipated; bumping to 0.16 to avoid last-minute feature crunch. |
What problem does this solve or what need does it fill?
Sometimes it is desirable to remove not just a specific component, but also its required components: #15572 (comment).
What solution would you like?
Add an
entity.remove_with_requires::<SomeComponent>()
API that removes all required components brought in by SomeComponent (recursively).What alternative(s) have you considered?
entity.remove::<SomeComponent>()
, as in many cases this could remove components that are required by other components. If we want to go this route, we need to consider building some sort of reference counting system to track requires across components. But there are still problems there (ex: what if someone manually added a component ... how do we distinguish between overriding a required component value and inserting a new separate persistent component).The text was updated successfully, but these errors were encountered: