From 5dcd047c8d00dc074c58aae6d8b6e344f4f9fb7a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 20 Jan 2025 17:32:54 -0800 Subject: [PATCH 1/3] Add `insert_recursive` and `remove_recursive` --- .../src/relationship/related_methods.rs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 4b42709384d1d..2dc1d76ad9e62 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -5,6 +5,7 @@ use crate::{ system::{Commands, EntityCommands}, world::{EntityWorldMut, World}, }; +use alloc::vec::Vec; use core::marker::PhantomData; impl<'w> EntityWorldMut<'w> { @@ -45,6 +46,53 @@ impl<'w> EntityWorldMut<'w> { } self } + + /// Inserts a component or bundle of components into the entity and all related entities, + /// traversing the relationship tracked in `S` in a breadth-first manner. + /// + /// # Warning + /// + /// This method should only be called on relationships that form a tree-like structure. + /// Any cycles will cause this method to loop infinitely. + pub fn insert_recursive( + &mut self, + bundle: B, + ) -> &mut Self { + self.insert(bundle.clone()); + if let Some(relationship_target) = self.get::() { + let related_vec: Vec = relationship_target.iter().collect(); + for related in related_vec { + self.world_scope(|world| { + world + .entity_mut(related) + .insert_recursive::(bundle.clone()); + }); + } + } + + self + } + + /// Removes a component or bundle of components of type `B` from the entity and all related entities, + /// traversing the relationship tracked in `S` in a breadth-first manner. + /// + /// # Warning + /// + /// This method should only be called on relationships that form a tree-like structure. + /// Any cycles will cause this method to loop infinitely. + pub fn remove_recursive(&mut self) -> &mut Self { + self.remove::(); + if let Some(relationship_target) = self.get::() { + let related_vec: Vec = relationship_target.iter().collect(); + for related in related_vec { + self.world_scope(|world| { + world.entity_mut(related).remove_recursive::(); + }); + } + } + + self + } } impl<'a> EntityCommands<'a> { @@ -79,6 +127,38 @@ impl<'a> EntityCommands<'a> { }); self } + + /// Inserts a component or bundle of components into the entity and all related entities, + /// traversing the relationship tracked in `S` in a breadth-first manner. + /// + /// # Warning + /// + /// This method should only be called on relationships that form a tree-like structure. + /// Any cycles will cause this method to loop infinitely. + pub fn insert_recursive( + &mut self, + bundle: B, + ) -> &mut Self { + let id = self.id(); + self.commands.queue(move |world: &mut World| { + world.entity_mut(id).insert_recursive::(bundle); + }); + self + } + + /// Removes a component or bundle of components of type `B` from the entity and all related entities, + /// traversing the relationship tracked in `S` in a breadth-first manner. + /// + /// # Warning + /// This method should only be called on relationships that form a tree-like structure. + /// Any cycles will cause this method to loop infinitely. + pub fn remove_recursive(&mut self) -> &mut Self { + let id = self.id(); + self.commands.queue(move |world: &mut World| { + world.entity_mut(id).remove_recursive::(); + }); + self + } } /// Directly spawns related "source" entities with the given [`Relationship`], targeting From 23c9784a910861c242ddd223789d3918f9f3c7ca Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 20 Jan 2025 17:43:18 -0800 Subject: [PATCH 2/3] Write a test for new methods --- .../src/relationship/related_methods.rs | 63 +++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 2dc1d76ad9e62..bae14d33f1b33 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -54,9 +54,11 @@ impl<'w> EntityWorldMut<'w> { /// /// This method should only be called on relationships that form a tree-like structure. /// Any cycles will cause this method to loop infinitely. - pub fn insert_recursive( + // We could keep track of a list of visited entities and track cycles, + // but this is not a very well-defined operation (or hard to write) for arbitrary relationships. + pub fn insert_recursive( &mut self, - bundle: B, + bundle: impl Bundle + Clone, ) -> &mut Self { self.insert(bundle.clone()); if let Some(relationship_target) = self.get::() { @@ -65,7 +67,7 @@ impl<'w> EntityWorldMut<'w> { self.world_scope(|world| { world .entity_mut(related) - .insert_recursive::(bundle.clone()); + .insert_recursive::(bundle.clone()); }); } } @@ -135,13 +137,13 @@ impl<'a> EntityCommands<'a> { /// /// This method should only be called on relationships that form a tree-like structure. /// Any cycles will cause this method to loop infinitely. - pub fn insert_recursive( + pub fn insert_recursive( &mut self, - bundle: B, + bundle: impl Bundle + Clone, ) -> &mut Self { let id = self.id(); self.commands.queue(move |world: &mut World| { - world.entity_mut(id).insert_recursive::(bundle); + world.entity_mut(id).insert_recursive::(bundle); }); self } @@ -242,3 +244,52 @@ impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { &mut self.commands } } + +#[cfg(test)] +mod tests { + use super::*; + use crate as bevy_ecs; + use crate::prelude::{ChildOf, Children, Component}; + + #[derive(Component, Clone, Copy)] + struct TestComponent; + + #[test] + fn insert_and_remove_recursive() { + let mut world = World::new(); + + let a = world.spawn_empty().id(); + let b = world.spawn(ChildOf(a)).id(); + let c = world.spawn(ChildOf(a)).id(); + let d = world.spawn(ChildOf(b)).id(); + + world + .entity_mut(a) + .insert_recursive::(TestComponent); + + for entity in [a, b, c, d] { + assert!(world.entity(entity).contains::()); + } + + world + .entity_mut(b) + .remove_recursive::(); + + // Parent + assert!(world.entity(a).contains::()); + // Target + assert!(!world.entity(b).contains::()); + // Sibling + assert!(world.entity(c).contains::()); + // Child + assert!(!world.entity(d).contains::()); + + world + .entity_mut(a) + .remove_recursive::(); + + for entity in [a, b, c, d] { + assert!(!world.entity(entity).contains::()); + } + } +} From 70883ea4630eaef9aed3d0642b018e65d15255dc Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 20 Jan 2025 20:59:25 -0500 Subject: [PATCH 3/3] Newline in docs T_T Co-authored-by: Zachary Harrold --- crates/bevy_ecs/src/relationship/related_methods.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index bae14d33f1b33..ea8bfb979fd77 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -152,6 +152,7 @@ impl<'a> EntityCommands<'a> { /// traversing the relationship tracked in `S` in a breadth-first manner. /// /// # Warning + /// /// This method should only be called on relationships that form a tree-like structure. /// Any cycles will cause this method to loop infinitely. pub fn remove_recursive(&mut self) -> &mut Self {