Skip to content

Add insert and remove recursive methods on EntityWorldMut and EntityCommands #17463

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

Merged
Merged
Changes from all commits
Commits
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
132 changes: 132 additions & 0 deletions crates/bevy_ecs/src/relationship/related_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
system::{Commands, EntityCommands},
world::{EntityWorldMut, World},
};
use alloc::vec::Vec;
use core::marker::PhantomData;

impl<'w> EntityWorldMut<'w> {
Expand Down Expand Up @@ -45,6 +46,55 @@ 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up: I reckon a detect_cyclic_traversal debug feature could be nice. Internally just keep an EntityHashSet and panic! when a cycle is detected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. There's a ton of methods that could use this.

// 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<S: RelationshipTarget>(
&mut self,
bundle: impl Bundle + Clone,
) -> &mut Self {
self.insert(bundle.clone());
if let Some(relationship_target) = self.get::<S>() {
let related_vec: Vec<Entity> = relationship_target.iter().collect();
for related in related_vec {
self.world_scope(|world| {
world
.entity_mut(related)
.insert_recursive::<S>(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<S: RelationshipTarget, B: Bundle>(&mut self) -> &mut Self {
self.remove::<B>();
if let Some(relationship_target) = self.get::<S>() {
let related_vec: Vec<Entity> = relationship_target.iter().collect();
for related in related_vec {
self.world_scope(|world| {
world.entity_mut(related).remove_recursive::<S, B>();
});
}
}

self
}
}

impl<'a> EntityCommands<'a> {
Expand Down Expand Up @@ -79,6 +129,39 @@ 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<S: RelationshipTarget>(
&mut self,
bundle: impl Bundle + Clone,
) -> &mut Self {
let id = self.id();
self.commands.queue(move |world: &mut World| {
world.entity_mut(id).insert_recursive::<S>(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<S: RelationshipTarget, B: Bundle>(&mut self) -> &mut Self {
let id = self.id();
self.commands.queue(move |world: &mut World| {
world.entity_mut(id).remove_recursive::<S, B>();
});
self
}
}

/// Directly spawns related "source" entities with the given [`Relationship`], targeting
Expand Down Expand Up @@ -162,3 +245,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::<Children>(TestComponent);

for entity in [a, b, c, d] {
assert!(world.entity(entity).contains::<TestComponent>());
}

world
.entity_mut(b)
.remove_recursive::<Children, TestComponent>();

// Parent
assert!(world.entity(a).contains::<TestComponent>());
// Target
assert!(!world.entity(b).contains::<TestComponent>());
// Sibling
assert!(world.entity(c).contains::<TestComponent>());
// Child
assert!(!world.entity(d).contains::<TestComponent>());

world
.entity_mut(a)
.remove_recursive::<Children, TestComponent>();

for entity in [a, b, c, d] {
assert!(!world.entity(entity).contains::<TestComponent>());
}
}
}
Loading