-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplifies component management by providing symmetrical operations for removal required components.
These are not entirely symmetrical, since if X
requires Y
and Y
is already present then inserting X
will not replace Y
, but later removing X
will remove Y
as well.
In other words .insert(X).remove_with_required::<X>()
is not a guaranteed noop.
I think this is fine, since there's probably no way to avoid this behaviour, but I think it should be mentioned in the documentation for remove_with_required
as a potential pitfall.
I'm concerned this might be too foot-gunny in the case of X and Y both requiring Z and then X is removed, leaving Y without Z. |
I agree with @tim-blackbird; there's a serious risk that this ends up removing e.g. |
Thank you all. I have read the comments and agree with all of them. I will then try to re-create the method to take into account the dependency tree of components on each other. And improve the documentation. |
@alice-i-cecile @tim-blackbird @SkiFire13 I created component require safe version of remove_with_required function, updated PR description and improved docs. I would appreciate it if you could look at the PR again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you try using this PR to clean up the mess left behind in #15572 (comment) ? :)
Sure! I can do it in another PR after merging this one, right?) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm on board for this approach, but I have a couple of optimization suggestions
Thanks for the review! All remarks were justified and fixed |
…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]>
I wrote logic to handle enabling and disabling components with dependencies which may be useful as a reference, based on how package systems typically work:
The logic is in |
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.
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:
For World:
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:
These tests ensure the feature works as expected across different scenarios.
Showcase
Example:
Motivation for PR
#15580
Performance
I made simple benchmark
Output:
CPU: Amd Ryzen 7 2700x
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.