From 4d76fd34a663d48b2cb4978e2a229ac6875ad21e Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 3 Oct 2024 18:18:35 +1000 Subject: [PATCH 01/25] Use `ComponentHooks` for `Parent` & `Children` Added new generalised relationship components for One-to-One and One-to-Many relationships. `Parent` and `Children` are now a particular case of a One-to-Many relationship (`OneToManyOne` and `OneToManyMany` respectively). With the hooks, users are able to add or remove these components from the ECS while ensuring the appropriate invariants are upheld _at the next sync point_. This is crucial because hooks can _not_ insert or remove these relationship components immediately, they must be deferred. Since the current system requires users to use builder methods and commands anyway, this doesn't change the status quo negatively. --- crates/bevy_hierarchy/src/child_builder.rs | 503 ++++++------------ .../bevy_hierarchy/src/components/children.rs | 168 ------ crates/bevy_hierarchy/src/components/mod.rs | 5 - .../bevy_hierarchy/src/components/parent.rs | 91 ---- crates/bevy_hierarchy/src/events.rs | 31 -- crates/bevy_hierarchy/src/family.rs | 40 ++ crates/bevy_hierarchy/src/hierarchy.rs | 27 +- crates/bevy_hierarchy/src/lib.rs | 15 +- .../bevy_hierarchy/src/one_to_many/event.rs | 46 ++ crates/bevy_hierarchy/src/one_to_many/many.rs | 218 ++++++++ crates/bevy_hierarchy/src/one_to_many/mod.rs | 178 +++++++ crates/bevy_hierarchy/src/one_to_many/one.rs | 200 +++++++ .../src/one_to_one/component.rs | 188 +++++++ crates/bevy_hierarchy/src/one_to_one/event.rs | 46 ++ crates/bevy_hierarchy/src/one_to_one/mod.rs | 215 ++++++++ 15 files changed, 1329 insertions(+), 642 deletions(-) delete mode 100644 crates/bevy_hierarchy/src/components/children.rs delete mode 100644 crates/bevy_hierarchy/src/components/mod.rs delete mode 100644 crates/bevy_hierarchy/src/components/parent.rs delete mode 100644 crates/bevy_hierarchy/src/events.rs create mode 100644 crates/bevy_hierarchy/src/family.rs create mode 100644 crates/bevy_hierarchy/src/one_to_many/event.rs create mode 100644 crates/bevy_hierarchy/src/one_to_many/many.rs create mode 100644 crates/bevy_hierarchy/src/one_to_many/mod.rs create mode 100644 crates/bevy_hierarchy/src/one_to_many/one.rs create mode 100644 crates/bevy_hierarchy/src/one_to_one/component.rs create mode 100644 crates/bevy_hierarchy/src/one_to_one/event.rs create mode 100644 crates/bevy_hierarchy/src/one_to_one/mod.rs diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 47c74bb4f6cdc..03ac2e9dc9ed5 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -1,163 +1,11 @@ -use crate::{Children, HierarchyEvent, Parent}; +use crate::{Children, Parent}; use bevy_ecs::{ bundle::Bundle, entity::Entity, - prelude::Events, system::{Commands, EntityCommands}, world::{Command, EntityWorldMut, World}, }; -use smallvec::{smallvec, SmallVec}; - -// Do not use `world.send_event_batch` as it prints error message when the Events are not available in the world, -// even though it's a valid use case to execute commands on a world without events. Loading a GLTF file for example -fn push_events(world: &mut World, events: impl IntoIterator) { - if let Some(mut moved) = world.get_resource_mut::>() { - moved.extend(events); - } -} - -/// Adds `child` to `parent`'s [`Children`], without checking if it is already present there. -/// -/// This might cause unexpected results when removing duplicate children. -fn add_child_unchecked(world: &mut World, parent: Entity, child: Entity) { - let mut parent = world.entity_mut(parent); - if let Some(mut children) = parent.get_mut::() { - children.0.push(child); - } else { - parent.insert(Children(smallvec![child])); - } -} - -/// Sets [`Parent`] of the `child` to `new_parent`. Inserts [`Parent`] if `child` doesn't have one. -fn update_parent(world: &mut World, child: Entity, new_parent: Entity) -> Option { - let mut child = world.entity_mut(child); - if let Some(mut parent) = child.get_mut::() { - let previous = parent.0; - *parent = Parent(new_parent); - Some(previous) - } else { - child.insert(Parent(new_parent)); - None - } -} - -/// Remove child from the parent's [`Children`] component. -/// -/// Removes the [`Children`] component from the parent if it's empty. -fn remove_from_children(world: &mut World, parent: Entity, child: Entity) { - let Some(mut parent) = world.get_entity_mut(parent) else { - return; - }; - let Some(mut children) = parent.get_mut::() else { - return; - }; - children.0.retain(|x| *x != child); - if children.is_empty() { - parent.remove::(); - } -} - -/// Update the [`Parent`] component of the `child`. -/// Removes the `child` from the previous parent's [`Children`]. -/// -/// Does not update the new parents [`Children`] component. -/// -/// Does nothing if `child` was already a child of `parent`. -/// -/// Sends [`HierarchyEvent`]'s. -fn update_old_parent(world: &mut World, child: Entity, parent: Entity) { - let previous = update_parent(world, child, parent); - if let Some(previous_parent) = previous { - // Do nothing if the child was already parented to this entity. - if previous_parent == parent { - return; - } - remove_from_children(world, previous_parent, child); - - push_events( - world, - [HierarchyEvent::ChildMoved { - child, - previous_parent, - new_parent: parent, - }], - ); - } else { - push_events(world, [HierarchyEvent::ChildAdded { child, parent }]); - } -} - -/// Update the [`Parent`] components of the `children`. -/// Removes the `children` from their previous parent's [`Children`]. -/// -/// Does not update the new parents [`Children`] component. -/// -/// Does nothing for a child if it was already a child of `parent`. -/// -/// Sends [`HierarchyEvent`]'s. -fn update_old_parents(world: &mut World, parent: Entity, children: &[Entity]) { - let mut events: SmallVec<[HierarchyEvent; 8]> = SmallVec::with_capacity(children.len()); - for &child in children { - if let Some(previous) = update_parent(world, child, parent) { - // Do nothing if the entity already has the correct parent. - if parent == previous { - continue; - } - - remove_from_children(world, previous, child); - events.push(HierarchyEvent::ChildMoved { - child, - previous_parent: previous, - new_parent: parent, - }); - } else { - events.push(HierarchyEvent::ChildAdded { child, parent }); - } - } - push_events(world, events); -} - -/// Removes entities in `children` from `parent`'s [`Children`], removing the component if it ends up empty. -/// Also removes [`Parent`] component from `children`. -fn remove_children(parent: Entity, children: &[Entity], world: &mut World) { - let mut events: SmallVec<[HierarchyEvent; 8]> = SmallVec::new(); - if let Some(parent_children) = world.get::(parent) { - for &child in children { - if parent_children.contains(&child) { - events.push(HierarchyEvent::ChildRemoved { child, parent }); - } - } - } else { - return; - } - for event in &events { - if let &HierarchyEvent::ChildRemoved { child, .. } = event { - world.entity_mut(child).remove::(); - } - } - push_events(world, events); - - let mut parent = world.entity_mut(parent); - if let Some(mut parent_children) = parent.get_mut::() { - parent_children - .0 - .retain(|parent_child| !children.contains(parent_child)); - - if parent_children.is_empty() { - parent.remove::(); - } - } -} - -/// Removes all children from `parent` by removing its [`Children`] component, as well as removing -/// [`Parent`] component from its children. -fn clear_children(parent: Entity, world: &mut World) { - if let Some(children) = world.entity_mut(parent).take::() { - for &child in &children.0 { - world.entity_mut(child).remove::(); - } - } -} +use smallvec::SmallVec; /// Command that adds a child to an entity. #[derive(Debug)] @@ -211,7 +59,17 @@ pub struct RemoveChildren { impl Command for RemoveChildren { fn apply(self, world: &mut World) { - remove_children(self.parent, &self.children, world); + for child in self.children { + let Some(mut child) = world.get_entity_mut(child) else { + continue; + }; + if child + .get::() + .is_some_and(|parent| parent.get() == self.parent) + { + child.remove::(); + } + } } } @@ -223,7 +81,9 @@ pub struct ClearChildren { impl Command for ClearChildren { fn apply(self, world: &mut World) { - clear_children(self.parent, world); + if let Some(mut parent) = world.get_entity_mut(self.parent) { + parent.remove::(); + } } } @@ -235,8 +95,17 @@ pub struct ReplaceChildren { impl Command for ReplaceChildren { fn apply(self, world: &mut World) { - clear_children(self.parent, world); - world.entity_mut(self.parent).add_children(&self.children); + if let Some(mut parent) = world.get_entity_mut(self.parent) { + parent.remove::(); + } + + world.flush(); + + for child in self.children { + if let Some(mut child) = world.get_entity_mut(child) { + child.insert(Parent::new(self.parent)); + } + } } } @@ -544,28 +413,12 @@ impl ChildBuild for WorldChildBuilder<'_> { Self: 'a; fn spawn(&mut self, bundle: impl Bundle) -> EntityWorldMut { - let entity = self.world.spawn((bundle, Parent(self.parent))).id(); - add_child_unchecked(self.world, self.parent, entity); - push_events( - self.world, - [HierarchyEvent::ChildAdded { - child: entity, - parent: self.parent, - }], - ); + let entity = self.world.spawn((bundle, Parent::new(self.parent))).id(); self.world.entity_mut(entity) } fn spawn_empty(&mut self) -> EntityWorldMut { - let entity = self.world.spawn(Parent(self.parent)).id(); - add_child_unchecked(self.world, self.parent, entity); - push_events( - self.world, - [HierarchyEvent::ChildAdded { - child: entity, - parent: self.parent, - }], - ); + let entity = self.world.spawn(Parent::new(self.parent)).id(); self.world.entity_mut(entity) } @@ -584,38 +437,41 @@ impl BuildChildren for EntityWorldMut<'_> { fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self { let parent = self.id(); + self.world_scope(|world| { spawn_children(&mut WorldChildBuilder { world, parent }); }); + + self.world_scope(World::flush); + self } fn with_child(&mut self, bundle: B) -> &mut Self { let parent = self.id(); - let child = self.world_scope(|world| world.spawn((bundle, Parent(parent))).id()); - if let Some(mut children_component) = self.get_mut::() { - children_component.0.retain(|value| child != *value); - children_component.0.push(child); - } else { - self.insert(Children::from_entities(&[child])); - } + + self.world_scope(|world| world.spawn((bundle, Parent::new(parent))).id()); + + self.world_scope(World::flush); + self } fn add_child(&mut self, child: Entity) -> &mut Self { let parent = self.id(); + if child == parent { panic!("Cannot add entity as a child of itself."); } + self.world_scope(|world| { - update_old_parent(world, child, parent); + if let Some(mut child) = world.get_entity_mut(child) { + child.insert(Parent::new(parent)); + } }); - if let Some(mut children_component) = self.get_mut::() { - children_component.0.retain(|value| child != *value); - children_component.0.push(child); - } else { - self.insert(Children::from_entities(&[child])); - } + + self.world_scope(World::flush); + self } @@ -628,17 +484,16 @@ impl BuildChildren for EntityWorldMut<'_> { if children.contains(&parent) { panic!("Cannot push entity as a child of itself."); } - self.world_scope(|world| { - update_old_parents(world, parent, children); + self.world_scope(move |world| { + for &child in children { + if let Some(mut child) = world.get_entity_mut(child) { + child.insert(Parent::new(parent)); + } + } }); - if let Some(mut children_component) = self.get_mut::() { - children_component - .0 - .retain(|value| !children.contains(value)); - children_component.0.extend(children.iter().cloned()); - } else { - self.insert(Children::from_entities(children)); - } + + self.world_scope(World::flush); + self } @@ -647,52 +502,74 @@ impl BuildChildren for EntityWorldMut<'_> { if children.contains(&parent) { panic!("Cannot insert entity as a child of itself."); } - self.world_scope(|world| { - update_old_parents(world, parent, children); - }); - if let Some(mut children_component) = self.get_mut::() { - children_component - .0 - .retain(|value| !children.contains(value)); - children_component.0.insert_from_slice(index, children); - } else { - self.insert(Children::from_entities(children)); - } + + let component = self.take::().unwrap_or_default(); + + let iter_first = component.iter().take(index); + let iter_second = component.iter().skip(index); + + let component = iter_first + .chain(children) + .chain(iter_second) + .copied() + .collect::(); + + self.insert(component); + + self.world_scope(World::flush); + self } fn remove_children(&mut self, children: &[Entity]) -> &mut Self { let parent = self.id(); - self.world_scope(|world| { - remove_children(parent, children, world); + + self.world_scope(move |world| { + for &child in children { + if let Some(mut child) = world.get_entity_mut(child) { + if child.get::().is_some_and(|p| p.get() == parent) { + child.remove::(); + } + } + } }); + + self.world_scope(World::flush); + self } fn set_parent(&mut self, parent: Entity) -> &mut Self { let child = self.id(); + self.world_scope(|world| { world.entity_mut(parent).add_child(child); }); + + self.world_scope(World::flush); + self } fn remove_parent(&mut self) -> &mut Self { - let child = self.id(); - if let Some(parent) = self.take::().map(|p| p.get()) { - self.world_scope(|world| { - remove_from_children(world, parent, child); - push_events(world, [HierarchyEvent::ChildRemoved { child, parent }]); - }); - } + self.remove::(); + + self.world_scope(World::flush); + self } fn clear_children(&mut self) -> &mut Self { let parent = self.id(); + self.world_scope(|world| { - clear_children(parent, world); + if let Some(mut parent) = world.get_entity_mut(parent) { + parent.remove::(); + } }); + + self.world_scope(World::flush); + self } @@ -703,11 +580,10 @@ impl BuildChildren for EntityWorldMut<'_> { #[cfg(test)] mod tests { + use core::marker::PhantomData; + use super::{BuildChildren, ChildBuild}; - use crate::{ - components::{Children, Parent}, - HierarchyEvent::{self, ChildAdded, ChildMoved, ChildRemoved}, - }; + use crate::{Children, HierarchyEvent, Parent}; use smallvec::{smallvec, SmallVec}; use bevy_ecs::{ @@ -762,25 +638,13 @@ mod tests { assert_parent(world, b, Some(a)); assert_children(world, a, Some(&[b])); - assert_events( - world, - &[ChildAdded { - child: b, - parent: a, - }], - ); + assert_events(world, &[HierarchyEvent::Added(b, a, PhantomData)]); world.entity_mut(a).add_child(c); assert_children(world, a, Some(&[b, c])); assert_parent(world, c, Some(a)); - assert_events( - world, - &[ChildAdded { - child: c, - parent: a, - }], - ); + assert_events(world, &[HierarchyEvent::Added(c, a, PhantomData)]); // Children component should be removed when it's empty. world.entity_mut(d).add_child(b).add_child(c); assert_children(world, a, None); @@ -797,13 +661,7 @@ mod tests { assert_parent(world, a, Some(b)); assert_children(world, b, Some(&[a])); - assert_events( - world, - &[ChildAdded { - child: a, - parent: b, - }], - ); + assert_events(world, &[HierarchyEvent::Added(a, b, PhantomData)]); world.entity_mut(a).set_parent(c); @@ -812,11 +670,10 @@ mod tests { assert_children(world, c, Some(&[a])); assert_events( world, - &[ChildMoved { - child: a, - previous_parent: b, - new_parent: c, - }], + &[ + HierarchyEvent::Removed(a, b, PhantomData), + HierarchyEvent::Added(a, c, PhantomData), + ], ); } @@ -851,24 +708,12 @@ mod tests { assert_parent(world, c, Some(a)); assert_children(world, a, Some(&[c])); omit_events(world, 2); // Omit ChildAdded events. - assert_events( - world, - &[ChildRemoved { - child: b, - parent: a, - }], - ); + assert_events(world, &[HierarchyEvent::Removed(b, a, PhantomData)]); world.entity_mut(c).remove_parent(); assert_parent(world, c, None); assert_children(world, a, None); - assert_events( - world, - &[ChildRemoved { - child: c, - parent: a, - }], - ); + assert_events(world, &[HierarchyEvent::Removed(c, a, PhantomData)]); } #[allow(dead_code)] @@ -893,14 +738,26 @@ mod tests { queue.apply(&mut world); assert_eq!( - world.get::(parent).unwrap().0.as_slice(), + world.get::(parent).unwrap().as_slice(), children.as_slice(), ); - assert_eq!(*world.get::(children[0]).unwrap(), Parent(parent)); - assert_eq!(*world.get::(children[1]).unwrap(), Parent(parent)); + assert_eq!( + *world.get::(children[0]).unwrap(), + Parent::new(parent) + ); + assert_eq!( + *world.get::(children[1]).unwrap(), + Parent::new(parent) + ); - assert_eq!(*world.get::(children[0]).unwrap(), Parent(parent)); - assert_eq!(*world.get::(children[1]).unwrap(), Parent(parent)); + assert_eq!( + *world.get::(children[0]).unwrap(), + Parent::new(parent) + ); + assert_eq!( + *world.get::(children[1]).unwrap(), + Parent::new(parent) + ); } #[test] @@ -913,7 +770,7 @@ mod tests { commands.entity(parent).with_child(C(2)); queue.apply(&mut world); - assert_eq!(world.get::(parent).unwrap().0.len(), 1); + assert_eq!(world.get::(parent).unwrap().len(), 1); } #[test] @@ -938,14 +795,14 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child1).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child2).unwrap(), Parent::new(parent)); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child1).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child2).unwrap(), Parent::new(parent)); { let mut commands = Commands::new(&mut queue, &world); @@ -955,11 +812,11 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child3, child4, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child3).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child4).unwrap(), Parent::new(parent)); let remove_children = [child1, child4]; { @@ -970,8 +827,8 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child3, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); assert!(world.get::(child1).is_none()); assert!(world.get::(child4).is_none()); @@ -997,11 +854,11 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child1).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child2).unwrap(), Parent::new(parent)); { let mut commands = Commands::new(&mut queue, &world); @@ -1036,11 +893,11 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child1).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child2).unwrap(), Parent::new(parent)); let replace_children = [child1, child4]; { @@ -1051,11 +908,11 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child4]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child1).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child4).unwrap(), Parent::new(parent)); assert!(world.get::(child2).is_none()); } @@ -1076,27 +933,27 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child1).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child2).unwrap(), Parent::new(parent)); world.entity_mut(parent).insert_children(1, &entities[3..]); let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child3, child4, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child3).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child4).unwrap(), Parent::new(parent)); let remove_children = [child1, child4]; world.entity_mut(parent).remove_children(&remove_children); let expected_children: SmallVec<[Entity; 8]> = smallvec![child3, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); assert!(world.get::(child1).is_none()); assert!(world.get::(child4).is_none()); @@ -1117,11 +974,11 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child1).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child2).unwrap(), Parent::new(parent)); world.entity_mut(parent).clear_children(); assert!(world.get::(parent).is_none()); @@ -1146,22 +1003,22 @@ mod tests { let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child2]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child1).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child2).unwrap(), Parent::new(parent)); world.entity_mut(parent).replace_children(&entities[2..]); let expected_children: SmallVec<[Entity; 8]> = smallvec![child2, child3, child4]; assert_eq!( - world.get::(parent).unwrap().0.clone(), - expected_children + world.get::(parent).unwrap().as_slice(), + expected_children.as_slice() ); assert!(world.get::(child1).is_none()); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); + assert_eq!(*world.get::(child2).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child3).unwrap(), Parent::new(parent)); + assert_eq!(*world.get::(child4).unwrap(), Parent::new(parent)); } /// Tests what happens when all children are removed from a parent using world functions @@ -1178,10 +1035,7 @@ mod tests { // add child into parent1 world.entity_mut(parent1).add_children(&[child]); - assert_eq!( - world.get::(parent1).unwrap().0.as_slice(), - &[child] - ); + assert_eq!(world.get::(parent1).unwrap().as_slice(), &[child]); // move only child from parent1 with `add_children` world.entity_mut(parent2).add_children(&[child]); @@ -1216,10 +1070,7 @@ mod tests { commands.entity(parent1).add_children(&[child]); queue.apply(&mut world); } - assert_eq!( - world.get::(parent1).unwrap().0.as_slice(), - &[child] - ); + assert_eq!(world.get::(parent1).unwrap().as_slice(), &[child]); // move only child from parent1 with `add_children` { diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs deleted file mode 100644 index 18ad943ee3637..0000000000000 --- a/crates/bevy_hierarchy/src/components/children.rs +++ /dev/null @@ -1,168 +0,0 @@ -#[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; -use bevy_ecs::{ - component::Component, - entity::{Entity, VisitEntitiesMut}, - prelude::FromWorld, - world::World, -}; -use core::{ops::Deref, slice}; -use smallvec::SmallVec; - -/// Contains references to the child entities of this entity. -/// -/// Each child must contain a [`Parent`] component that points back to this entity. -/// This component rarely needs to be created manually, -/// consider using higher level utilities like [`BuildChildren::with_children`] -/// which are safer and easier to use. -/// -/// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. -/// -/// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt -/// [`Query`]: bevy_ecs::system::Query -/// [`Parent`]: crate::components::parent::Parent -/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children -#[derive(Component, Debug, VisitEntitiesMut)] -#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - Debug, - FromWorld - ) -)] -pub struct Children(pub(crate) SmallVec<[Entity; 8]>); - -// TODO: We need to impl either FromWorld or Default so Children can be registered as Reflect. -// This is because Reflect deserialize by creating an instance and apply a patch on top. -// However Children should only ever be set with a real user-defined entities. Its worth looking -// into better ways to handle cases like this. -impl FromWorld for Children { - #[inline] - fn from_world(_world: &mut World) -> Self { - Children(SmallVec::new()) - } -} - -impl Children { - /// Constructs a [`Children`] component with the given entities. - #[inline] - pub(crate) fn from_entities(entities: &[Entity]) -> Self { - Self(SmallVec::from_slice(entities)) - } - - /// Swaps the child at `a_index` with the child at `b_index`. - #[inline] - pub fn swap(&mut self, a_index: usize, b_index: usize) { - self.0.swap(a_index, b_index); - } - - /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided comparator function. - /// - /// For the underlying implementation, see [`slice::sort_by`]. - /// - /// For the unstable version, see [`sort_unstable_by`](Children::sort_unstable_by). - /// - /// See also [`sort_by_key`](Children::sort_by_key), [`sort_by_cached_key`](Children::sort_by_cached_key). - #[inline] - pub fn sort_by(&mut self, compare: F) - where - F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, - { - self.0.sort_by(compare); - } - - /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. - /// - /// For the underlying implementation, see [`slice::sort_by_key`]. - /// - /// For the unstable version, see [`sort_unstable_by_key`](Children::sort_unstable_by_key). - /// - /// See also [`sort_by`](Children::sort_by), [`sort_by_cached_key`](Children::sort_by_cached_key). - #[inline] - pub fn sort_by_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.0.sort_by_key(compare); - } - - /// Sorts children [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. Only evaluates each key at most - /// once per sort, caching the intermediate results in memory. - /// - /// For the underlying implementation, see [`slice::sort_by_cached_key`]. - /// - /// See also [`sort_by`](Children::sort_by), [`sort_by_key`](Children::sort_by_key). - #[inline] - pub fn sort_by_cached_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.0.sort_by_cached_key(compare); - } - - /// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided comparator function. - /// - /// For the underlying implementation, see [`slice::sort_unstable_by`]. - /// - /// For the stable version, see [`sort_by`](Children::sort_by). - /// - /// See also [`sort_unstable_by_key`](Children::sort_unstable_by_key). - #[inline] - pub fn sort_unstable_by(&mut self, compare: F) - where - F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, - { - self.0.sort_unstable_by(compare); - } - - /// Sorts children [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. - /// - /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. - /// - /// For the stable version, see [`sort_by_key`](Children::sort_by_key). - /// - /// See also [`sort_unstable_by`](Children::sort_unstable_by). - #[inline] - pub fn sort_unstable_by_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.0.sort_unstable_by_key(compare); - } -} - -impl Deref for Children { - type Target = [Entity]; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0[..] - } -} - -impl<'a> IntoIterator for &'a Children { - type Item = ::Item; - - type IntoIter = slice::Iter<'a, Entity>; - - #[inline(always)] - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} diff --git a/crates/bevy_hierarchy/src/components/mod.rs b/crates/bevy_hierarchy/src/components/mod.rs deleted file mode 100644 index 3c8b544850382..0000000000000 --- a/crates/bevy_hierarchy/src/components/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod children; -mod parent; - -pub use children::Children; -pub use parent::Parent; diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs deleted file mode 100644 index b369447734206..0000000000000 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ /dev/null @@ -1,91 +0,0 @@ -#[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; -use bevy_ecs::{ - component::Component, - entity::{Entity, VisitEntities, VisitEntitiesMut}, - traversal::Traversal, - world::{FromWorld, World}, -}; -use core::ops::Deref; - -/// Holds a reference to the parent entity of this entity. -/// This component should only be present on entities that actually have a parent entity. -/// -/// Parent entity must have this entity stored in its [`Children`] component. -/// It is hard to set up parent/child relationships manually, -/// consider using higher level utilities like [`BuildChildren::with_children`]. -/// -/// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. -/// -/// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt -/// [`Query`]: bevy_ecs::system::Query -/// [`Children`]: super::children::Children -/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children -#[derive(Component, Debug, Eq, PartialEq, VisitEntities, VisitEntitiesMut)] -#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - PartialEq, - Debug, - FromWorld - ) -)] -pub struct Parent(pub(crate) Entity); - -impl Parent { - /// Gets the [`Entity`] ID of the parent. - #[inline(always)] - pub fn get(&self) -> Entity { - self.0 - } - - /// Gets the parent [`Entity`] as a slice of length 1. - /// - /// Useful for making APIs that require a type or homogeneous storage - /// for both [`Children`] & [`Parent`] that is agnostic to edge direction. - /// - /// [`Children`]: super::children::Children - #[inline(always)] - pub fn as_slice(&self) -> &[Entity] { - core::slice::from_ref(&self.0) - } -} - -// TODO: We need to impl either FromWorld or Default so Parent can be registered as Reflect. -// This is because Reflect deserialize by creating an instance and apply a patch on top. -// However Parent should only ever be set with a real user-defined entity. Its worth looking into -// better ways to handle cases like this. -impl FromWorld for Parent { - #[inline(always)] - fn from_world(_world: &mut World) -> Self { - Parent(Entity::PLACEHOLDER) - } -} - -impl Deref for Parent { - type Target = Entity; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// This provides generalized hierarchy traversal for use in [event propagation]. -/// -/// `Parent::traverse` will never form loops in properly-constructed hierarchies. -/// -/// [event propagation]: bevy_ecs::observer::Trigger::propagate -impl Traversal for &Parent { - fn traverse(item: Self::Item<'_>) -> Option { - Some(item.0) - } -} diff --git a/crates/bevy_hierarchy/src/events.rs b/crates/bevy_hierarchy/src/events.rs deleted file mode 100644 index 8c8263cecfe74..0000000000000 --- a/crates/bevy_hierarchy/src/events.rs +++ /dev/null @@ -1,31 +0,0 @@ -use bevy_ecs::{event::Event, prelude::Entity}; - -/// An [`Event`] that is fired whenever there is a change in the world's hierarchy. -/// -/// [`Event`]: bevy_ecs::event::Event -#[derive(Event, Debug, Clone, PartialEq, Eq)] -pub enum HierarchyEvent { - /// Fired whenever an [`Entity`] is added as a child to a parent. - ChildAdded { - /// The child that was added - child: Entity, - /// The parent the child was added to - parent: Entity, - }, - /// Fired whenever a child [`Entity`] is removed from its parent. - ChildRemoved { - /// The child that was removed - child: Entity, - /// The parent the child was removed from - parent: Entity, - }, - /// Fired whenever a child [`Entity`] is moved to a new parent. - ChildMoved { - /// The child that was moved - child: Entity, - /// The parent the child was removed from - previous_parent: Entity, - /// The parent the child was added to - new_parent: Entity, - }, -} diff --git a/crates/bevy_hierarchy/src/family.rs b/crates/bevy_hierarchy/src/family.rs new file mode 100644 index 0000000000000..83d769645cee2 --- /dev/null +++ b/crates/bevy_hierarchy/src/family.rs @@ -0,0 +1,40 @@ +use crate::{OneToManyEvent, OneToManyMany, OneToManyOne}; + +/// A familial relationship +#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] +pub struct Family; + +/// Holds a reference to the parent entity of this entity. +/// This component should only be present on entities that actually have a parent entity. +/// +/// Parent entity must have this entity stored in its [`Children`] component. +/// It is hard to set up parent/child relationships manually, +/// consider using higher level utilities like [`BuildChildren::with_children`]. +/// +/// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. +/// +/// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt +/// [`Query`]: bevy_ecs::system::Query +/// [`Children`]: super::children::Children +/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children +pub type Parent = OneToManyOne; + +/// Contains references to the child entities of this entity. +/// +/// Each child must contain a [`Parent`] component that points back to this entity. +/// This component rarely needs to be created manually, +/// consider using higher level utilities like [`BuildChildren::with_children`] +/// which are safer and easier to use. +/// +/// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. +/// +/// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt +/// [`Query`]: bevy_ecs::system::Query +/// [`Parent`]: crate::components::parent::Parent +/// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children +pub type Children = OneToManyMany; + +/// An [`Event`] that is fired whenever there is a change in the world's hierarchy. +/// +/// [`Event`]: bevy_ecs::event::Event +pub type HierarchyEvent = OneToManyEvent; diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs index 9f090d8ce4b91..6a5a1dd5c30fb 100644 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ b/crates/bevy_hierarchy/src/hierarchy.rs @@ -1,4 +1,4 @@ -use crate::components::{Children, Parent}; +use crate::Children; use bevy_ecs::{ entity::Entity, system::EntityCommands, @@ -22,21 +22,16 @@ pub struct DespawnChildrenRecursive { /// Function for despawning an entity and all its children pub fn despawn_with_children_recursive(world: &mut World, entity: Entity) { - // first, make the entity's own parent forget about it - if let Some(parent) = world.get::(entity).map(|parent| parent.0) { - if let Some(mut children) = world.get_mut::(parent) { - children.0.retain(|c| *c != entity); - } - } - - // then despawn the entity and all of its children despawn_with_children_recursive_inner(world, entity); + + world.flush(); } // Should only be called by `despawn_with_children_recursive`! fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity) { - if let Some(mut children) = world.get_mut::(entity) { - for e in core::mem::take(&mut children.0) { + if let Some(children) = world.get::(entity) { + let children = children.iter().copied().collect::>(); + for e in children { despawn_with_children_recursive_inner(world, e); } } @@ -48,10 +43,12 @@ fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity) { fn despawn_children_recursive(world: &mut World, entity: Entity) { if let Some(children) = world.entity_mut(entity).take::() { - for e in children.0 { + for &e in children.iter().collect::>() { despawn_with_children_recursive_inner(world, e); } } + + world.flush(); } impl Command for DespawnRecursive { @@ -148,7 +145,7 @@ mod tests { use super::DespawnRecursiveExt; use crate::{ child_builder::{BuildChildren, ChildBuild}, - components::Children, + Children, }; #[derive(Component, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug)] @@ -214,9 +211,9 @@ mod tests { results.sort_unstable_by_key(|(_, index)| *index); { - let children = world.get::(grandparent_entity).unwrap(); + let children = world.get::(grandparent_entity); assert!( - !children.iter().any(|&i| i == parent_entity), + children.is_none(), "grandparent should no longer know about its child which has been removed" ); } diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index ced37bd154f64..82d3270571bad 100755 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -53,8 +53,14 @@ extern crate alloc; -mod components; -pub use components::*; +mod one_to_one; +pub use one_to_one::{OneToOne, OneToOneEvent}; + +mod one_to_many; +pub use one_to_many::{OneToManyEvent, OneToManyMany, OneToManyOne}; + +mod family; +pub use family::*; mod hierarchy; pub use hierarchy::*; @@ -62,9 +68,6 @@ pub use hierarchy::*; mod child_builder; pub use child_builder::*; -mod events; -pub use events::*; - mod valid_parent_check_plugin; pub use valid_parent_check_plugin::*; @@ -76,7 +79,7 @@ pub use query_extension::*; /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { #[doc(hidden)] - pub use crate::{child_builder::*, components::*, hierarchy::*, query_extension::*}; + pub use crate::{child_builder::*, family::*, hierarchy::*, query_extension::*}; #[doc(hidden)] #[cfg(feature = "bevy_app")] diff --git a/crates/bevy_hierarchy/src/one_to_many/event.rs b/crates/bevy_hierarchy/src/one_to_many/event.rs new file mode 100644 index 0000000000000..0fa75cf4e0aa6 --- /dev/null +++ b/crates/bevy_hierarchy/src/one_to_many/event.rs @@ -0,0 +1,46 @@ +use core::marker::PhantomData; + +use bevy_ecs::{entity::Entity, event::Event}; + +/// An [`Event`] that is fired whenever there is a change in [`OneToMany`] relationship `R`. +/// +/// [`Event`]: bevy_ecs::event::Event +#[derive(Event)] +pub enum OneToManyEvent { + /// Fired whenever a [`OneToOne`] relationship of type `R` is added between two [entities](Entity) + Added(Entity, Entity, PhantomData), + /// Fired whenever a [`OneToOne`] relationship of type `R` is remove from two [entities](Entity) + Removed(Entity, Entity, PhantomData), +} + +impl core::fmt::Debug for OneToManyEvent { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Added(arg0, arg1, _) => f.debug_tuple("Added").field(arg0).field(arg1).finish(), + Self::Removed(arg0, arg1, _) => { + f.debug_tuple("Removed").field(arg0).field(arg1).finish() + } + } + } +} + +impl PartialEq for OneToManyEvent { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Added(l0, l1, _), Self::Added(r0, r1, _)) + | (Self::Removed(l0, l1, _), Self::Removed(r0, r1, _)) => l0 == r0 && l1 == r1, + _ => false, + } + } +} + +impl Eq for OneToManyEvent {} + +impl Clone for OneToManyEvent { + fn clone(&self) -> Self { + match self { + Self::Added(arg0, arg1, _) => Self::Added(*arg0, *arg1, PhantomData), + Self::Removed(arg0, arg1, _) => Self::Removed(*arg0, *arg1, PhantomData), + } + } +} diff --git a/crates/bevy_hierarchy/src/one_to_many/many.rs b/crates/bevy_hierarchy/src/one_to_many/many.rs new file mode 100644 index 0000000000000..cfd5eec4e8abe --- /dev/null +++ b/crates/bevy_hierarchy/src/one_to_many/many.rs @@ -0,0 +1,218 @@ +#[cfg(feature = "reflect")] +use bevy_ecs::reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, +}; +use bevy_ecs::{ + component::{Component, ComponentId}, + entity::{Entity, VisitEntitiesMut}, + event::Events, + world::{DeferredWorld, World}, +}; +use core::fmt::Debug; +use core::marker::PhantomData; +use core::ops::Deref; +use smallvec::SmallVec; + +use super::{OneToManyEvent, OneToManyOne}; + +/// Represents one half of a one-to-many relationship between an [`Entity`] and some number of other [entities](Entity). +/// +/// The type of relationship is denoted by the parameter `R`. +#[derive(Component)] +#[component( + on_insert = Self::associate, + on_replace = Self::disassociate, + on_remove = Self::disassociate +)] +#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "reflect", + reflect( + Component, + MapEntities, + VisitEntities, + VisitEntitiesMut, + PartialEq, + Debug, + FromWorld + ) +)] +pub struct OneToManyMany { + entities: SmallVec<[Entity; 8]>, + #[cfg_attr(feature = "reflect", reflect(ignore))] + _phantom: PhantomData, +} + +impl OneToManyMany { + fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + world.commands().queue(move |world: &mut World| { + let b_ids_len = world + .get_entity(a_id) + .and_then(|a| a.get::()) + .map(|a_relationship| a_relationship.entities.len()); + + let Some(b_ids_len) = b_ids_len else { return }; + + for b_id_index in 0..b_ids_len { + let b = world + .get_entity(a_id) + .and_then(|a| a.get::()) + .map(|a_relationship| a_relationship.entities[b_id_index]) + .and_then(|b_id| world.get_entity_mut(b_id)); + + let Some(mut b) = b else { return }; + + let b_id = b.id(); + + let b_points_to_a = b + .get::>() + .is_some_and(|b_relationship| b_relationship.get() == a_id); + + if !b_points_to_a { + b.insert(OneToManyOne::::new(a_id)); + + if let Some(mut moved) = world.get_resource_mut::>>() { + moved.send(OneToManyEvent::::Added(a_id, b_id, PhantomData)); + } + } + } + }); + } + + fn disassociate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + let Some(a_relationship) = world.get::(a_id) else { + unreachable!("component hook should only be called when component is available"); + }; + + // Cloning to allow a user to `take` the component for modification + let b_ids = a_relationship.entities.clone(); + + world.commands().queue(move |world: &mut World| { + for b_id in b_ids { + let a_points_to_b = world + .get_entity(a_id) + .and_then(|a| a.get::()) + .is_some_and(|a_relationship| a_relationship.entities.contains(&b_id)); + + let b_points_to_a = world + .get_entity(b_id) + .and_then(|b| b.get::>()) + .is_some_and(|b_relationship| b_relationship.get() == a_id); + + if b_points_to_a && !a_points_to_b { + if let Some(mut b) = world.get_entity_mut(b_id) { + b.remove::>(); + } + + if let Some(mut moved) = world.get_resource_mut::>>() { + moved.send(OneToManyEvent::::Removed(a_id, b_id, PhantomData)); + } + } + } + }); + } +} + +impl PartialEq for OneToManyMany { + fn eq(&self, other: &Self) -> bool { + self.entities == other.entities + } +} + +impl Eq for OneToManyMany {} + +impl Debug for OneToManyMany { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("OneToManyMany") + .field(&self.entities) + .field(&core::any::type_name::()) + .finish() + } +} + +impl VisitEntitiesMut for OneToManyMany { + fn visit_entities_mut(&mut self, mut f: F) { + for entity in &mut self.entities { + f(entity); + } + } +} + +impl Deref for OneToManyMany { + type Target = [Entity]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.entities + } +} + +impl<'a, R> IntoIterator for &'a OneToManyMany { + type Item = ::Item; + + type IntoIter = core::slice::Iter<'a, Entity>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.entities.iter() + } +} + +impl FromIterator for OneToManyMany { + fn from_iter>(iter: T) -> Self { + Self::from_smallvec(iter.into_iter().collect()) + } +} + +impl Default for OneToManyMany { + fn default() -> Self { + Self::new() + } +} + +impl OneToManyMany { + /// Gets the other [`Entity`] as a slice of length 1. + #[inline(always)] + pub fn as_slice(&self) -> &[Entity] { + &self.entities + } + + /// Create a new relationship. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::from_smallvec(SmallVec::new()) + } + + #[inline(always)] + #[must_use] + fn from_smallvec(entities: SmallVec<[Entity; 8]>) -> Self { + Self { + entities, + _phantom: PhantomData, + } + } + + /// Ensures the provided [`Entity`] is present in this relationship. + #[inline(always)] + #[must_use] + pub fn with(mut self, other: Entity) -> Self { + if !self.entities.contains(&other) { + self.entities.push(other); + } + self + } + + /// Ensures the provided [`Entity`] is _not_ present in this relationship. + #[inline(always)] + #[must_use] + pub fn without(mut self, other: Entity) -> Self { + self.entities.retain(|&mut e| e != other); + self + } + + pub(super) fn entities_mut(&mut self) -> &mut SmallVec<[Entity; 8]> { + &mut self.entities + } +} diff --git a/crates/bevy_hierarchy/src/one_to_many/mod.rs b/crates/bevy_hierarchy/src/one_to_many/mod.rs new file mode 100644 index 0000000000000..fdd77366ce9f0 --- /dev/null +++ b/crates/bevy_hierarchy/src/one_to_many/mod.rs @@ -0,0 +1,178 @@ +mod event; +pub use event::OneToManyEvent; + +mod one; +pub use one::OneToManyOne; + +mod many; +pub use many::OneToManyMany; + +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + + use bevy_ecs::{event::Events, world::World}; + + use super::*; + + /// A familial relationship + struct Family; + + /// Shorthand for a Parent in a Family relationship + type Parent = OneToManyOne; + + /// Shorthand for a Parent in a Family relationship + type Children = OneToManyMany; + + #[test] + fn simple_add_then_remove() { + let mut world = World::new(); + + world.register_component::(); + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Parent::new(a)).id(); + let c = world.spawn(Parent::new(a)).id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b, c]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), Some(&Parent::new(a))); + + world.entity_mut(a).remove::(); + + world.flush(); + + assert_eq!(world.get::(a), None); + assert_eq!(world.get::(b), None); + assert_eq!(world.get::(c), None); + } + + #[test] + fn partial_add_then_remove() { + let mut world = World::new(); + + world.register_component::(); + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Parent::new(a)).id(); + let c = world.spawn(Parent::new(a)).id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b, c]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), Some(&Parent::new(a))); + + world.entity_mut(c).remove::(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), None); + } + + #[test] + fn take_and_return() { + let mut world = World::new(); + + world.register_component::(); + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Parent::new(a)).id(); + let c = world.spawn_empty().id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), None); + + let component = world.entity_mut(a).take::().unwrap(); + + let component = component.with(c); + + world.entity_mut(a).insert(component); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b, c]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), Some(&Parent::new(a))); + } + + #[test] + fn event_testing() { + let mut world = World::new(); + + world.register_component::(); + world.register_component::(); + world.init_resource::>>(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Parent::new(a)).id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + + assert_eq!( + world + .resource_mut::>>() + .drain() + .collect::>(), + vec![OneToManyEvent::::Added(b, a, PhantomData)] + ); + + world.entity_mut(b).remove::(); + + world.flush(); + + assert_eq!(world.get::(a), None); + assert_eq!(world.get::(b), None); + + assert_eq!( + world + .resource_mut::>>() + .drain() + .collect::>(), + vec![OneToManyEvent::::Removed(b, a, PhantomData)] + ); + } +} diff --git a/crates/bevy_hierarchy/src/one_to_many/one.rs b/crates/bevy_hierarchy/src/one_to_many/one.rs new file mode 100644 index 0000000000000..2a3c4125c5e35 --- /dev/null +++ b/crates/bevy_hierarchy/src/one_to_many/one.rs @@ -0,0 +1,200 @@ +#[cfg(feature = "reflect")] +use bevy_ecs::reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, +}; +use bevy_ecs::{ + component::{Component, ComponentId}, + entity::{Entity, VisitEntities, VisitEntitiesMut}, + event::Events, + traversal::Traversal, + world::{DeferredWorld, FromWorld, World}, +}; +use core::fmt::Debug; +use core::marker::PhantomData; +use core::ops::Deref; + +use super::{OneToManyEvent, OneToManyMany}; + +/// Represents one half of a one-to-many relationship between an [`Entity`] and some number of other [entities](Entity). +/// +/// The type of relationship is denoted by the parameter `R`. +#[derive(Component)] +#[component( + on_insert = Self::associate, + on_replace = Self::disassociate, + on_remove = Self::disassociate +)] +#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "reflect", + reflect( + Component, + MapEntities, + VisitEntities, + VisitEntitiesMut, + PartialEq, + Debug, + FromWorld + ) +)] +pub struct OneToManyOne { + entity: Entity, + #[cfg_attr(feature = "reflect", reflect(ignore))] + _phantom: PhantomData, +} + +impl OneToManyOne { + fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + world.commands().queue(move |world: &mut World| { + let b = world + .get_entity(a_id) + .and_then(|a| a.get::()) + .map(|a_relationship| a_relationship.entity) + .and_then(|b_id| world.get_entity_mut(b_id)); + + let Some(mut b) = b else { return }; + + let b_id = b.id(); + + let b_points_to_a = b + .get::>() + .is_some_and(|b_relationship| b_relationship.contains(&a_id)); + + if !b_points_to_a { + if let Some(mut component) = b.get_mut::>() { + component.entities_mut().push(a_id); + } else { + b.insert(OneToManyMany::::new().with(a_id)); + } + + if let Some(mut moved) = world.get_resource_mut::>>() { + moved.send(OneToManyEvent::::Added(a_id, b_id, PhantomData)); + } + } + }); + } + + fn disassociate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + let Some(a_relationship) = world.get::(a_id) else { + unreachable!("component hook should only be called when component is available"); + }; + + let b_id = a_relationship.entity; + + world.commands().queue(move |world: &mut World| { + let a_points_to_b = world + .get_entity(a_id) + .and_then(|a| a.get::()) + .is_some_and(|a_relationship| a_relationship.entity == b_id); + + let b_points_to_a = world + .get_entity(b_id) + .and_then(|b| b.get::>()) + .is_some_and(|b_relationship| b_relationship.contains(&a_id)); + + if b_points_to_a && !a_points_to_b { + if let Some(mut b) = world.get_entity_mut(b_id) { + if let Some(mut component) = b.get_mut::>() { + component.entities_mut().retain(|&mut e| e != a_id); + + if component.is_empty() { + b.remove::>(); + } + } + } + + if let Some(mut moved) = world.get_resource_mut::>>() { + moved.send(OneToManyEvent::::Removed(a_id, b_id, PhantomData)); + } + } + }); + } +} + +impl PartialEq for OneToManyOne { + fn eq(&self, other: &Self) -> bool { + self.entity == other.entity + } +} + +impl Eq for OneToManyOne {} + +impl Debug for OneToManyOne { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("OneToManyOne") + .field(&self.entity) + .field(&core::any::type_name::()) + .finish() + } +} + +impl VisitEntities for OneToManyOne { + fn visit_entities(&self, mut f: F) { + f(self.entity); + } +} + +impl VisitEntitiesMut for OneToManyOne { + fn visit_entities_mut(&mut self, mut f: F) { + f(&mut self.entity); + } +} + +// TODO: We need to impl either FromWorld or Default so OneToOne can be registered as Reflect. +// This is because Reflect deserialize by creating an instance and apply a patch on top. +// However OneToOne should only ever be set with a real user-defined entity. It's worth looking into +// better ways to handle cases like this. +impl FromWorld for OneToManyOne { + #[inline(always)] + fn from_world(_world: &mut World) -> Self { + Self { + entity: Entity::PLACEHOLDER, + _phantom: PhantomData, + } + } +} + +impl Deref for OneToManyOne { + type Target = Entity; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.entity + } +} + +/// This provides generalized hierarchy traversal for use in [event propagation]. +/// +/// `OneToManyOne::traverse` will never form loops in properly-constructed hierarchies. +/// +/// [event propagation]: bevy_ecs::observer::Trigger::propagate +impl Traversal for &OneToManyOne { + fn traverse(item: Self::Item<'_>) -> Option { + Some(item.entity) + } +} + +impl OneToManyOne { + /// Gets the [`Entity`] ID of the other member of this one-to-many relationship. + #[inline(always)] + pub fn get(&self) -> Entity { + self.entity + } + + /// Gets the other [`Entity`] as a slice of length 1. + #[inline(always)] + pub fn as_slice(&self) -> &[Entity] { + core::slice::from_ref(&self.entity) + } + + /// Create a new relationship with the provided [`Entity`]. + #[inline(always)] + #[must_use] + pub fn new(other: Entity) -> Self { + Self { + entity: other, + _phantom: PhantomData, + } + } +} diff --git a/crates/bevy_hierarchy/src/one_to_one/component.rs b/crates/bevy_hierarchy/src/one_to_one/component.rs new file mode 100644 index 0000000000000..f4735a21166f3 --- /dev/null +++ b/crates/bevy_hierarchy/src/one_to_one/component.rs @@ -0,0 +1,188 @@ +#[cfg(feature = "reflect")] +use bevy_ecs::reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, +}; +use bevy_ecs::{ + component::{Component, ComponentId}, + entity::{Entity, VisitEntities, VisitEntitiesMut}, + event::Events, + traversal::Traversal, + world::{DeferredWorld, FromWorld, World}, +}; +use core::fmt::Debug; +use core::marker::PhantomData; +use core::ops::Deref; + +use super::OneToOneEvent; + +/// Represents one half of a one-to-one relationship between two [entities](Entity). +/// +/// The type of relationship is denoted by the parameter `R`. +#[derive(Component)] +#[component( + on_insert = Self::associate, + on_replace = Self::disassociate, + on_remove = Self::disassociate +)] +#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "reflect", + reflect( + Component, + MapEntities, + VisitEntities, + VisitEntitiesMut, + PartialEq, + Debug, + FromWorld + ) +)] +pub struct OneToOne { + entity: Entity, + #[cfg_attr(feature = "reflect", reflect(ignore))] + _phantom: PhantomData, +} + +impl OneToOne { + fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + world.commands().queue(move |world: &mut World| { + let b = world + .get_entity(a_id) + .and_then(|a| a.get::()) + .map(|a_relationship| a_relationship.entity) + .and_then(|b_id| world.get_entity_mut(b_id)); + + let Some(mut b) = b else { return }; + + let b_id = b.id(); + + let b_points_to_a = b + .get::() + .is_some_and(|b_relationship| b_relationship.entity == a_id); + + if !b_points_to_a { + b.insert(Self::new(a_id)); + + if let Some(mut moved) = world.get_resource_mut::>>() { + moved.send(OneToOneEvent::::Added(a_id, b_id, PhantomData)); + } + } + }); + } + + fn disassociate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + let Some(a_relationship) = world.get::(a_id) else { + unreachable!("component hook should only be called when component is available"); + }; + + let b_id = a_relationship.entity; + + world.commands().queue(move |world: &mut World| { + let a_points_to_b = world + .get_entity(a_id) + .and_then(|a| a.get::()) + .is_some_and(|a_relationship| a_relationship.entity == b_id); + + let b_points_to_a = world + .get_entity(b_id) + .and_then(|b| b.get::()) + .is_some_and(|b_relationship| b_relationship.entity == a_id); + + if b_points_to_a && !a_points_to_b { + if let Some(mut b) = world.get_entity_mut(b_id) { + b.remove::(); + } + + if let Some(mut moved) = world.get_resource_mut::>>() { + moved.send(OneToOneEvent::::Removed(a_id, b_id, PhantomData)); + } + } + }); + } +} + +impl PartialEq for OneToOne { + fn eq(&self, other: &Self) -> bool { + self.entity == other.entity + } +} + +impl Eq for OneToOne {} + +impl Debug for OneToOne { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("OneToOne") + .field(&self.entity) + .field(&core::any::type_name::()) + .finish() + } +} + +impl VisitEntities for OneToOne { + fn visit_entities(&self, mut f: F) { + f(self.entity); + } +} + +impl VisitEntitiesMut for OneToOne { + fn visit_entities_mut(&mut self, mut f: F) { + f(&mut self.entity); + } +} + +// TODO: We need to impl either FromWorld or Default so OneToOne can be registered as Reflect. +// This is because Reflect deserialize by creating an instance and apply a patch on top. +// However OneToOne should only ever be set with a real user-defined entity. It's worth looking into +// better ways to handle cases like this. +impl FromWorld for OneToOne { + #[inline(always)] + fn from_world(_world: &mut World) -> Self { + Self { + entity: Entity::PLACEHOLDER, + _phantom: PhantomData, + } + } +} + +impl Deref for OneToOne { + type Target = Entity; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.entity + } +} + +/// This provides generalized hierarchy traversal for use in [event propagation]. +/// +/// [event propagation]: bevy_ecs::observer::Trigger::propagate +impl Traversal for &OneToOne { + fn traverse(item: Self::Item<'_>) -> Option { + Some(item.entity) + } +} + +impl OneToOne { + /// Gets the [`Entity`] ID of the other member of this one-to-one relationship. + #[inline(always)] + pub fn get(&self) -> Entity { + self.entity + } + + /// Gets the other [`Entity`] as a slice of length 1. + #[inline(always)] + pub fn as_slice(&self) -> &[Entity] { + core::slice::from_ref(&self.entity) + } + + /// Create a new relationship with the provided [`Entity`]. + #[inline(always)] + #[must_use] + pub fn new(other: Entity) -> Self { + Self { + entity: other, + _phantom: PhantomData, + } + } +} diff --git a/crates/bevy_hierarchy/src/one_to_one/event.rs b/crates/bevy_hierarchy/src/one_to_one/event.rs new file mode 100644 index 0000000000000..2246323d38c7c --- /dev/null +++ b/crates/bevy_hierarchy/src/one_to_one/event.rs @@ -0,0 +1,46 @@ +use core::marker::PhantomData; + +use bevy_ecs::{entity::Entity, event::Event}; + +/// An [`Event`] that is fired whenever there is a change in [`OneToOne`] relationship `R`. +/// +/// [`Event`]: bevy_ecs::event::Event +#[derive(Event)] +pub enum OneToOneEvent { + /// Fired whenever a [`OneToOne`] relationship of type `R` is added between two [entities](Entity) + Added(Entity, Entity, PhantomData), + /// Fired whenever a [`OneToOne`] relationship of type `R` is remove from two [entities](Entity) + Removed(Entity, Entity, PhantomData), +} + +impl core::fmt::Debug for OneToOneEvent { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Added(arg0, arg1, _) => f.debug_tuple("Added").field(arg0).field(arg1).finish(), + Self::Removed(arg0, arg1, _) => { + f.debug_tuple("Removed").field(arg0).field(arg1).finish() + } + } + } +} + +impl PartialEq for OneToOneEvent { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Added(l0, l1, _), Self::Added(r0, r1, _)) + | (Self::Removed(l0, l1, _), Self::Removed(r0, r1, _)) => l0 == r0 && l1 == r1, + _ => false, + } + } +} + +impl Eq for OneToOneEvent {} + +impl Clone for OneToOneEvent { + fn clone(&self) -> Self { + match self { + Self::Added(arg0, arg1, _) => Self::Added(*arg0, *arg1, PhantomData), + Self::Removed(arg0, arg1, _) => Self::Removed(*arg0, *arg1, PhantomData), + } + } +} diff --git a/crates/bevy_hierarchy/src/one_to_one/mod.rs b/crates/bevy_hierarchy/src/one_to_one/mod.rs new file mode 100644 index 0000000000000..39b1139c5006e --- /dev/null +++ b/crates/bevy_hierarchy/src/one_to_one/mod.rs @@ -0,0 +1,215 @@ +//! Provides access to the [`OneToOne`] component, allowing for one-to-one relationships +//! of an arbitrary type to be automatically managed in the ECS. + +mod component; +pub use component::OneToOne; + +mod event; +pub use event::OneToOneEvent; + +#[cfg(test)] +mod tests { + use core::marker::PhantomData; + + use bevy_ecs::{event::Events, world::World}; + + use super::*; + + /// An example relationship between two entities + struct Friendship; + + /// Shorthand for a best friend relationship + type Friend = OneToOne; + + #[test] + fn simple_add_then_remove() { + let mut world = World::new(); + + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Friend::new(a)).id(); + + world.flush(); + + assert_eq!(world.get::(a), Some(&Friend::new(b))); + assert_eq!(world.get::(b), Some(&Friend::new(a))); + + world.entity_mut(a).remove::(); + + world.flush(); + + assert_eq!(world.get::(a), None); + assert_eq!(world.get::(b), None); + } + + #[test] + fn triangular_break_up() { + let mut world = World::new(); + + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Friend::new(a)).id(); + let c = world.spawn_empty().id(); + + world.flush(); + + assert_eq!(world.get::(a), Some(&Friend::new(b))); + assert_eq!(world.get::(b), Some(&Friend::new(a))); + assert_eq!(world.get::(c), None); + + world.entity_mut(a).insert(Friend::new(c)); + + world.flush(); + + assert_eq!(world.get::(a), Some(&Friend::new(c))); + assert_eq!(world.get::(b), None); + assert_eq!(world.get::(c), Some(&Friend::new(a))); + } + + #[test] + fn repeated_adding() { + let mut world = World::new(); + + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + + world.entity_mut(a).insert(Friend::new(b)); + world.entity_mut(a).insert(Friend::new(b)); + world.entity_mut(a).insert(Friend::new(b)); + + world.flush(); + + assert_eq!(world.get::(a), Some(&Friend::new(b))); + assert_eq!(world.get::(b), Some(&Friend::new(a))); + } + + #[test] + fn swap() { + let mut world = World::new(); + + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + let c = world.spawn_empty().id(); + let d = world.spawn_empty().id(); + + world.entity_mut(a).insert(Friend::new(b)); + world.entity_mut(c).insert(Friend::new(d)); + + world.flush(); + + assert_eq!(world.get::(a), Some(&Friend::new(b))); + assert_eq!(world.get::(b), Some(&Friend::new(a))); + assert_eq!(world.get::(c), Some(&Friend::new(d))); + assert_eq!(world.get::(d), Some(&Friend::new(c))); + + world.entity_mut(a).insert(Friend::new(c)); + world.entity_mut(b).insert(Friend::new(d)); + + world.flush(); + + assert_eq!(world.get::(a), Some(&Friend::new(c))); + assert_eq!(world.get::(b), Some(&Friend::new(d))); + assert_eq!(world.get::(c), Some(&Friend::new(a))); + assert_eq!(world.get::(d), Some(&Friend::new(b))); + } + + #[test] + fn looped_add_and_remove() { + let mut world = World::new(); + + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + + for _ in 0..10_000 { + world.entity_mut(a).insert(Friend::new(b)); + world.entity_mut(a).remove::(); + } + + world.flush(); + + assert_eq!(world.get::(a), None); + assert_eq!(world.get::(b), None); + } + + #[test] + fn invalid_chaining() { + let mut world = World::new(); + + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + let c = world.spawn_empty().id(); + let d = world.spawn_empty().id(); + let e = world.spawn_empty().id(); + let f = world.spawn_empty().id(); + + world.entity_mut(a).insert(Friend::new(b)); + world.entity_mut(b).insert(Friend::new(c)); + world.entity_mut(c).insert(Friend::new(d)); + world.entity_mut(d).insert(Friend::new(e)); + world.entity_mut(e).insert(Friend::new(f)); + world.entity_mut(f).insert(Friend::new(a)); + + world.flush(); + + assert_eq!(world.get::(a), Some(&Friend::new(b))); + assert_eq!(world.get::(b), Some(&Friend::new(a))); + assert_eq!(world.get::(c), Some(&Friend::new(d))); + assert_eq!(world.get::(d), Some(&Friend::new(c))); + assert_eq!(world.get::(e), Some(&Friend::new(f))); + assert_eq!(world.get::(f), Some(&Friend::new(e))); + + // The pairing is caused by the first member of the pair (e.g., a, c, e) replacing + // the relationship on the second member of the pair (e.g, b, d, f). + // When the replacement occurs, it checks if the second member had a valid relationship + // with it's old data (e.g., b -> c, d -> e, etc.) and if it did not, then no action is taken. + } + + #[test] + fn event_testing() { + let mut world = World::new(); + + world.register_component::(); + world.init_resource::>>(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Friend::new(a)).id(); + + world.flush(); + + assert_eq!(world.get::(a), Some(&Friend::new(b))); + assert_eq!(world.get::(b), Some(&Friend::new(a))); + + assert_eq!( + world + .resource_mut::>>() + .drain() + .collect::>(), + vec![OneToOneEvent::::Added(b, a, PhantomData)] + ); + + world.entity_mut(a).remove::(); + + world.flush(); + + assert_eq!(world.get::(a), None); + assert_eq!(world.get::(b), None); + + assert_eq!( + world + .resource_mut::>>() + .drain() + .collect::>(), + vec![OneToOneEvent::::Removed(a, b, PhantomData)] + ); + } +} From c81436a98b6ab23021f27cc808312f035bc2effb Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 4 Oct 2024 09:58:01 +1000 Subject: [PATCH 02/25] Fix docs --- crates/bevy_hierarchy/src/family.rs | 2 -- crates/bevy_hierarchy/src/one_to_many/event.rs | 6 +++--- crates/bevy_hierarchy/src/one_to_one/event.rs | 6 +++--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/bevy_hierarchy/src/family.rs b/crates/bevy_hierarchy/src/family.rs index 83d769645cee2..ccc1009838c95 100644 --- a/crates/bevy_hierarchy/src/family.rs +++ b/crates/bevy_hierarchy/src/family.rs @@ -15,7 +15,6 @@ pub struct Family; /// /// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt /// [`Query`]: bevy_ecs::system::Query -/// [`Children`]: super::children::Children /// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children pub type Parent = OneToManyOne; @@ -30,7 +29,6 @@ pub type Parent = OneToManyOne; /// /// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt /// [`Query`]: bevy_ecs::system::Query -/// [`Parent`]: crate::components::parent::Parent /// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children pub type Children = OneToManyMany; diff --git a/crates/bevy_hierarchy/src/one_to_many/event.rs b/crates/bevy_hierarchy/src/one_to_many/event.rs index 0fa75cf4e0aa6..ec70bf39be016 100644 --- a/crates/bevy_hierarchy/src/one_to_many/event.rs +++ b/crates/bevy_hierarchy/src/one_to_many/event.rs @@ -2,14 +2,14 @@ use core::marker::PhantomData; use bevy_ecs::{entity::Entity, event::Event}; -/// An [`Event`] that is fired whenever there is a change in [`OneToMany`] relationship `R`. +/// An [`Event`] that is fired whenever there is a change in One-to-Many relationship `R`. /// /// [`Event`]: bevy_ecs::event::Event #[derive(Event)] pub enum OneToManyEvent { - /// Fired whenever a [`OneToOne`] relationship of type `R` is added between two [entities](Entity) + /// Fired whenever a One-to-Many relationship of type `R` is added between two [entities](Entity) Added(Entity, Entity, PhantomData), - /// Fired whenever a [`OneToOne`] relationship of type `R` is remove from two [entities](Entity) + /// Fired whenever a One-to-Many relationship of type `R` is remove from two [entities](Entity) Removed(Entity, Entity, PhantomData), } diff --git a/crates/bevy_hierarchy/src/one_to_one/event.rs b/crates/bevy_hierarchy/src/one_to_one/event.rs index 2246323d38c7c..95a8855d20aa6 100644 --- a/crates/bevy_hierarchy/src/one_to_one/event.rs +++ b/crates/bevy_hierarchy/src/one_to_one/event.rs @@ -2,14 +2,14 @@ use core::marker::PhantomData; use bevy_ecs::{entity::Entity, event::Event}; -/// An [`Event`] that is fired whenever there is a change in [`OneToOne`] relationship `R`. +/// An [`Event`] that is fired whenever there is a change in One-to-One relationship `R`. /// /// [`Event`]: bevy_ecs::event::Event #[derive(Event)] pub enum OneToOneEvent { - /// Fired whenever a [`OneToOne`] relationship of type `R` is added between two [entities](Entity) + /// Fired whenever a One-to-One relationship of type `R` is added between two [entities](Entity) Added(Entity, Entity, PhantomData), - /// Fired whenever a [`OneToOne`] relationship of type `R` is remove from two [entities](Entity) + /// Fired whenever a One-to-One relationship of type `R` is remove from two [entities](Entity) Removed(Entity, Entity, PhantomData), } From f6b83c6a0a22cd556f077d69dbeaf3fda753a3bb Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 4 Oct 2024 16:39:07 +1000 Subject: [PATCH 03/25] Fix documentation Co-Authored-By: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> --- crates/bevy_hierarchy/src/one_to_many/one.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_hierarchy/src/one_to_many/one.rs b/crates/bevy_hierarchy/src/one_to_many/one.rs index 2a3c4125c5e35..3785c67897577 100644 --- a/crates/bevy_hierarchy/src/one_to_many/one.rs +++ b/crates/bevy_hierarchy/src/one_to_many/one.rs @@ -182,7 +182,7 @@ impl OneToManyOne { self.entity } - /// Gets the other [`Entity`] as a slice of length 1. + /// Gets the other [`Entity`] as a slice. #[inline(always)] pub fn as_slice(&self) -> &[Entity] { core::slice::from_ref(&self.entity) From 0e1844dfdea03a871dd1c3caa0fc05aa6944fc17 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Fri, 4 Oct 2024 16:53:36 +1000 Subject: [PATCH 04/25] Remove needless allocation Co-Authored-By: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> --- crates/bevy_hierarchy/src/hierarchy.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs index f2ede1f731e67..affadc5459468 100644 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ b/crates/bevy_hierarchy/src/hierarchy.rs @@ -31,9 +31,12 @@ pub fn despawn_with_children_recursive(world: &mut World, entity: Entity, warn: // Should only be called by `despawn_with_children_recursive`! fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity, warn: bool) { - if let Some(children) = world.get::(entity) { - let children = children.as_slice().to_vec(); - for e in children { + if let Some(children) = world + .get_entity_mut(entity) + .as_mut() + .and_then(EntityWorldMut::take::) + { + for &e in &children { despawn_with_children_recursive_inner(world, e, warn); } } @@ -48,9 +51,12 @@ fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity, warn } fn despawn_children_recursive(world: &mut World, entity: Entity, warn: bool) { - if let Some(children) = world.entity_mut(entity).get::() { - let children = children.as_slice().to_vec(); - for e in children { + if let Some(children) = world + .get_entity_mut(entity) + .as_mut() + .and_then(EntityWorldMut::take::) + { + for &e in &children { despawn_with_children_recursive_inner(world, e, warn); } } From 85e1beb68821f27f85562597034c2d63c4b08f02 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 5 Oct 2024 20:04:01 +1000 Subject: [PATCH 05/25] Refactored event types to be more ergonomic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Tamás Kiss <20684057+eidloi@users.noreply.github.com> --- crates/bevy_hierarchy/src/child_builder.rs | 19 ++- .../bevy_hierarchy/src/one_to_many/event.rs | 108 ++++++++++++++--- crates/bevy_hierarchy/src/one_to_many/many.rs | 4 +- crates/bevy_hierarchy/src/one_to_many/mod.rs | 6 +- crates/bevy_hierarchy/src/one_to_many/one.rs | 4 +- .../src/one_to_one/component.rs | 4 +- crates/bevy_hierarchy/src/one_to_one/event.rs | 110 +++++++++++++++--- crates/bevy_hierarchy/src/one_to_one/mod.rs | 6 +- 8 files changed, 203 insertions(+), 58 deletions(-) diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 03ac2e9dc9ed5..363d4b7b2362f 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -580,8 +580,6 @@ impl BuildChildren for EntityWorldMut<'_> { #[cfg(test)] mod tests { - use core::marker::PhantomData; - use super::{BuildChildren, ChildBuild}; use crate::{Children, HierarchyEvent, Parent}; use smallvec::{smallvec, SmallVec}; @@ -638,13 +636,13 @@ mod tests { assert_parent(world, b, Some(a)); assert_children(world, a, Some(&[b])); - assert_events(world, &[HierarchyEvent::Added(b, a, PhantomData)]); + assert_events(world, &[HierarchyEvent::added(a, b)]); world.entity_mut(a).add_child(c); assert_children(world, a, Some(&[b, c])); assert_parent(world, c, Some(a)); - assert_events(world, &[HierarchyEvent::Added(c, a, PhantomData)]); + assert_events(world, &[HierarchyEvent::added(a, c)]); // Children component should be removed when it's empty. world.entity_mut(d).add_child(b).add_child(c); assert_children(world, a, None); @@ -661,7 +659,7 @@ mod tests { assert_parent(world, a, Some(b)); assert_children(world, b, Some(&[a])); - assert_events(world, &[HierarchyEvent::Added(a, b, PhantomData)]); + assert_events(world, &[HierarchyEvent::added(b, a)]); world.entity_mut(a).set_parent(c); @@ -670,10 +668,7 @@ mod tests { assert_children(world, c, Some(&[a])); assert_events( world, - &[ - HierarchyEvent::Removed(a, b, PhantomData), - HierarchyEvent::Added(a, c, PhantomData), - ], + &[HierarchyEvent::removed(b, a), HierarchyEvent::added(c, a)], ); } @@ -707,13 +702,13 @@ mod tests { assert_parent(world, b, None); assert_parent(world, c, Some(a)); assert_children(world, a, Some(&[c])); - omit_events(world, 2); // Omit ChildAdded events. - assert_events(world, &[HierarchyEvent::Removed(b, a, PhantomData)]); + omit_events(world, 2); // Omit Added events. + assert_events(world, &[HierarchyEvent::removed(a, b)]); world.entity_mut(c).remove_parent(); assert_parent(world, c, None); assert_children(world, a, None); - assert_events(world, &[HierarchyEvent::Removed(c, a, PhantomData)]); + assert_events(world, &[HierarchyEvent::removed(a, c)]); } #[allow(dead_code)] diff --git a/crates/bevy_hierarchy/src/one_to_many/event.rs b/crates/bevy_hierarchy/src/one_to_many/event.rs index ec70bf39be016..2f703668a9ed3 100644 --- a/crates/bevy_hierarchy/src/one_to_many/event.rs +++ b/crates/bevy_hierarchy/src/one_to_many/event.rs @@ -2,24 +2,46 @@ use core::marker::PhantomData; use bevy_ecs::{entity::Entity, event::Event}; -/// An [`Event`] that is fired whenever there is a change in One-to-Many relationship `R`. -/// -/// [`Event`]: bevy_ecs::event::Event +/// A One-to-Many [relationship](crate::OneToManyOne) [`Event`]. #[derive(Event)] pub enum OneToManyEvent { - /// Fired whenever a One-to-Many relationship of type `R` is added between two [entities](Entity) - Added(Entity, Entity, PhantomData), - /// Fired whenever a One-to-Many relationship of type `R` is remove from two [entities](Entity) - Removed(Entity, Entity, PhantomData), + /// A [relationship](crate::OneToManyOne) was added between two [entities](Entity). + Added(OneToManyEventDetails), + /// A [relationship](crate::OneToManyOne) was removed from two [entities](Entity). + Removed(OneToManyEventDetails), +} + +impl OneToManyEvent { + /// Create a new [`Added`](OneToOneEvent::Added) [`Event`] + pub const fn added(many: Entity, one: Entity) -> Self { + Self::Added(OneToManyEventDetails::new(many, one)) + } + + /// Create a new [`Removed`](OneToOneEvent::Removed) [`Event`] + pub const fn removed(many: Entity, one: Entity) -> Self { + Self::Removed(OneToManyEventDetails::new(many, one)) + } + + /// Get the [`Entity`] that has the [`OneToManyMany`](crate::OneToManyMany) component. + pub const fn many(&self) -> Entity { + match self { + Self::Added(details) | Self::Removed(details) => details.many(), + } + } + + /// Get the [`Entity`] that has the [`OneToManyOne`](crate::OneToManyOne) component. + pub const fn one(&self) -> Entity { + match self { + Self::Added(details) | Self::Removed(details) => details.one(), + } + } } impl core::fmt::Debug for OneToManyEvent { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::Added(arg0, arg1, _) => f.debug_tuple("Added").field(arg0).field(arg1).finish(), - Self::Removed(arg0, arg1, _) => { - f.debug_tuple("Removed").field(arg0).field(arg1).finish() - } + Self::Added(arg0) => f.debug_tuple("Added").field(arg0).finish(), + Self::Removed(arg0) => f.debug_tuple("Removed").field(arg0).finish(), } } } @@ -27,8 +49,8 @@ impl core::fmt::Debug for OneToManyEvent { impl PartialEq for OneToManyEvent { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Added(l0, l1, _), Self::Added(r0, r1, _)) - | (Self::Removed(l0, l1, _), Self::Removed(r0, r1, _)) => l0 == r0 && l1 == r1, + (Self::Added(l0), Self::Added(r0)) => l0 == r0, + (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, _ => false, } } @@ -38,9 +60,63 @@ impl Eq for OneToManyEvent {} impl Clone for OneToManyEvent { fn clone(&self) -> Self { - match self { - Self::Added(arg0, arg1, _) => Self::Added(*arg0, *arg1, PhantomData), - Self::Removed(arg0, arg1, _) => Self::Removed(*arg0, *arg1, PhantomData), + *self + } +} + +impl Copy for OneToManyEvent {} + +/// The details of a [`OneToManyEvent`]. +pub struct OneToManyEventDetails { + many: Entity, + one: Entity, + phantom_data: PhantomData, +} + +impl OneToManyEventDetails { + /// Create a new [`OneToManyEventDetails`] for a `many` and a `one` [`Entity`]. + /// The `many` [`Entity`] has the [`OneToManyMany`](crate::OneToManyMany) component, + /// while the `one` [`Entity`] has the [`OneToManyOne`](crate::OneToManyOne) component. + pub const fn new(many: Entity, one: Entity) -> Self { + Self { + many, + one, + phantom_data: PhantomData, } } + + /// Get the [`Entity`] that has the [`OneToManyMany`](crate::OneToManyMany) component. + pub const fn many(&self) -> Entity { + self.many + } + + /// Get the [`Entity`] that has the [`OneToManyOne`](crate::OneToManyOne) component. + pub const fn one(&self) -> Entity { + self.many + } } + +impl core::fmt::Debug for OneToManyEventDetails { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("OneToManyEventDetails") + .field("many", &self.many) + .field("one", &self.one) + .finish() + } +} + +impl PartialEq for OneToManyEventDetails { + fn eq(&self, other: &Self) -> bool { + self.many == other.many && self.one == other.one + } +} + +impl Eq for OneToManyEventDetails {} + +impl Clone for OneToManyEventDetails { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for OneToManyEventDetails {} diff --git a/crates/bevy_hierarchy/src/one_to_many/many.rs b/crates/bevy_hierarchy/src/one_to_many/many.rs index cfd5eec4e8abe..d4d1cb362464b 100644 --- a/crates/bevy_hierarchy/src/one_to_many/many.rs +++ b/crates/bevy_hierarchy/src/one_to_many/many.rs @@ -73,7 +73,7 @@ impl OneToManyMany { b.insert(OneToManyOne::::new(a_id)); if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToManyEvent::::Added(a_id, b_id, PhantomData)); + moved.send(OneToManyEvent::::added(a_id, b_id)); } } } @@ -106,7 +106,7 @@ impl OneToManyMany { } if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToManyEvent::::Removed(a_id, b_id, PhantomData)); + moved.send(OneToManyEvent::::removed(a_id, b_id)); } } } diff --git a/crates/bevy_hierarchy/src/one_to_many/mod.rs b/crates/bevy_hierarchy/src/one_to_many/mod.rs index fdd77366ce9f0..71cdee7508d3c 100644 --- a/crates/bevy_hierarchy/src/one_to_many/mod.rs +++ b/crates/bevy_hierarchy/src/one_to_many/mod.rs @@ -9,8 +9,6 @@ pub use many::OneToManyMany; #[cfg(test)] mod tests { - use core::marker::PhantomData; - use bevy_ecs::{event::Events, world::World}; use super::*; @@ -157,7 +155,7 @@ mod tests { .resource_mut::>>() .drain() .collect::>(), - vec![OneToManyEvent::::Added(b, a, PhantomData)] + vec![OneToManyEvent::::added(a, b)] ); world.entity_mut(b).remove::(); @@ -172,7 +170,7 @@ mod tests { .resource_mut::>>() .drain() .collect::>(), - vec![OneToManyEvent::::Removed(b, a, PhantomData)] + vec![OneToManyEvent::::removed(a, b)] ); } } diff --git a/crates/bevy_hierarchy/src/one_to_many/one.rs b/crates/bevy_hierarchy/src/one_to_many/one.rs index 3785c67897577..b83bd04f7e490 100644 --- a/crates/bevy_hierarchy/src/one_to_many/one.rs +++ b/crates/bevy_hierarchy/src/one_to_many/one.rs @@ -69,7 +69,7 @@ impl OneToManyOne { } if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToManyEvent::::Added(a_id, b_id, PhantomData)); + moved.send(OneToManyEvent::::added(b_id, a_id)); } } }); @@ -105,7 +105,7 @@ impl OneToManyOne { } if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToManyEvent::::Removed(a_id, b_id, PhantomData)); + moved.send(OneToManyEvent::::removed(b_id, a_id)); } } }); diff --git a/crates/bevy_hierarchy/src/one_to_one/component.rs b/crates/bevy_hierarchy/src/one_to_one/component.rs index f4735a21166f3..b8ce5e697ba08 100644 --- a/crates/bevy_hierarchy/src/one_to_one/component.rs +++ b/crates/bevy_hierarchy/src/one_to_one/component.rs @@ -65,7 +65,7 @@ impl OneToOne { b.insert(Self::new(a_id)); if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToOneEvent::::Added(a_id, b_id, PhantomData)); + moved.send(OneToOneEvent::::added(a_id, b_id)); } } }); @@ -95,7 +95,7 @@ impl OneToOne { } if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToOneEvent::::Removed(a_id, b_id, PhantomData)); + moved.send(OneToOneEvent::::removed(a_id, b_id)); } } }); diff --git a/crates/bevy_hierarchy/src/one_to_one/event.rs b/crates/bevy_hierarchy/src/one_to_one/event.rs index 95a8855d20aa6..9dee59b35100f 100644 --- a/crates/bevy_hierarchy/src/one_to_one/event.rs +++ b/crates/bevy_hierarchy/src/one_to_one/event.rs @@ -2,24 +2,48 @@ use core::marker::PhantomData; use bevy_ecs::{entity::Entity, event::Event}; -/// An [`Event`] that is fired whenever there is a change in One-to-One relationship `R`. -/// -/// [`Event`]: bevy_ecs::event::Event +/// A One-to-One [relationship](crate::OneToOne) [`Event`]. #[derive(Event)] pub enum OneToOneEvent { - /// Fired whenever a One-to-One relationship of type `R` is added between two [entities](Entity) - Added(Entity, Entity, PhantomData), - /// Fired whenever a One-to-One relationship of type `R` is remove from two [entities](Entity) - Removed(Entity, Entity, PhantomData), + /// A [relationship](crate::OneToOne) was added between two [entities](Entity). + Added(OneToOneEventDetails), + /// A [relationship](crate::OneToOne) was removed from two [entities](Entity). + Removed(OneToOneEventDetails), +} + +impl OneToOneEvent { + /// Create a new [`Added`](OneToOneEvent::Added) [`Event`] + pub const fn added(primary: Entity, secondary: Entity) -> Self { + Self::Added(OneToOneEventDetails::new(primary, secondary)) + } + + /// Create a new [`Removed`](OneToOneEvent::Removed) [`Event`] + pub const fn removed(primary: Entity, secondary: Entity) -> Self { + Self::Removed(OneToOneEventDetails::new(primary, secondary)) + } + + /// Get the primary [`Entity`] in this [`Event`]. + /// The primary is the _cause_ of the event, while the secondary is the relation. + pub const fn primary(&self) -> Entity { + match self { + Self::Added(details) | Self::Removed(details) => details.primary(), + } + } + + /// Get the secondary [`Entity`] in this [`Event`]. + /// The primary is the _cause_ of the event, while the secondary is the relation. + pub const fn secondary(&self) -> Entity { + match self { + Self::Added(details) | Self::Removed(details) => details.secondary(), + } + } } impl core::fmt::Debug for OneToOneEvent { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::Added(arg0, arg1, _) => f.debug_tuple("Added").field(arg0).field(arg1).finish(), - Self::Removed(arg0, arg1, _) => { - f.debug_tuple("Removed").field(arg0).field(arg1).finish() - } + Self::Added(arg0) => f.debug_tuple("Added").field(arg0).finish(), + Self::Removed(arg0) => f.debug_tuple("Removed").field(arg0).finish(), } } } @@ -27,8 +51,8 @@ impl core::fmt::Debug for OneToOneEvent { impl PartialEq for OneToOneEvent { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Added(l0, l1, _), Self::Added(r0, r1, _)) - | (Self::Removed(l0, l1, _), Self::Removed(r0, r1, _)) => l0 == r0 && l1 == r1, + (Self::Added(l0), Self::Added(r0)) => l0 == r0, + (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, _ => false, } } @@ -38,9 +62,63 @@ impl Eq for OneToOneEvent {} impl Clone for OneToOneEvent { fn clone(&self) -> Self { - match self { - Self::Added(arg0, arg1, _) => Self::Added(*arg0, *arg1, PhantomData), - Self::Removed(arg0, arg1, _) => Self::Removed(*arg0, *arg1, PhantomData), + *self + } +} + +impl Copy for OneToOneEvent {} + +/// The details of a [`OneToOneEvent`]. +pub struct OneToOneEventDetails { + primary: Entity, + secondary: Entity, + phantom_data: PhantomData, +} + +impl OneToOneEventDetails { + /// Create a new [`OneToOneEventDetails`] for a `primary` and a `secondary` [`Entity`]. + /// The `primary` [`Entity`] is the cause of the [`Event`], while the `secondary` + /// is the other member of the relationship. + pub const fn new(primary: Entity, secondary: Entity) -> Self { + Self { + primary, + secondary, + phantom_data: PhantomData, } } + + /// Get the [`Entity`] that caused this [`Event`] to be triggered. + pub const fn primary(&self) -> Entity { + self.primary + } + + /// Get the [`Entity`] related to the `primary` [`Entity`]. + pub const fn secondary(&self) -> Entity { + self.primary + } } + +impl core::fmt::Debug for OneToOneEventDetails { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("OneToOneEventDetails") + .field("primary", &self.primary) + .field("secondary", &self.secondary) + .finish() + } +} + +impl PartialEq for OneToOneEventDetails { + fn eq(&self, other: &Self) -> bool { + self.primary == other.primary && self.secondary == other.secondary + } +} + +impl Eq for OneToOneEventDetails {} + +impl Clone for OneToOneEventDetails { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for OneToOneEventDetails {} diff --git a/crates/bevy_hierarchy/src/one_to_one/mod.rs b/crates/bevy_hierarchy/src/one_to_one/mod.rs index 39b1139c5006e..2405642dec85d 100644 --- a/crates/bevy_hierarchy/src/one_to_one/mod.rs +++ b/crates/bevy_hierarchy/src/one_to_one/mod.rs @@ -9,8 +9,6 @@ pub use event::OneToOneEvent; #[cfg(test)] mod tests { - use core::marker::PhantomData; - use bevy_ecs::{event::Events, world::World}; use super::*; @@ -194,7 +192,7 @@ mod tests { .resource_mut::>>() .drain() .collect::>(), - vec![OneToOneEvent::::Added(b, a, PhantomData)] + vec![OneToOneEvent::::added(b, a)] ); world.entity_mut(a).remove::(); @@ -209,7 +207,7 @@ mod tests { .resource_mut::>>() .drain() .collect::>(), - vec![OneToOneEvent::::Removed(a, b, PhantomData)] + vec![OneToOneEvent::::removed(a, b)] ); } } From ed19f97d8f27a282ab23695e062a90602e0e5bc0 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 5 Oct 2024 20:17:49 +1000 Subject: [PATCH 06/25] Silly Typo --- crates/bevy_hierarchy/src/one_to_many/event.rs | 2 +- crates/bevy_hierarchy/src/one_to_one/event.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_hierarchy/src/one_to_many/event.rs b/crates/bevy_hierarchy/src/one_to_many/event.rs index 2f703668a9ed3..c1700900316e0 100644 --- a/crates/bevy_hierarchy/src/one_to_many/event.rs +++ b/crates/bevy_hierarchy/src/one_to_many/event.rs @@ -92,7 +92,7 @@ impl OneToManyEventDetails { /// Get the [`Entity`] that has the [`OneToManyOne`](crate::OneToManyOne) component. pub const fn one(&self) -> Entity { - self.many + self.one } } diff --git a/crates/bevy_hierarchy/src/one_to_one/event.rs b/crates/bevy_hierarchy/src/one_to_one/event.rs index 9dee59b35100f..7b62773d37981 100644 --- a/crates/bevy_hierarchy/src/one_to_one/event.rs +++ b/crates/bevy_hierarchy/src/one_to_one/event.rs @@ -94,7 +94,7 @@ impl OneToOneEventDetails { /// Get the [`Entity`] related to the `primary` [`Entity`]. pub const fn secondary(&self) -> Entity { - self.primary + self.secondary } } From b3acfe459b8960fe0fba8beecec693e44ae8109d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 5 Oct 2024 20:21:35 +1000 Subject: [PATCH 07/25] Clippy --- crates/bevy_hierarchy/src/one_to_many/event.rs | 3 +-- crates/bevy_hierarchy/src/one_to_one/event.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_hierarchy/src/one_to_many/event.rs b/crates/bevy_hierarchy/src/one_to_many/event.rs index c1700900316e0..d9dd39046785b 100644 --- a/crates/bevy_hierarchy/src/one_to_many/event.rs +++ b/crates/bevy_hierarchy/src/one_to_many/event.rs @@ -49,8 +49,7 @@ impl core::fmt::Debug for OneToManyEvent { impl PartialEq for OneToManyEvent { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Added(l0), Self::Added(r0)) => l0 == r0, - (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, + (Self::Added(l0), Self::Added(r0)) | (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, _ => false, } } diff --git a/crates/bevy_hierarchy/src/one_to_one/event.rs b/crates/bevy_hierarchy/src/one_to_one/event.rs index 7b62773d37981..09955c8855a2b 100644 --- a/crates/bevy_hierarchy/src/one_to_one/event.rs +++ b/crates/bevy_hierarchy/src/one_to_one/event.rs @@ -51,8 +51,7 @@ impl core::fmt::Debug for OneToOneEvent { impl PartialEq for OneToOneEvent { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Added(l0), Self::Added(r0)) => l0 == r0, - (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, + (Self::Added(l0), Self::Added(r0)) | (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, _ => false, } } From cc676f659c7e83567a8992f57d5ff65f501beaf7 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 5 Oct 2024 20:33:17 +1000 Subject: [PATCH 08/25] Fix documentation --- crates/bevy_hierarchy/src/one_to_many/event.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_hierarchy/src/one_to_many/event.rs b/crates/bevy_hierarchy/src/one_to_many/event.rs index d9dd39046785b..4ec41b726a303 100644 --- a/crates/bevy_hierarchy/src/one_to_many/event.rs +++ b/crates/bevy_hierarchy/src/one_to_many/event.rs @@ -12,12 +12,12 @@ pub enum OneToManyEvent { } impl OneToManyEvent { - /// Create a new [`Added`](OneToOneEvent::Added) [`Event`] + /// Create a new [`Added`](OneToManyEvent::Added) [`Event`] pub const fn added(many: Entity, one: Entity) -> Self { Self::Added(OneToManyEventDetails::new(many, one)) } - /// Create a new [`Removed`](OneToOneEvent::Removed) [`Event`] + /// Create a new [`Removed`](OneToManyEvent::Removed) [`Event`] pub const fn removed(many: Entity, one: Entity) -> Self { Self::Removed(OneToManyEventDetails::new(many, one)) } From e2bc4b92395cf53de220dae45232a3d6ca86237d Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 6 Oct 2024 11:04:49 +1100 Subject: [PATCH 09/25] Generalised `depsawn_recursive` Allow calling `despawn_recursive` on more relationships types that just `Parent`/`Children`. Now supports any `Component` providing `VisitEntities`. --- .../src/despawn_recursive/command.rs | 138 +++++++ .../src/despawn_recursive/ext.rs | 90 +++++ .../src/despawn_recursive/mod.rs | 151 ++++++++ crates/bevy_hierarchy/src/hierarchy.rs | 347 ------------------ crates/bevy_hierarchy/src/lib.rs | 6 +- crates/bevy_hierarchy/src/one_to_many/many.rs | 4 +- crates/bevy_hierarchy/src/one_to_many/one.rs | 4 +- .../src/one_to_one/component.rs | 4 +- crates/bevy_ui/src/layout/mod.rs | 10 +- 9 files changed, 390 insertions(+), 364 deletions(-) create mode 100644 crates/bevy_hierarchy/src/despawn_recursive/command.rs create mode 100644 crates/bevy_hierarchy/src/despawn_recursive/ext.rs create mode 100644 crates/bevy_hierarchy/src/despawn_recursive/mod.rs delete mode 100644 crates/bevy_hierarchy/src/hierarchy.rs diff --git a/crates/bevy_hierarchy/src/despawn_recursive/command.rs b/crates/bevy_hierarchy/src/despawn_recursive/command.rs new file mode 100644 index 0000000000000..53f0f81ec1282 --- /dev/null +++ b/crates/bevy_hierarchy/src/despawn_recursive/command.rs @@ -0,0 +1,138 @@ +use core::marker::PhantomData; + +use bevy_ecs::{ + component::Component, + entity::{Entity, VisitEntities}, + world::{Command, EntityWorldMut, World}, +}; +use bevy_utils::tracing::debug; + +/// Despawns all [entities](Entity) found using [`VisitEntities`] on the +/// [`Component`] `C` for the provided [`Entity`], including said [`Entity`]. +/// If an [`Entity`] cannot be found, a warning will be emitted. +/// +/// The target [`Entity`] can be excluded from being despawned using +/// [`with_inclusion`](`DespawnRecursive::with_inclusion`). +/// +/// Warnings can be disabled using [`with_warn`](`DespawnRecursive::with_warn`). +/// +/// Note that the [`Component`] `C` is _removed_ from the [`Entity`] even if it isn't despawned. +/// +/// # Examples +/// +/// ```rust +/// # use bevy_hierarchy::{DespawnRecursive, Children, Parent}; +/// # use bevy_ecs::world::{Command, World}; +/// # +/// # let mut world = World::new(); +/// # let parent = world.spawn_empty().id(); +/// # let child = world.spawn(Parent::new(parent)).id(); +/// # +/// # let mut commands = world.commands(); +/// # +/// # let command = { +/// // Despawn all Children from a parent +/// DespawnRecursive::::new(parent) +/// # }; +/// # +/// # commands.queue(command); +/// # world.flush(); +/// # +/// # assert!(world.get_entity(child).is_none()); +/// # assert!(world.get_entity(parent).is_none()); +/// ``` +#[derive(Debug)] +pub struct DespawnRecursive { + /// Target entity + entity: Entity, + /// Whether or not this command should output a warning if the entity does not exist + warn: bool, + /// Whether this command will despawn the provided entity (`inclusive`) or just + /// its descendants (`exclusive`). + inclusive: bool, + /// Marker for the + _phantom: PhantomData, +} + +impl DespawnRecursive { + /// Create a new [`DespawnRecursive`] [`Command`]. + pub const fn new(entity: Entity) -> Self { + Self { + entity, + warn: true, + inclusive: true, + _phantom: PhantomData, + } + } + + /// Control whether this [`Command`] should emit a warning when attempting to despawn + /// a nonexistent [`Entity`]. + pub const fn with_warn(mut self, warn: bool) -> Self { + self.warn = warn; + self + } + + /// Control whether this [`Command`] should also despawn the target [`Entity`] (`true`) + /// or on its descendants (`false`). + pub const fn with_inclusion(mut self, inclusive: bool) -> Self { + self.inclusive = inclusive; + self + } +} + +impl Command for DespawnRecursive { + fn apply(self, world: &mut World) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "command", + name = "DespawnRecursive", + entity = bevy_utils::tracing::field::debug(self.entity), + warn = bevy_utils::tracing::field::debug(self.warn) + ) + .entered(); + + let f = if self.warn { + despawn:: + } else { + despawn:: + }; + + if self.inclusive { + visit_recursive_depth_first::(world, self.entity, f); + } else { + visit_recursive_depth_first::(world, self.entity, f); + } + } +} + +fn visit_recursive_depth_first( + world: &mut World, + entity: Entity, + f: fn(&mut World, Entity), +) { + if let Some(component) = world + .get_entity_mut(entity) + .as_mut() + .and_then(EntityWorldMut::take::) + { + component.visit_entities(|e| { + visit_recursive_depth_first::(world, e, f); + }); + } + + if INCLUSIVE { + f(world, entity); + } +} + +fn despawn(world: &mut World, entity: Entity) { + let succeeded = if WARN { + world.despawn(entity) + } else { + world.try_despawn(entity) + }; + + if !succeeded { + debug!("Failed to despawn entity {:?}", entity); + } +} diff --git a/crates/bevy_hierarchy/src/despawn_recursive/ext.rs b/crates/bevy_hierarchy/src/despawn_recursive/ext.rs new file mode 100644 index 0000000000000..4722fcaace7e5 --- /dev/null +++ b/crates/bevy_hierarchy/src/despawn_recursive/ext.rs @@ -0,0 +1,90 @@ +use bevy_ecs::{ + component::Component, + entity::VisitEntities, + system::EntityCommands, + world::{Command, EntityWorldMut}, +}; + +use crate::{Children, DespawnRecursive}; + +/// Trait that holds functions for despawning recursively down the a hierarchy. +pub trait DespawnRecursiveExt: Sized { + /// Despawns the provided [`Entity`]((bevy_ecs::entity::Entity)) alongside all descendants. + fn despawn_recursive(self) { + self.despawn_recursive_with_option::(true); + } + + /// Despawns all descendants of the given [`Entity`](bevy_ecs::entity::Entity). + fn despawn_descendants(&mut self) -> &mut Self { + self.despawn_descendants_with_option::(true) + } + + /// Similar to [`despawn_recursive`](`DespawnRecursiveExt::despawn_recursive`) but does not emit warnings + fn try_despawn_recursive(self) { + self.despawn_recursive_with_option::(false); + } + + /// Similar to [`despawn_descendants`](`DespawnRecursiveExt::despawn_descendants`) but does not emit warnings + fn try_despawn_descendants(&mut self) -> &mut Self { + self.despawn_descendants_with_option::(false) + } + + /// Despawns the provided [`Entity`](bevy_ecs::entity::Entity) alongside all descendants as related via `C`. + /// Optionally warns when attempting to despawn a nonexistent [`Entity`](bevy_ecs::entity::Entity). + fn despawn_recursive_with_option(self, warn: bool); + + /// Despawns from [`Entity`](bevy_ecs::entity::Entity) all descendants as related via `C`. + /// Optionally warns when attempting to despawn a nonexistent [`Entity`](bevy_ecs::entity::Entity). + fn despawn_descendants_with_option( + &mut self, + warn: bool, + ) -> &mut Self; +} + +impl DespawnRecursiveExt for EntityCommands<'_> { + fn despawn_recursive_with_option(mut self, warn: bool) { + let entity = self.id(); + self.commands().queue( + DespawnRecursive::::new(entity) + .with_warn(warn) + .with_inclusion(true), + ); + } + + fn despawn_descendants_with_option( + &mut self, + warn: bool, + ) -> &mut Self { + let entity = self.id(); + self.commands().queue( + DespawnRecursive::::new(entity) + .with_warn(warn) + .with_inclusion(false), + ); + self + } +} + +impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { + fn despawn_recursive_with_option(self, warn: bool) { + DespawnRecursive::::new(self.id()) + .with_warn(warn) + .with_inclusion(true) + .apply(self.into_world_mut()); + } + + fn despawn_descendants_with_option( + &mut self, + warn: bool, + ) -> &mut Self { + let entity = self.id(); + + self.world_scope(|world| { + DespawnRecursive::::new(entity) + .with_warn(warn) + .with_inclusion(false) + .apply(world); + }); + self + } +} diff --git a/crates/bevy_hierarchy/src/despawn_recursive/mod.rs b/crates/bevy_hierarchy/src/despawn_recursive/mod.rs new file mode 100644 index 0000000000000..661cd503f5e02 --- /dev/null +++ b/crates/bevy_hierarchy/src/despawn_recursive/mod.rs @@ -0,0 +1,151 @@ +mod command; +pub use command::DespawnRecursive; + +mod ext; +pub use ext::DespawnRecursiveExt; + +#[cfg(test)] +mod tests { + use bevy_ecs::{ + component::Component, + system::Commands, + world::{CommandQueue, World}, + }; + + use super::DespawnRecursiveExt; + use crate::{ + child_builder::{BuildChildren, ChildBuild}, + Children, + }; + + #[derive(Component, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug)] + struct Idx(u32); + + #[derive(Component, Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] + struct N(String); + + #[test] + fn despawn_recursive() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + let grandparent_entity; + { + let mut commands = Commands::new(&mut queue, &world); + + commands + .spawn((N("Another parent".to_owned()), Idx(0))) + .with_children(|parent| { + parent.spawn((N("Another child".to_owned()), Idx(1))); + }); + + // Create a grandparent entity which will _not_ be deleted + grandparent_entity = commands.spawn((N("Grandparent".to_owned()), Idx(2))).id(); + commands.entity(grandparent_entity).with_children(|parent| { + // Add a child to the grandparent (the "parent"), which will get deleted + parent + .spawn((N("Parent, to be deleted".to_owned()), Idx(3))) + // All descendants of the "parent" should also be deleted. + .with_children(|parent| { + parent + .spawn((N("First Child, to be deleted".to_owned()), Idx(4))) + .with_children(|parent| { + // child + parent.spawn(( + N("First grand child, to be deleted".to_owned()), + Idx(5), + )); + }); + parent.spawn((N("Second child, to be deleted".to_owned()), Idx(6))); + }); + }); + + commands.spawn((N("An innocent bystander".to_owned()), Idx(7))); + } + queue.apply(&mut world); + + let parent_entity = world.get::(grandparent_entity).unwrap()[0]; + + { + let mut commands = Commands::new(&mut queue, &world); + commands.entity(parent_entity).despawn_recursive(); + // despawning the same entity twice should not panic + commands.entity(parent_entity).despawn_recursive(); + } + queue.apply(&mut world); + + let mut results = world + .query::<(&N, &Idx)>() + .iter(&world) + .map(|(a, b)| (a.clone(), *b)) + .collect::>(); + results.sort_unstable_by_key(|(_, index)| *index); + + { + let children = world.get::(grandparent_entity); + assert!( + children.is_none(), + "grandparent should no longer know about its child which has been removed" + ); + } + + assert_eq!( + results, + vec![ + (N("Another parent".to_owned()), Idx(0)), + (N("Another child".to_owned()), Idx(1)), + (N("Grandparent".to_owned()), Idx(2)), + (N("An innocent bystander".to_owned()), Idx(7)) + ] + ); + } + + #[test] + fn despawn_descendants() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + let mut commands = Commands::new(&mut queue, &world); + + let parent = commands.spawn_empty().id(); + let child = commands.spawn_empty().id(); + + commands + .entity(parent) + .add_child(child) + .despawn_descendants(); + + queue.apply(&mut world); + + // The parent's Children component should be removed. + assert!(world.entity(parent).get::().is_none()); + // The child should be despawned. + assert!(world.get_entity(child).is_none()); + } + + #[test] + fn spawn_children_after_despawn_descendants() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + let mut commands = Commands::new(&mut queue, &world); + + let parent = commands.spawn_empty().id(); + let child = commands.spawn_empty().id(); + + commands + .entity(parent) + .add_child(child) + .despawn_descendants() + .with_children(|parent| { + parent.spawn_empty(); + parent.spawn_empty(); + }); + + queue.apply(&mut world); + + // The parent's Children component should still have two children. + let children = world.entity(parent).get::(); + assert!(children.is_some()); + assert_eq!(children.unwrap().len(), 2_usize); + // The original child should be despawned. + assert!(world.get_entity(child).is_none()); + } +} diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs deleted file mode 100644 index affadc5459468..0000000000000 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ /dev/null @@ -1,347 +0,0 @@ -use crate::Children; -use bevy_ecs::{ - entity::Entity, - system::EntityCommands, - world::{Command, EntityWorldMut, World}, -}; -use bevy_utils::tracing::debug; - -/// Despawns the given entity and all its children recursively -#[derive(Debug)] -pub struct DespawnRecursive { - /// Target entity - pub entity: Entity, - /// Whether or not this command should output a warning if the entity does not exist - pub warn: bool, -} - -/// Despawns the given entity's children recursively -#[derive(Debug)] -pub struct DespawnChildrenRecursive { - /// Target entity - pub entity: Entity, - /// Whether or not this command should output a warning if the entity does not exist - pub warn: bool, -} - -/// Function for despawning an entity and all its children -pub fn despawn_with_children_recursive(world: &mut World, entity: Entity, warn: bool) { - despawn_with_children_recursive_inner(world, entity, warn); -} - -// Should only be called by `despawn_with_children_recursive`! -fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity, warn: bool) { - if let Some(children) = world - .get_entity_mut(entity) - .as_mut() - .and_then(EntityWorldMut::take::) - { - for &e in &children { - despawn_with_children_recursive_inner(world, e, warn); - } - } - - if warn { - if !world.despawn(entity) { - debug!("Failed to despawn entity {:?}", entity); - } - } else if !world.try_despawn(entity) { - debug!("Failed to despawn entity {:?}", entity); - } -} - -fn despawn_children_recursive(world: &mut World, entity: Entity, warn: bool) { - if let Some(children) = world - .get_entity_mut(entity) - .as_mut() - .and_then(EntityWorldMut::take::) - { - for &e in &children { - despawn_with_children_recursive_inner(world, e, warn); - } - } - - world.flush(); -} - -impl Command for DespawnRecursive { - fn apply(self, world: &mut World) { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!( - "command", - name = "DespawnRecursive", - entity = bevy_utils::tracing::field::debug(self.entity), - warn = bevy_utils::tracing::field::debug(self.warn) - ) - .entered(); - despawn_with_children_recursive(world, self.entity, self.warn); - } -} - -impl Command for DespawnChildrenRecursive { - fn apply(self, world: &mut World) { - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!( - "command", - name = "DespawnChildrenRecursive", - entity = bevy_utils::tracing::field::debug(self.entity), - warn = bevy_utils::tracing::field::debug(self.warn) - ) - .entered(); - - despawn_children_recursive(world, self.entity, self.warn); - } -} - -/// Trait that holds functions for despawning recursively down the transform hierarchy -pub trait DespawnRecursiveExt { - /// Despawns the provided entity alongside all descendants. - fn despawn_recursive(self); - - /// Despawns all descendants of the given entity. - fn despawn_descendants(&mut self) -> &mut Self; - - /// Similar to [`Self::despawn_recursive`] but does not emit warnings - fn try_despawn_recursive(self); - - /// Similar to [`Self::despawn_descendants`] but does not emit warnings - fn try_despawn_descendants(&mut self) -> &mut Self; -} - -impl DespawnRecursiveExt for EntityCommands<'_> { - /// Despawns the provided entity and its children. - /// This will emit warnings for any entity that does not exist. - fn despawn_recursive(mut self) { - let entity = self.id(); - self.commands() - .queue(DespawnRecursive { entity, warn: true }); - } - - fn despawn_descendants(&mut self) -> &mut Self { - let entity = self.id(); - self.commands() - .queue(DespawnChildrenRecursive { entity, warn: true }); - self - } - - /// Despawns the provided entity and its children. - /// This will never emit warnings. - fn try_despawn_recursive(mut self) { - let entity = self.id(); - self.commands().queue(DespawnRecursive { - entity, - warn: false, - }); - } - - fn try_despawn_descendants(&mut self) -> &mut Self { - let entity = self.id(); - self.commands().queue(DespawnChildrenRecursive { - entity, - warn: false, - }); - self - } -} - -fn despawn_recursive_inner(world: EntityWorldMut, warn: bool) { - let entity = world.id(); - - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!( - "despawn_recursive", - entity = bevy_utils::tracing::field::debug(entity), - warn = bevy_utils::tracing::field::debug(warn) - ) - .entered(); - - despawn_with_children_recursive(world.into_world_mut(), entity, warn); -} - -fn despawn_descendants_inner<'v, 'w>( - world: &'v mut EntityWorldMut<'w>, - warn: bool, -) -> &'v mut EntityWorldMut<'w> { - let entity = world.id(); - - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!( - "despawn_descendants", - entity = bevy_utils::tracing::field::debug(entity), - warn = bevy_utils::tracing::field::debug(warn) - ) - .entered(); - - world.world_scope(|world| { - despawn_children_recursive(world, entity, warn); - }); - world -} - -impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { - /// Despawns the provided entity and its children. - /// This will emit warnings for any entity that does not exist. - fn despawn_recursive(self) { - despawn_recursive_inner(self, true); - } - - fn despawn_descendants(&mut self) -> &mut Self { - despawn_descendants_inner(self, true) - } - - /// Despawns the provided entity and its children. - /// This will not emit warnings. - fn try_despawn_recursive(self) { - despawn_recursive_inner(self, false); - } - - fn try_despawn_descendants(&mut self) -> &mut Self { - despawn_descendants_inner(self, false) - } -} - -#[cfg(test)] -mod tests { - use bevy_ecs::{ - component::Component, - system::Commands, - world::{CommandQueue, World}, - }; - - use super::DespawnRecursiveExt; - use crate::{ - child_builder::{BuildChildren, ChildBuild}, - Children, - }; - - #[derive(Component, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug)] - struct Idx(u32); - - #[derive(Component, Clone, PartialEq, Eq, Ord, PartialOrd, Debug)] - struct N(String); - - #[test] - fn despawn_recursive() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let grandparent_entity; - { - let mut commands = Commands::new(&mut queue, &world); - - commands - .spawn((N("Another parent".to_owned()), Idx(0))) - .with_children(|parent| { - parent.spawn((N("Another child".to_owned()), Idx(1))); - }); - - // Create a grandparent entity which will _not_ be deleted - grandparent_entity = commands.spawn((N("Grandparent".to_owned()), Idx(2))).id(); - commands.entity(grandparent_entity).with_children(|parent| { - // Add a child to the grandparent (the "parent"), which will get deleted - parent - .spawn((N("Parent, to be deleted".to_owned()), Idx(3))) - // All descendants of the "parent" should also be deleted. - .with_children(|parent| { - parent - .spawn((N("First Child, to be deleted".to_owned()), Idx(4))) - .with_children(|parent| { - // child - parent.spawn(( - N("First grand child, to be deleted".to_owned()), - Idx(5), - )); - }); - parent.spawn((N("Second child, to be deleted".to_owned()), Idx(6))); - }); - }); - - commands.spawn((N("An innocent bystander".to_owned()), Idx(7))); - } - queue.apply(&mut world); - - let parent_entity = world.get::(grandparent_entity).unwrap()[0]; - - { - let mut commands = Commands::new(&mut queue, &world); - commands.entity(parent_entity).despawn_recursive(); - // despawning the same entity twice should not panic - commands.entity(parent_entity).despawn_recursive(); - } - queue.apply(&mut world); - - let mut results = world - .query::<(&N, &Idx)>() - .iter(&world) - .map(|(a, b)| (a.clone(), *b)) - .collect::>(); - results.sort_unstable_by_key(|(_, index)| *index); - - { - let children = world.get::(grandparent_entity); - assert!( - children.is_none(), - "grandparent should no longer know about its child which has been removed" - ); - } - - assert_eq!( - results, - vec![ - (N("Another parent".to_owned()), Idx(0)), - (N("Another child".to_owned()), Idx(1)), - (N("Grandparent".to_owned()), Idx(2)), - (N("An innocent bystander".to_owned()), Idx(7)) - ] - ); - } - - #[test] - fn despawn_descendants() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn_empty().id(); - let child = commands.spawn_empty().id(); - - commands - .entity(parent) - .add_child(child) - .despawn_descendants(); - - queue.apply(&mut world); - - // The parent's Children component should be removed. - assert!(world.entity(parent).get::().is_none()); - // The child should be despawned. - assert!(world.get_entity(child).is_none()); - } - - #[test] - fn spawn_children_after_despawn_descendants() { - let mut world = World::default(); - let mut queue = CommandQueue::default(); - let mut commands = Commands::new(&mut queue, &world); - - let parent = commands.spawn_empty().id(); - let child = commands.spawn_empty().id(); - - commands - .entity(parent) - .add_child(child) - .despawn_descendants() - .with_children(|parent| { - parent.spawn_empty(); - parent.spawn_empty(); - }); - - queue.apply(&mut world); - - // The parent's Children component should still have two children. - let children = world.entity(parent).get::(); - assert!(children.is_some()); - assert_eq!(children.unwrap().len(), 2_usize); - // The original child should be despawned. - assert!(world.get_entity(child).is_none()); - } -} diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index 82d3270571bad..b39ffefbf6744 100755 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -62,8 +62,8 @@ pub use one_to_many::{OneToManyEvent, OneToManyMany, OneToManyOne}; mod family; pub use family::*; -mod hierarchy; -pub use hierarchy::*; +mod despawn_recursive; +pub use despawn_recursive::*; mod child_builder; pub use child_builder::*; @@ -79,7 +79,7 @@ pub use query_extension::*; /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { #[doc(hidden)] - pub use crate::{child_builder::*, family::*, hierarchy::*, query_extension::*}; + pub use crate::{child_builder::*, despawn_recursive::*, family::*, query_extension::*}; #[doc(hidden)] #[cfg(feature = "bevy_app")] diff --git a/crates/bevy_hierarchy/src/one_to_many/many.rs b/crates/bevy_hierarchy/src/one_to_many/many.rs index d4d1cb362464b..c3e49eda13552 100644 --- a/crates/bevy_hierarchy/src/one_to_many/many.rs +++ b/crates/bevy_hierarchy/src/one_to_many/many.rs @@ -9,9 +9,7 @@ use bevy_ecs::{ event::Events, world::{DeferredWorld, World}, }; -use core::fmt::Debug; -use core::marker::PhantomData; -use core::ops::Deref; +use core::{fmt::Debug, marker::PhantomData, ops::Deref}; use smallvec::SmallVec; use super::{OneToManyEvent, OneToManyOne}; diff --git a/crates/bevy_hierarchy/src/one_to_many/one.rs b/crates/bevy_hierarchy/src/one_to_many/one.rs index b83bd04f7e490..c9275182b29c8 100644 --- a/crates/bevy_hierarchy/src/one_to_many/one.rs +++ b/crates/bevy_hierarchy/src/one_to_many/one.rs @@ -10,9 +10,7 @@ use bevy_ecs::{ traversal::Traversal, world::{DeferredWorld, FromWorld, World}, }; -use core::fmt::Debug; -use core::marker::PhantomData; -use core::ops::Deref; +use core::{fmt::Debug, marker::PhantomData, ops::Deref}; use super::{OneToManyEvent, OneToManyMany}; diff --git a/crates/bevy_hierarchy/src/one_to_one/component.rs b/crates/bevy_hierarchy/src/one_to_one/component.rs index b8ce5e697ba08..2aa811e6b1226 100644 --- a/crates/bevy_hierarchy/src/one_to_one/component.rs +++ b/crates/bevy_hierarchy/src/one_to_one/component.rs @@ -10,9 +10,7 @@ use bevy_ecs::{ traversal::Traversal, world::{DeferredWorld, FromWorld, World}, }; -use core::fmt::Debug; -use core::marker::PhantomData; -use core::ops::Deref; +use core::{fmt::Debug, marker::PhantomData, ops::Deref}; use super::OneToOneEvent; diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 1965b24096d89..ddad6e175f5de 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -472,11 +472,9 @@ mod tests { query::Without, schedule::{apply_deferred, IntoSystemConfigs, Schedule}, system::RunSystemOnce, - world::World, - }; - use bevy_hierarchy::{ - despawn_with_children_recursive, BuildChildren, ChildBuild, Children, Parent, + world::{Command, World}, }; + use bevy_hierarchy::{BuildChildren, ChildBuild, Children, DespawnRecursive, Parent}; use bevy_math::{vec2, Rect, UVec2, Vec2}; use bevy_render::{ camera::{ManualTextureViews, OrthographicProjection}, @@ -793,7 +791,9 @@ mod tests { } // despawn the parent entity and its descendants - despawn_with_children_recursive(&mut world, ui_parent_entity, true); + DespawnRecursive::::new(ui_parent_entity) + .with_warn(true) + .apply(&mut world); ui_schedule.run(&mut world); From c3ffe42b07173a643214583e6c5e3bb5a36096a2 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 6 Oct 2024 13:33:40 +1100 Subject: [PATCH 10/25] Fix benchmark --- benches/benches/bevy_ecs/world/despawn_recursive.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/benches/benches/bevy_ecs/world/despawn_recursive.rs b/benches/benches/bevy_ecs/world/despawn_recursive.rs index 482086ab17444..db2ba631c2d01 100644 --- a/benches/benches/bevy_ecs/world/despawn_recursive.rs +++ b/benches/benches/bevy_ecs/world/despawn_recursive.rs @@ -1,7 +1,5 @@ use bevy_ecs::prelude::*; -use bevy_hierarchy::despawn_with_children_recursive; -use bevy_hierarchy::BuildChildren; -use bevy_hierarchy::ChildBuild; +use bevy_hierarchy::{Children, DespawnRecursive, BuildChildren, ChildBuild}; use criterion::Criterion; use glam::*; @@ -29,7 +27,7 @@ pub fn world_despawn_recursive(criterion: &mut Criterion) { group.bench_function(format!("{}_entities", entity_count), |bencher| { bencher.iter(|| { ents.iter().for_each(|e| { - despawn_with_children_recursive(&mut world, *e, true); + DespawnRecursive::::new(*e).apply(&mut world); }); }); }); From add4631fe1244d8d9fe162b185421bfa2e79c163 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 6 Oct 2024 14:30:24 +1100 Subject: [PATCH 11/25] Add inadvertently removed sorting methods --- crates/bevy_hierarchy/src/one_to_many/many.rs | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/crates/bevy_hierarchy/src/one_to_many/many.rs b/crates/bevy_hierarchy/src/one_to_many/many.rs index c3e49eda13552..0c5db9bb0ec93 100644 --- a/crates/bevy_hierarchy/src/one_to_many/many.rs +++ b/crates/bevy_hierarchy/src/one_to_many/many.rs @@ -213,4 +213,92 @@ impl OneToManyMany { pub(super) fn entities_mut(&mut self) -> &mut SmallVec<[Entity; 8]> { &mut self.entities } + + /// Swaps the entity at `a_index` with the entity at `b_index`. + #[inline] + pub fn swap(&mut self, a_index: usize, b_index: usize) { + self.entities.swap(a_index, b_index); + } + + /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided comparator function. + /// + /// For the underlying implementation, see [`slice::sort_by`]. + /// + /// For the unstable version, see [`sort_unstable_by`](OneToManyMany::sort_unstable_by). + /// + /// See also [`sort_by_key`](OneToManyMany::sort_by_key), [`sort_by_cached_key`](OneToManyMany::sort_by_cached_key). + #[inline] + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, + { + self.entities.sort_by(compare); + } + + /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. + /// + /// For the underlying implementation, see [`slice::sort_by_key`]. + /// + /// For the unstable version, see [`sort_unstable_by_key`](OneToManyMany::sort_unstable_by_key). + /// + /// See also [`sort_by`](OneToManyMany::sort_by), [`sort_by_cached_key`](OneToManyMany::sort_by_cached_key). + #[inline] + pub fn sort_by_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.entities.sort_by_key(compare); + } + + /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. Only evaluates each key at most + /// once per sort, caching the intermediate results in memory. + /// + /// For the underlying implementation, see [`slice::sort_by_cached_key`]. + /// + /// See also [`sort_by`](OneToManyMany::sort_by), [`sort_by_key`](OneToManyMany::sort_by_key). + #[inline] + pub fn sort_by_cached_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.entities.sort_by_cached_key(compare); + } + + /// Sorts entities [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided comparator function. + /// + /// For the underlying implementation, see [`slice::sort_unstable_by`]. + /// + /// For the stable version, see [`sort_by`](OneToManyMany::sort_by). + /// + /// See also [`sort_unstable_by_key`](OneToManyMany::sort_unstable_by_key). + #[inline] + pub fn sort_unstable_by(&mut self, compare: F) + where + F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, + { + self.entities.sort_unstable_by(compare); + } + + /// Sorts entities [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. + /// + /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. + /// + /// For the stable version, see [`sort_by_key`](OneToManyMany::sort_by_key). + /// + /// See also [`sort_unstable_by`](OneToManyMany::sort_unstable_by). + #[inline] + pub fn sort_unstable_by_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.entities.sort_unstable_by_key(compare); + } } From 3a8f35c8e97d32228ff373fc1482f7e58be9074f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 17 Oct 2024 21:30:15 +1100 Subject: [PATCH 12/25] Rename `OneToManyOne` and `OneToManyMany` Co-Authored-By: Amjad Mohamed --- crates/bevy_hierarchy/src/family.rs | 6 +-- crates/bevy_hierarchy/src/lib.rs | 2 +- .../bevy_hierarchy/src/one_to_many/event.rs | 18 +++---- crates/bevy_hierarchy/src/one_to_many/many.rs | 52 +++++++++---------- crates/bevy_hierarchy/src/one_to_many/mod.rs | 8 +-- crates/bevy_hierarchy/src/one_to_many/one.rs | 40 +++++++------- 6 files changed, 63 insertions(+), 63 deletions(-) diff --git a/crates/bevy_hierarchy/src/family.rs b/crates/bevy_hierarchy/src/family.rs index ccc1009838c95..3b8ff2f4e8e9d 100644 --- a/crates/bevy_hierarchy/src/family.rs +++ b/crates/bevy_hierarchy/src/family.rs @@ -1,4 +1,4 @@ -use crate::{OneToManyEvent, OneToManyMany, OneToManyOne}; +use crate::{ManyToOne, OneToMany, OneToManyEvent}; /// A familial relationship #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] @@ -16,7 +16,7 @@ pub struct Family; /// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt /// [`Query`]: bevy_ecs::system::Query /// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children -pub type Parent = OneToManyOne; +pub type Parent = ManyToOne; /// Contains references to the child entities of this entity. /// @@ -30,7 +30,7 @@ pub type Parent = OneToManyOne; /// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt /// [`Query`]: bevy_ecs::system::Query /// [`BuildChildren::with_children`]: crate::child_builder::BuildChildren::with_children -pub type Children = OneToManyMany; +pub type Children = OneToMany; /// An [`Event`] that is fired whenever there is a change in the world's hierarchy. /// diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index b39ffefbf6744..27ff330d2ea90 100755 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -57,7 +57,7 @@ mod one_to_one; pub use one_to_one::{OneToOne, OneToOneEvent}; mod one_to_many; -pub use one_to_many::{OneToManyEvent, OneToManyMany, OneToManyOne}; +pub use one_to_many::{ManyToOne, OneToMany, OneToManyEvent}; mod family; pub use family::*; diff --git a/crates/bevy_hierarchy/src/one_to_many/event.rs b/crates/bevy_hierarchy/src/one_to_many/event.rs index 4ec41b726a303..51cddb374aa15 100644 --- a/crates/bevy_hierarchy/src/one_to_many/event.rs +++ b/crates/bevy_hierarchy/src/one_to_many/event.rs @@ -2,12 +2,12 @@ use core::marker::PhantomData; use bevy_ecs::{entity::Entity, event::Event}; -/// A One-to-Many [relationship](crate::OneToManyOne) [`Event`]. +/// A One-to-Many [relationship](crate::ManyToOne) [`Event`]. #[derive(Event)] pub enum OneToManyEvent { - /// A [relationship](crate::OneToManyOne) was added between two [entities](Entity). + /// A [relationship](crate::ManyToOne) was added between two [entities](Entity). Added(OneToManyEventDetails), - /// A [relationship](crate::OneToManyOne) was removed from two [entities](Entity). + /// A [relationship](crate::ManyToOne) was removed from two [entities](Entity). Removed(OneToManyEventDetails), } @@ -22,14 +22,14 @@ impl OneToManyEvent { Self::Removed(OneToManyEventDetails::new(many, one)) } - /// Get the [`Entity`] that has the [`OneToManyMany`](crate::OneToManyMany) component. + /// Get the [`Entity`] that has the [`OneToMany`](crate::OneToMany) component. pub const fn many(&self) -> Entity { match self { Self::Added(details) | Self::Removed(details) => details.many(), } } - /// Get the [`Entity`] that has the [`OneToManyOne`](crate::OneToManyOne) component. + /// Get the [`Entity`] that has the [`ManyToOne`](crate::ManyToOne) component. pub const fn one(&self) -> Entity { match self { Self::Added(details) | Self::Removed(details) => details.one(), @@ -74,8 +74,8 @@ pub struct OneToManyEventDetails { impl OneToManyEventDetails { /// Create a new [`OneToManyEventDetails`] for a `many` and a `one` [`Entity`]. - /// The `many` [`Entity`] has the [`OneToManyMany`](crate::OneToManyMany) component, - /// while the `one` [`Entity`] has the [`OneToManyOne`](crate::OneToManyOne) component. + /// The `many` [`Entity`] has the [`OneToMany`](crate::OneToMany) component, + /// while the `one` [`Entity`] has the [`ManyToOne`](crate::ManyToOne) component. pub const fn new(many: Entity, one: Entity) -> Self { Self { many, @@ -84,12 +84,12 @@ impl OneToManyEventDetails { } } - /// Get the [`Entity`] that has the [`OneToManyMany`](crate::OneToManyMany) component. + /// Get the [`Entity`] that has the [`OneToMany`](crate::OneToMany) component. pub const fn many(&self) -> Entity { self.many } - /// Get the [`Entity`] that has the [`OneToManyOne`](crate::OneToManyOne) component. + /// Get the [`Entity`] that has the [`ManyToOne`](crate::ManyToOne) component. pub const fn one(&self) -> Entity { self.one } diff --git a/crates/bevy_hierarchy/src/one_to_many/many.rs b/crates/bevy_hierarchy/src/one_to_many/many.rs index ba454b2f13538..5e284988bd2d9 100644 --- a/crates/bevy_hierarchy/src/one_to_many/many.rs +++ b/crates/bevy_hierarchy/src/one_to_many/many.rs @@ -12,7 +12,7 @@ use bevy_ecs::{ use core::{fmt::Debug, marker::PhantomData, ops::Deref}; use smallvec::SmallVec; -use super::{OneToManyEvent, OneToManyOne}; +use super::{ManyToOne, OneToManyEvent}; /// Represents one half of a one-to-many relationship between an [`Entity`] and some number of other [entities](Entity). /// @@ -36,13 +36,13 @@ use super::{OneToManyEvent, OneToManyOne}; FromWorld ) )] -pub struct OneToManyMany { +pub struct OneToMany { entities: SmallVec<[Entity; 8]>, #[cfg_attr(feature = "reflect", reflect(ignore))] _phantom: PhantomData, } -impl OneToManyMany { +impl OneToMany { fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { world.commands().queue(move |world: &mut World| { let b_ids_len = world @@ -66,11 +66,11 @@ impl OneToManyMany { let b_id = b.id(); let b_points_to_a = b - .get::>() + .get::>() .is_some_and(|b_relationship| b_relationship.get() == a_id); if !b_points_to_a { - b.insert(OneToManyOne::::new(a_id)); + b.insert(ManyToOne::::new(a_id)); if let Some(mut moved) = world.get_resource_mut::>>() { moved.send(OneToManyEvent::::added(a_id, b_id)); @@ -99,12 +99,12 @@ impl OneToManyMany { let b_points_to_a = world .get_entity(b_id) .ok() - .and_then(|b| b.get::>()) + .and_then(|b| b.get::>()) .is_some_and(|b_relationship| b_relationship.get() == a_id); if b_points_to_a && !a_points_to_b { if let Ok(mut b) = world.get_entity_mut(b_id) { - b.remove::>(); + b.remove::>(); } if let Some(mut moved) = world.get_resource_mut::>>() { @@ -116,24 +116,24 @@ impl OneToManyMany { } } -impl PartialEq for OneToManyMany { +impl PartialEq for OneToMany { fn eq(&self, other: &Self) -> bool { self.entities == other.entities } } -impl Eq for OneToManyMany {} +impl Eq for OneToMany {} -impl Debug for OneToManyMany { +impl Debug for OneToMany { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("OneToManyMany") + f.debug_tuple("OneToMany") .field(&self.entities) .field(&core::any::type_name::()) .finish() } } -impl VisitEntitiesMut for OneToManyMany { +impl VisitEntitiesMut for OneToMany { fn visit_entities_mut(&mut self, mut f: F) { for entity in &mut self.entities { f(entity); @@ -141,7 +141,7 @@ impl VisitEntitiesMut for OneToManyMany { } } -impl Deref for OneToManyMany { +impl Deref for OneToMany { type Target = [Entity]; #[inline(always)] @@ -150,7 +150,7 @@ impl Deref for OneToManyMany { } } -impl<'a, R> IntoIterator for &'a OneToManyMany { +impl<'a, R> IntoIterator for &'a OneToMany { type Item = ::Item; type IntoIter = core::slice::Iter<'a, Entity>; @@ -161,19 +161,19 @@ impl<'a, R> IntoIterator for &'a OneToManyMany { } } -impl FromIterator for OneToManyMany { +impl FromIterator for OneToMany { fn from_iter>(iter: T) -> Self { Self::from_smallvec(iter.into_iter().collect()) } } -impl Default for OneToManyMany { +impl Default for OneToMany { fn default() -> Self { Self::new() } } -impl OneToManyMany { +impl OneToMany { /// Gets the other [`Entity`] as a slice of length 1. #[inline(always)] pub fn as_slice(&self) -> &[Entity] { @@ -229,9 +229,9 @@ impl OneToManyMany { /// /// For the underlying implementation, see [`slice::sort_by`]. /// - /// For the unstable version, see [`sort_unstable_by`](OneToManyMany::sort_unstable_by). + /// For the unstable version, see [`sort_unstable_by`](OneToMany::sort_unstable_by). /// - /// See also [`sort_by_key`](OneToManyMany::sort_by_key), [`sort_by_cached_key`](OneToManyMany::sort_by_cached_key). + /// See also [`sort_by_key`](OneToMany::sort_by_key), [`sort_by_cached_key`](OneToMany::sort_by_cached_key). #[inline] pub fn sort_by(&mut self, compare: F) where @@ -245,9 +245,9 @@ impl OneToManyMany { /// /// For the underlying implementation, see [`slice::sort_by_key`]. /// - /// For the unstable version, see [`sort_unstable_by_key`](OneToManyMany::sort_unstable_by_key). + /// For the unstable version, see [`sort_unstable_by_key`](OneToMany::sort_unstable_by_key). /// - /// See also [`sort_by`](OneToManyMany::sort_by), [`sort_by_cached_key`](OneToManyMany::sort_by_cached_key). + /// See also [`sort_by`](OneToMany::sort_by), [`sort_by_cached_key`](OneToMany::sort_by_cached_key). #[inline] pub fn sort_by_key(&mut self, compare: F) where @@ -263,7 +263,7 @@ impl OneToManyMany { /// /// For the underlying implementation, see [`slice::sort_by_cached_key`]. /// - /// See also [`sort_by`](OneToManyMany::sort_by), [`sort_by_key`](OneToManyMany::sort_by_key). + /// See also [`sort_by`](OneToMany::sort_by), [`sort_by_key`](OneToMany::sort_by_key). #[inline] pub fn sort_by_cached_key(&mut self, compare: F) where @@ -278,9 +278,9 @@ impl OneToManyMany { /// /// For the underlying implementation, see [`slice::sort_unstable_by`]. /// - /// For the stable version, see [`sort_by`](OneToManyMany::sort_by). + /// For the stable version, see [`sort_by`](OneToMany::sort_by). /// - /// See also [`sort_unstable_by_key`](OneToManyMany::sort_unstable_by_key). + /// See also [`sort_unstable_by_key`](OneToMany::sort_unstable_by_key). #[inline] pub fn sort_unstable_by(&mut self, compare: F) where @@ -294,9 +294,9 @@ impl OneToManyMany { /// /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. /// - /// For the stable version, see [`sort_by_key`](OneToManyMany::sort_by_key). + /// For the stable version, see [`sort_by_key`](OneToMany::sort_by_key). /// - /// See also [`sort_unstable_by`](OneToManyMany::sort_unstable_by). + /// See also [`sort_unstable_by`](OneToMany::sort_unstable_by). #[inline] pub fn sort_unstable_by_key(&mut self, compare: F) where diff --git a/crates/bevy_hierarchy/src/one_to_many/mod.rs b/crates/bevy_hierarchy/src/one_to_many/mod.rs index 71cdee7508d3c..7c980bfd1dfbe 100644 --- a/crates/bevy_hierarchy/src/one_to_many/mod.rs +++ b/crates/bevy_hierarchy/src/one_to_many/mod.rs @@ -2,10 +2,10 @@ mod event; pub use event::OneToManyEvent; mod one; -pub use one::OneToManyOne; +pub use one::ManyToOne; mod many; -pub use many::OneToManyMany; +pub use many::OneToMany; #[cfg(test)] mod tests { @@ -17,10 +17,10 @@ mod tests { struct Family; /// Shorthand for a Parent in a Family relationship - type Parent = OneToManyOne; + type Parent = ManyToOne; /// Shorthand for a Parent in a Family relationship - type Children = OneToManyMany; + type Children = OneToMany; #[test] fn simple_add_then_remove() { diff --git a/crates/bevy_hierarchy/src/one_to_many/one.rs b/crates/bevy_hierarchy/src/one_to_many/one.rs index 7c01285cfb7ea..bb1bdb24426ce 100644 --- a/crates/bevy_hierarchy/src/one_to_many/one.rs +++ b/crates/bevy_hierarchy/src/one_to_many/one.rs @@ -12,7 +12,7 @@ use bevy_ecs::{ }; use core::{fmt::Debug, marker::PhantomData, ops::Deref}; -use super::{OneToManyEvent, OneToManyMany}; +use super::{OneToMany, OneToManyEvent}; /// Represents one half of a one-to-many relationship between an [`Entity`] and some number of other [entities](Entity). /// @@ -36,13 +36,13 @@ use super::{OneToManyEvent, OneToManyMany}; FromWorld ) )] -pub struct OneToManyOne { +pub struct ManyToOne { entity: Entity, #[cfg_attr(feature = "reflect", reflect(ignore))] _phantom: PhantomData, } -impl OneToManyOne { +impl ManyToOne { fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { world.commands().queue(move |world: &mut World| { let b = world @@ -57,14 +57,14 @@ impl OneToManyOne { let b_id = b.id(); let b_points_to_a = b - .get::>() + .get::>() .is_some_and(|b_relationship| b_relationship.contains(&a_id)); if !b_points_to_a { - if let Some(mut component) = b.get_mut::>() { + if let Some(mut component) = b.get_mut::>() { component.entities_mut().push(a_id); } else { - b.insert(OneToManyMany::::new().with(a_id)); + b.insert(OneToMany::::new().with(a_id)); } if let Some(mut moved) = world.get_resource_mut::>>() { @@ -91,16 +91,16 @@ impl OneToManyOne { let b_points_to_a = world .get_entity(b_id) .ok() - .and_then(|b| b.get::>()) + .and_then(|b| b.get::>()) .is_some_and(|b_relationship| b_relationship.contains(&a_id)); if b_points_to_a && !a_points_to_b { if let Ok(mut b) = world.get_entity_mut(b_id) { - if let Some(mut component) = b.get_mut::>() { + if let Some(mut component) = b.get_mut::>() { component.entities_mut().retain(|&mut e| e != a_id); if component.is_empty() { - b.remove::>(); + b.remove::>(); } } } @@ -113,30 +113,30 @@ impl OneToManyOne { } } -impl PartialEq for OneToManyOne { +impl PartialEq for ManyToOne { fn eq(&self, other: &Self) -> bool { self.entity == other.entity } } -impl Eq for OneToManyOne {} +impl Eq for ManyToOne {} -impl Debug for OneToManyOne { +impl Debug for ManyToOne { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("OneToManyOne") + f.debug_tuple("ManyToOne") .field(&self.entity) .field(&core::any::type_name::()) .finish() } } -impl VisitEntities for OneToManyOne { +impl VisitEntities for ManyToOne { fn visit_entities(&self, mut f: F) { f(self.entity); } } -impl VisitEntitiesMut for OneToManyOne { +impl VisitEntitiesMut for ManyToOne { fn visit_entities_mut(&mut self, mut f: F) { f(&mut self.entity); } @@ -146,7 +146,7 @@ impl VisitEntitiesMut for OneToManyOne { // This is because Reflect deserialize by creating an instance and apply a patch on top. // However OneToOne should only ever be set with a real user-defined entity. It's worth looking into // better ways to handle cases like this. -impl FromWorld for OneToManyOne { +impl FromWorld for ManyToOne { #[inline(always)] fn from_world(_world: &mut World) -> Self { Self { @@ -156,7 +156,7 @@ impl FromWorld for OneToManyOne { } } -impl Deref for OneToManyOne { +impl Deref for ManyToOne { type Target = Entity; #[inline(always)] @@ -167,16 +167,16 @@ impl Deref for OneToManyOne { /// This provides generalized hierarchy traversal for use in [event propagation]. /// -/// `OneToManyOne::traverse` will never form loops in properly-constructed hierarchies. +/// `ManyToOne::traverse` will never form loops in properly-constructed hierarchies. /// /// [event propagation]: bevy_ecs::observer::Trigger::propagate -impl Traversal for &OneToManyOne { +impl Traversal for &ManyToOne { fn traverse(item: Self::Item<'_>) -> Option { Some(item.entity) } } -impl OneToManyOne { +impl ManyToOne { /// Gets the [`Entity`] ID of the other member of this one-to-many relationship. #[inline(always)] pub fn get(&self) -> Entity { From 4493faa865ebec1723a6f2d4ec70d59e0771c8e9 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 19 Oct 2024 19:27:42 +1100 Subject: [PATCH 13/25] Remove redundant `on_remove` hooks `on_replace` is always called when `on_remove` would be called, so only `on_replace` is required. Co-Authored-By: Nuutti Kotivuori --- crates/bevy_hierarchy/src/one_to_many/many.rs | 1 - crates/bevy_hierarchy/src/one_to_many/one.rs | 1 - crates/bevy_hierarchy/src/one_to_one/component.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/crates/bevy_hierarchy/src/one_to_many/many.rs b/crates/bevy_hierarchy/src/one_to_many/many.rs index 5e284988bd2d9..465f2cea94b59 100644 --- a/crates/bevy_hierarchy/src/one_to_many/many.rs +++ b/crates/bevy_hierarchy/src/one_to_many/many.rs @@ -21,7 +21,6 @@ use super::{ManyToOne, OneToManyEvent}; #[component( on_insert = Self::associate, on_replace = Self::disassociate, - on_remove = Self::disassociate )] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( diff --git a/crates/bevy_hierarchy/src/one_to_many/one.rs b/crates/bevy_hierarchy/src/one_to_many/one.rs index bb1bdb24426ce..271592e5e8ac7 100644 --- a/crates/bevy_hierarchy/src/one_to_many/one.rs +++ b/crates/bevy_hierarchy/src/one_to_many/one.rs @@ -21,7 +21,6 @@ use super::{OneToMany, OneToManyEvent}; #[component( on_insert = Self::associate, on_replace = Self::disassociate, - on_remove = Self::disassociate )] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( diff --git a/crates/bevy_hierarchy/src/one_to_one/component.rs b/crates/bevy_hierarchy/src/one_to_one/component.rs index 3777689078b4c..2d26ea9460747 100644 --- a/crates/bevy_hierarchy/src/one_to_one/component.rs +++ b/crates/bevy_hierarchy/src/one_to_one/component.rs @@ -21,7 +21,6 @@ use super::OneToOneEvent; #[component( on_insert = Self::associate, on_replace = Self::disassociate, - on_remove = Self::disassociate )] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( From 0aad67321da9f779da545b72d171a1e13d9198df Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sat, 19 Oct 2024 19:37:37 +1100 Subject: [PATCH 14/25] Updated documentation on `Parent` and `Children` With component hooks, it's now possible to work with these components easily and safely without the builder methods. However I have also explicitly called out the need for a sync point for the relationship invariants to be upheld. Co-Authored-By: Nuutti Kotivuori --- crates/bevy_hierarchy/src/family.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/bevy_hierarchy/src/family.rs b/crates/bevy_hierarchy/src/family.rs index 3b8ff2f4e8e9d..3ee7441b8de3d 100644 --- a/crates/bevy_hierarchy/src/family.rs +++ b/crates/bevy_hierarchy/src/family.rs @@ -8,8 +8,9 @@ pub struct Family; /// This component should only be present on entities that actually have a parent entity. /// /// Parent entity must have this entity stored in its [`Children`] component. -/// It is hard to set up parent/child relationships manually, -/// consider using higher level utilities like [`BuildChildren::with_children`]. +/// This invariant will be upheld using component hooks, but will only be valid after a sync point, +/// when deferred commands are applied. +/// To avoid this delay, consider using higher level utilities like [`BuildChildren::with_children`]. /// /// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. /// @@ -21,9 +22,10 @@ pub type Parent = ManyToOne; /// Contains references to the child entities of this entity. /// /// Each child must contain a [`Parent`] component that points back to this entity. -/// This component rarely needs to be created manually, -/// consider using higher level utilities like [`BuildChildren::with_children`] -/// which are safer and easier to use. +/// This component rarely needs to be created manually, the recommended way to +/// work with this component is to insert [`Parent`] on all child entities, as +/// component hooks will ensure this component is available. +/// You may also consider using higher level utilities like [`BuildChildren::with_children`]. /// /// See [`HierarchyQueryExt`] for hierarchy related methods on [`Query`]. /// From ec1543cf2d39847d95a51605fbf7359653bf367f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 20 Oct 2024 09:31:59 +1100 Subject: [PATCH 15/25] Refactored Relationships into a new trait Trait `Relationship` is private as it's only relevant to the setting up and management of relationships, which should be done by users with the `OneToOne`, `ManyToOne`, etc. components. This refactor allowed making directed `OneToOne`, and un/directed `ManyToMany` relationships trivial. --- crates/bevy_hierarchy/src/child_builder.rs | 12 +- crates/bevy_hierarchy/src/family.rs | 4 +- crates/bevy_hierarchy/src/lib.rs | 13 +- .../{one_to_many/many.rs => many_to_many.rs} | 216 +++++---- .../{one_to_many/one.rs => many_to_one.rs} | 112 ++--- crates/bevy_hierarchy/src/one_to_many.rs | 434 ++++++++++++++++++ .../bevy_hierarchy/src/one_to_many/event.rs | 121 ----- crates/bevy_hierarchy/src/one_to_many/mod.rs | 176 ------- .../src/{one_to_one/mod.rs => one_to_one.rs} | 175 ++++++- .../src/one_to_one/component.rs | 188 -------- crates/bevy_hierarchy/src/one_to_one/event.rs | 123 ----- crates/bevy_hierarchy/src/relationship.rs | 257 +++++++++++ 12 files changed, 1028 insertions(+), 803 deletions(-) rename crates/bevy_hierarchy/src/{one_to_many/many.rs => many_to_many.rs} (60%) rename crates/bevy_hierarchy/src/{one_to_many/one.rs => many_to_one.rs} (51%) create mode 100644 crates/bevy_hierarchy/src/one_to_many.rs delete mode 100644 crates/bevy_hierarchy/src/one_to_many/event.rs delete mode 100644 crates/bevy_hierarchy/src/one_to_many/mod.rs rename crates/bevy_hierarchy/src/{one_to_one/mod.rs => one_to_one.rs} (56%) delete mode 100644 crates/bevy_hierarchy/src/one_to_one/component.rs delete mode 100644 crates/bevy_hierarchy/src/one_to_one/event.rs create mode 100644 crates/bevy_hierarchy/src/relationship.rs diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 4630e84ca731c..6b1b9826a99ee 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -636,13 +636,13 @@ mod tests { assert_parent(world, b, Some(a)); assert_children(world, a, Some(&[b])); - assert_events(world, &[HierarchyEvent::added(a, b)]); + assert_events(world, &[HierarchyEvent::added(b, a)]); world.entity_mut(a).add_child(c); assert_children(world, a, Some(&[b, c])); assert_parent(world, c, Some(a)); - assert_events(world, &[HierarchyEvent::added(a, c)]); + assert_events(world, &[HierarchyEvent::added(c, a)]); // Children component should be removed when it's empty. world.entity_mut(d).add_child(b).add_child(c); assert_children(world, a, None); @@ -659,7 +659,7 @@ mod tests { assert_parent(world, a, Some(b)); assert_children(world, b, Some(&[a])); - assert_events(world, &[HierarchyEvent::added(b, a)]); + assert_events(world, &[HierarchyEvent::added(a, b)]); world.entity_mut(a).set_parent(c); @@ -668,7 +668,7 @@ mod tests { assert_children(world, c, Some(&[a])); assert_events( world, - &[HierarchyEvent::removed(b, a), HierarchyEvent::added(c, a)], + &[HierarchyEvent::removed(a, b), HierarchyEvent::added(a, c)], ); } @@ -703,12 +703,12 @@ mod tests { assert_parent(world, c, Some(a)); assert_children(world, a, Some(&[c])); omit_events(world, 2); // Omit Added events. - assert_events(world, &[HierarchyEvent::removed(a, b)]); + assert_events(world, &[HierarchyEvent::removed(b, a)]); world.entity_mut(c).remove_parent(); assert_parent(world, c, None); assert_children(world, a, None); - assert_events(world, &[HierarchyEvent::removed(a, c)]); + assert_events(world, &[HierarchyEvent::removed(c, a)]); } #[allow(dead_code)] diff --git a/crates/bevy_hierarchy/src/family.rs b/crates/bevy_hierarchy/src/family.rs index 3ee7441b8de3d..18f2ef20da784 100644 --- a/crates/bevy_hierarchy/src/family.rs +++ b/crates/bevy_hierarchy/src/family.rs @@ -1,4 +1,4 @@ -use crate::{ManyToOne, OneToMany, OneToManyEvent}; +use crate::{ManyToOne, OneToMany, RelationshipEvent}; /// A familial relationship #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] @@ -37,4 +37,4 @@ pub type Children = OneToMany; /// An [`Event`] that is fired whenever there is a change in the world's hierarchy. /// /// [`Event`]: bevy_ecs::event::Event -pub type HierarchyEvent = OneToManyEvent; +pub type HierarchyEvent = RelationshipEvent; diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index 27ff330d2ea90..667007470228e 100755 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -53,11 +53,20 @@ extern crate alloc; +pub(crate) mod relationship; +pub use relationship::*; + mod one_to_one; -pub use one_to_one::{OneToOne, OneToOneEvent}; +pub use one_to_one::OneToOne; mod one_to_many; -pub use one_to_many::{ManyToOne, OneToMany, OneToManyEvent}; +pub use one_to_many::OneToMany; + +mod many_to_one; +pub use many_to_one::ManyToOne; + +mod many_to_many; +pub use many_to_many::ManyToMany; mod family; pub use family::*; diff --git a/crates/bevy_hierarchy/src/one_to_many/many.rs b/crates/bevy_hierarchy/src/many_to_many.rs similarity index 60% rename from crates/bevy_hierarchy/src/one_to_many/many.rs rename to crates/bevy_hierarchy/src/many_to_many.rs index 465f2cea94b59..ba00d66bc7edc 100644 --- a/crates/bevy_hierarchy/src/one_to_many/many.rs +++ b/crates/bevy_hierarchy/src/many_to_many.rs @@ -4,23 +4,21 @@ use bevy_ecs::reflect::{ ReflectVisitEntitiesMut, }; use bevy_ecs::{ - component::{Component, ComponentId}, + component::Component, entity::{Entity, VisitEntitiesMut}, - event::Events, - world::{DeferredWorld, World}, }; use core::{fmt::Debug, marker::PhantomData, ops::Deref}; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; -use super::{ManyToOne, OneToManyEvent}; +use crate::relationship::Relationship; -/// Represents one half of a one-to-many relationship between an [`Entity`] and some number of other [entities](Entity). +/// Represents one half of a many-to-many relationship between an [`Entity`] and some number of other [entities](Entity). /// /// The type of relationship is denoted by the parameter `R`. #[derive(Component)] #[component( - on_insert = Self::associate, - on_replace = Self::disassociate, + on_insert = ::associate, + on_replace = ::disassociate, )] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( @@ -35,104 +33,67 @@ use super::{ManyToOne, OneToManyEvent}; FromWorld ) )] -pub struct OneToMany { - entities: SmallVec<[Entity; 8]>, +pub struct ManyToMany { + // [Entity; 7] chosen to keep entities at 64 bytes on 64 bit platforms. + entities: SmallVec<[Entity; 7]>, #[cfg_attr(feature = "reflect", reflect(ignore))] - _phantom: PhantomData, + _phantom: PhantomData, } -impl OneToMany { - fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { - world.commands().queue(move |world: &mut World| { - let b_ids_len = world - .get_entity(a_id) - .ok() - .and_then(|a| a.get::()) - .map(|a_relationship| a_relationship.entities.len()); - - let Some(b_ids_len) = b_ids_len else { return }; - - for b_id_index in 0..b_ids_len { - let b = world - .get_entity(a_id) - .ok() - .and_then(|a| a.get::()) - .map(|a_relationship| a_relationship.entities[b_id_index]) - .and_then(|b_id| world.get_entity_mut(b_id).ok()); - - let Some(mut b) = b else { return }; - - let b_id = b.id(); - - let b_points_to_a = b - .get::>() - .is_some_and(|b_relationship| b_relationship.get() == a_id); - - if !b_points_to_a { - b.insert(ManyToOne::::new(a_id)); - - if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToManyEvent::::added(a_id, b_id)); - } - } - } - }); +impl Relationship for ManyToMany { + type Other = ManyToMany; + + fn has(&self, entity: Entity) -> bool { + self.entities.contains(&entity) + } + + fn new(entity: Entity) -> Self { + Self { + entities: smallvec![entity], + _phantom: PhantomData, + } } - fn disassociate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { - let Some(a_relationship) = world.get::(a_id) else { - unreachable!("component hook should only be called when component is available"); - }; - - // Cloning to allow a user to `take` the component for modification - let b_ids = a_relationship.entities.clone(); - - world.commands().queue(move |world: &mut World| { - for b_id in b_ids { - let a_points_to_b = world - .get_entity(a_id) - .ok() - .and_then(|a| a.get::()) - .is_some_and(|a_relationship| a_relationship.entities.contains(&b_id)); - - let b_points_to_a = world - .get_entity(b_id) - .ok() - .and_then(|b| b.get::>()) - .is_some_and(|b_relationship| b_relationship.get() == a_id); - - if b_points_to_a && !a_points_to_b { - if let Ok(mut b) = world.get_entity_mut(b_id) { - b.remove::>(); - } - - if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToManyEvent::::removed(a_id, b_id)); - } - } - } - }); + fn with(mut self, entity: Entity) -> Self { + if !self.has(entity) { + self.entities.push(entity); + } + + self + } + + fn without(mut self, entity: Entity) -> Option { + self.entities.retain(|&mut id| id != entity); + + (!self.entities.is_empty()).then_some(self) + } + + fn iter(&self) -> impl ExactSizeIterator { + self.entities.iter().copied() } } -impl PartialEq for OneToMany { +impl PartialEq for ManyToMany { fn eq(&self, other: &Self) -> bool { self.entities == other.entities } } -impl Eq for OneToMany {} +impl Eq for ManyToMany {} -impl Debug for OneToMany { +impl Debug for ManyToMany { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("OneToMany") - .field(&self.entities) - .field(&core::any::type_name::()) - .finish() + write!( + f, + "Has Many {:?} ({}) With Many ({})", + self.entities, + core::any::type_name::(), + core::any::type_name::() + ) } } -impl VisitEntitiesMut for OneToMany { +impl VisitEntitiesMut for ManyToMany { fn visit_entities_mut(&mut self, mut f: F) { for entity in &mut self.entities { f(entity); @@ -140,7 +101,7 @@ impl VisitEntitiesMut for OneToMany { } } -impl Deref for OneToMany { +impl Deref for ManyToMany { type Target = [Entity]; #[inline(always)] @@ -149,7 +110,7 @@ impl Deref for OneToMany { } } -impl<'a, R> IntoIterator for &'a OneToMany { +impl<'a, FK, PK> IntoIterator for &'a ManyToMany { type Item = ::Item; type IntoIter = core::slice::Iter<'a, Entity>; @@ -160,19 +121,19 @@ impl<'a, R> IntoIterator for &'a OneToMany { } } -impl FromIterator for OneToMany { +impl FromIterator for ManyToMany { fn from_iter>(iter: T) -> Self { Self::from_smallvec(iter.into_iter().collect()) } } -impl Default for OneToMany { +impl Default for ManyToMany { fn default() -> Self { Self::new() } } -impl OneToMany { +impl ManyToMany { /// Gets the other [`Entity`] as a slice of length 1. #[inline(always)] pub fn as_slice(&self) -> &[Entity] { @@ -188,7 +149,7 @@ impl OneToMany { #[inline(always)] #[must_use] - fn from_smallvec(entities: SmallVec<[Entity; 8]>) -> Self { + fn from_smallvec(entities: SmallVec<[Entity; 7]>) -> Self { Self { entities, _phantom: PhantomData, @@ -213,10 +174,6 @@ impl OneToMany { self } - pub(super) fn entities_mut(&mut self) -> &mut SmallVec<[Entity; 8]> { - &mut self.entities - } - /// Swaps the entity at `a_index` with the entity at `b_index`. #[inline] pub fn swap(&mut self, a_index: usize, b_index: usize) { @@ -305,3 +262,66 @@ impl OneToMany { self.entities.sort_unstable_by_key(compare); } } + +#[cfg(test)] +mod tests { + use bevy_ecs::world::World; + + use super::ManyToMany; + + /// A familial relationship + struct Friendship; + + /// Shorthand for a group of friends + type Friends = ManyToMany; + + #[test] + fn simple_add_then_remove() { + let mut world = World::new(); + + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn_empty().id(); + let c = world.spawn(Friends::new().with(a).with(b)).id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![c]) + ); + assert_eq!( + world + .get::(b) + .map(|c| c.iter().copied().collect::>()), + Some(vec![c]) + ); + assert_eq!( + world + .get::(c) + .map(|c| c.iter().copied().collect::>()), + Some(vec![a, b]) + ); + + world.entity_mut(a).remove::(); + + world.flush(); + + assert_eq!(world.get::(a), None); + assert_eq!( + world + .get::(b) + .map(|c| c.iter().copied().collect::>()), + Some(vec![c]) + ); + assert_eq!( + world + .get::(c) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b]) + ); + } +} diff --git a/crates/bevy_hierarchy/src/one_to_many/one.rs b/crates/bevy_hierarchy/src/many_to_one.rs similarity index 51% rename from crates/bevy_hierarchy/src/one_to_many/one.rs rename to crates/bevy_hierarchy/src/many_to_one.rs index 271592e5e8ac7..eac8877f4d905 100644 --- a/crates/bevy_hierarchy/src/one_to_many/one.rs +++ b/crates/bevy_hierarchy/src/many_to_one.rs @@ -4,23 +4,24 @@ use bevy_ecs::reflect::{ ReflectVisitEntitiesMut, }; use bevy_ecs::{ - component::{Component, ComponentId}, + component::Component, entity::{Entity, VisitEntities, VisitEntitiesMut}, - event::Events, traversal::Traversal, - world::{DeferredWorld, FromWorld, World}, + world::{FromWorld, World}, }; use core::{fmt::Debug, marker::PhantomData, ops::Deref}; -use super::{OneToMany, OneToManyEvent}; +use crate::relationship::Relationship; + +use super::OneToMany; /// Represents one half of a one-to-many relationship between an [`Entity`] and some number of other [entities](Entity). /// /// The type of relationship is denoted by the parameter `R`. #[derive(Component)] #[component( - on_insert = Self::associate, - on_replace = Self::disassociate, + on_insert = ::associate, + on_replace = ::disassociate, )] #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] #[cfg_attr( @@ -41,74 +42,30 @@ pub struct ManyToOne { _phantom: PhantomData, } -impl ManyToOne { - fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { - world.commands().queue(move |world: &mut World| { - let b = world - .get_entity(a_id) - .ok() - .and_then(|a| a.get::()) - .map(|a_relationship| a_relationship.entity) - .and_then(|b_id| world.get_entity_mut(b_id).ok()); - - let Some(mut b) = b else { return }; - - let b_id = b.id(); - - let b_points_to_a = b - .get::>() - .is_some_and(|b_relationship| b_relationship.contains(&a_id)); - - if !b_points_to_a { - if let Some(mut component) = b.get_mut::>() { - component.entities_mut().push(a_id); - } else { - b.insert(OneToMany::::new().with(a_id)); - } - - if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToManyEvent::::added(b_id, a_id)); - } - } - }); +impl Relationship for ManyToOne { + type Other = OneToMany; + + fn has(&self, entity: Entity) -> bool { + self.entity == entity + } + + fn new(entity: Entity) -> Self { + Self { + entity, + _phantom: PhantomData, + } + } + + fn with(self, entity: Entity) -> Self { + Self::new(entity) + } + + fn without(self, entity: Entity) -> Option { + (self.entity != entity).then_some(self) } - fn disassociate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { - let Some(a_relationship) = world.get::(a_id) else { - unreachable!("component hook should only be called when component is available"); - }; - - let b_id = a_relationship.entity; - - world.commands().queue(move |world: &mut World| { - let a_points_to_b = world - .get_entity(a_id) - .ok() - .and_then(|a| a.get::()) - .is_some_and(|a_relationship| a_relationship.entity == b_id); - - let b_points_to_a = world - .get_entity(b_id) - .ok() - .and_then(|b| b.get::>()) - .is_some_and(|b_relationship| b_relationship.contains(&a_id)); - - if b_points_to_a && !a_points_to_b { - if let Ok(mut b) = world.get_entity_mut(b_id) { - if let Some(mut component) = b.get_mut::>() { - component.entities_mut().retain(|&mut e| e != a_id); - - if component.is_empty() { - b.remove::>(); - } - } - } - - if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToManyEvent::::removed(b_id, a_id)); - } - } - }); + fn iter(&self) -> impl ExactSizeIterator { + [self.entity].into_iter() } } @@ -122,10 +79,13 @@ impl Eq for ManyToOne {} impl Debug for ManyToOne { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("ManyToOne") - .field(&self.entity) - .field(&core::any::type_name::()) - .finish() + write!( + f, + "Has One {:?} ({}) With Many ({})", + self.entity, + core::any::type_name::(), + core::any::type_name::() + ) } } diff --git a/crates/bevy_hierarchy/src/one_to_many.rs b/crates/bevy_hierarchy/src/one_to_many.rs new file mode 100644 index 0000000000000..f444b501faaad --- /dev/null +++ b/crates/bevy_hierarchy/src/one_to_many.rs @@ -0,0 +1,434 @@ +#[cfg(feature = "reflect")] +use bevy_ecs::reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, +}; +use bevy_ecs::{ + component::Component, + entity::{Entity, VisitEntitiesMut}, +}; +use core::{fmt::Debug, marker::PhantomData, ops::Deref}; +use smallvec::{smallvec, SmallVec}; + +use crate::{many_to_one::ManyToOne, relationship::Relationship}; + +/// Represents one half of a one-to-many relationship between an [`Entity`] and some number of other [entities](Entity). +/// +/// The type of relationship is denoted by the parameter `R`. +#[derive(Component)] +#[component( + on_insert = ::associate, + on_replace = ::disassociate, +)] +#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "reflect", + reflect( + Component, + MapEntities, + VisitEntities, + VisitEntitiesMut, + PartialEq, + Debug, + FromWorld + ) +)] +pub struct OneToMany { + // [Entity; 7] chosen to keep entities at 64 bytes on 64 bit platforms. + entities: SmallVec<[Entity; 7]>, + #[cfg_attr(feature = "reflect", reflect(ignore))] + _phantom: PhantomData, +} + +impl Relationship for OneToMany { + type Other = ManyToOne; + + fn has(&self, entity: Entity) -> bool { + self.entities.contains(&entity) + } + + fn new(entity: Entity) -> Self { + Self { + entities: smallvec![entity], + _phantom: PhantomData, + } + } + + fn with(mut self, entity: Entity) -> Self { + if !self.has(entity) { + self.entities.push(entity); + } + + self + } + + fn without(mut self, entity: Entity) -> Option { + self.entities.retain(|&mut id| id != entity); + + (!self.entities.is_empty()).then_some(self) + } + + fn iter(&self) -> impl ExactSizeIterator { + self.entities.iter().copied() + } +} + +impl PartialEq for OneToMany { + fn eq(&self, other: &Self) -> bool { + self.entities == other.entities + } +} + +impl Eq for OneToMany {} + +impl Debug for OneToMany { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "Has Many {:?} ({}) With One ({})", + self.entities, + core::any::type_name::(), + core::any::type_name::() + ) + } +} + +impl VisitEntitiesMut for OneToMany { + fn visit_entities_mut(&mut self, mut f: F) { + for entity in &mut self.entities { + f(entity); + } + } +} + +impl Deref for OneToMany { + type Target = [Entity]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.entities + } +} + +impl<'a, R> IntoIterator for &'a OneToMany { + type Item = ::Item; + + type IntoIter = core::slice::Iter<'a, Entity>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.entities.iter() + } +} + +impl FromIterator for OneToMany { + fn from_iter>(iter: T) -> Self { + Self::from_smallvec(iter.into_iter().collect()) + } +} + +impl Default for OneToMany { + fn default() -> Self { + Self::new() + } +} + +impl OneToMany { + /// Gets the other [`Entity`] as a slice of length 1. + #[inline(always)] + pub fn as_slice(&self) -> &[Entity] { + &self.entities + } + + /// Create a new relationship. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::from_smallvec(SmallVec::new()) + } + + #[inline(always)] + #[must_use] + fn from_smallvec(entities: SmallVec<[Entity; 7]>) -> Self { + Self { + entities, + _phantom: PhantomData, + } + } + + /// Ensures the provided [`Entity`] is present in this relationship. + #[inline(always)] + #[must_use] + pub fn with(mut self, other: Entity) -> Self { + if !self.entities.contains(&other) { + self.entities.push(other); + } + self + } + + /// Ensures the provided [`Entity`] is _not_ present in this relationship. + #[inline(always)] + #[must_use] + pub fn without(mut self, other: Entity) -> Self { + self.entities.retain(|&mut e| e != other); + self + } + + /// Swaps the entity at `a_index` with the entity at `b_index`. + #[inline] + pub fn swap(&mut self, a_index: usize, b_index: usize) { + self.entities.swap(a_index, b_index); + } + + /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided comparator function. + /// + /// For the underlying implementation, see [`slice::sort_by`]. + /// + /// For the unstable version, see [`sort_unstable_by`](OneToMany::sort_unstable_by). + /// + /// See also [`sort_by_key`](OneToMany::sort_by_key), [`sort_by_cached_key`](OneToMany::sort_by_cached_key). + #[inline] + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, + { + self.entities.sort_by(compare); + } + + /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. + /// + /// For the underlying implementation, see [`slice::sort_by_key`]. + /// + /// For the unstable version, see [`sort_unstable_by_key`](OneToMany::sort_unstable_by_key). + /// + /// See also [`sort_by`](OneToMany::sort_by), [`sort_by_cached_key`](OneToMany::sort_by_cached_key). + #[inline] + pub fn sort_by_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.entities.sort_by_key(compare); + } + + /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. Only evaluates each key at most + /// once per sort, caching the intermediate results in memory. + /// + /// For the underlying implementation, see [`slice::sort_by_cached_key`]. + /// + /// See also [`sort_by`](OneToMany::sort_by), [`sort_by_key`](OneToMany::sort_by_key). + #[inline] + pub fn sort_by_cached_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.entities.sort_by_cached_key(compare); + } + + /// Sorts entities [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided comparator function. + /// + /// For the underlying implementation, see [`slice::sort_unstable_by`]. + /// + /// For the stable version, see [`sort_by`](OneToMany::sort_by). + /// + /// See also [`sort_unstable_by_key`](OneToMany::sort_unstable_by_key). + #[inline] + pub fn sort_unstable_by(&mut self, compare: F) + where + F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, + { + self.entities.sort_unstable_by(compare); + } + + /// Sorts entities [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) + /// in place using the provided key extraction function. + /// + /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. + /// + /// For the stable version, see [`sort_by_key`](OneToMany::sort_by_key). + /// + /// See also [`sort_unstable_by`](OneToMany::sort_unstable_by). + #[inline] + pub fn sort_unstable_by_key(&mut self, compare: F) + where + F: FnMut(&Entity) -> K, + K: Ord, + { + self.entities.sort_unstable_by_key(compare); + } +} + +#[cfg(test)] +mod tests { + use bevy_ecs::{event::Events, world::World}; + + use crate::{ManyToOne, RelationshipEvent}; + + use super::OneToMany; + + /// A familial relationship + struct Family; + + /// Shorthand for a Parent in a Family relationship + type Parent = ManyToOne; + + /// Shorthand for a Parent in a Family relationship + type Children = OneToMany; + + #[test] + fn simple_add_then_remove() { + let mut world = World::new(); + + world.register_component::(); + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Parent::new(a)).id(); + let c = world.spawn(Parent::new(a)).id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b, c]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), Some(&Parent::new(a))); + + world.entity_mut(a).remove::(); + + world.flush(); + + assert_eq!(world.get::(a), None); + assert_eq!(world.get::(b), None); + assert_eq!(world.get::(c), None); + } + + #[test] + fn partial_add_then_remove() { + let mut world = World::new(); + + world.register_component::(); + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Parent::new(a)).id(); + let c = world.spawn(Parent::new(a)).id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b, c]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), Some(&Parent::new(a))); + + world.entity_mut(c).remove::(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), None); + } + + #[test] + fn take_and_return() { + let mut world = World::new(); + + world.register_component::(); + world.register_component::(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Parent::new(a)).id(); + let c = world.spawn_empty().id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), None); + + let component = world.entity_mut(a).take::().unwrap(); + + let component = component.with(c); + + world.entity_mut(a).insert(component); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b, c]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + assert_eq!(world.get::(c), Some(&Parent::new(a))); + } + + #[test] + fn event_testing() { + let mut world = World::new(); + + world.register_component::(); + world.register_component::(); + world.init_resource::>>(); + + let a = world.spawn_empty().id(); + let b = world.spawn(Parent::new(a)).id(); + + world.flush(); + + assert_eq!( + world + .get::(a) + .map(|c| c.iter().copied().collect::>()), + Some(vec![b]) + ); + assert_eq!(world.get::(b), Some(&Parent::new(a))); + + assert_eq!( + world + .resource_mut::>>() + .drain() + .collect::>(), + vec![RelationshipEvent::::added(b, a)] + ); + + world.entity_mut(b).remove::(); + + world.flush(); + + assert_eq!(world.get::(a), None); + assert_eq!(world.get::(b), None); + + assert_eq!( + world + .resource_mut::>>() + .drain() + .collect::>(), + vec![RelationshipEvent::::removed(b, a)] + ); + } +} diff --git a/crates/bevy_hierarchy/src/one_to_many/event.rs b/crates/bevy_hierarchy/src/one_to_many/event.rs deleted file mode 100644 index 51cddb374aa15..0000000000000 --- a/crates/bevy_hierarchy/src/one_to_many/event.rs +++ /dev/null @@ -1,121 +0,0 @@ -use core::marker::PhantomData; - -use bevy_ecs::{entity::Entity, event::Event}; - -/// A One-to-Many [relationship](crate::ManyToOne) [`Event`]. -#[derive(Event)] -pub enum OneToManyEvent { - /// A [relationship](crate::ManyToOne) was added between two [entities](Entity). - Added(OneToManyEventDetails), - /// A [relationship](crate::ManyToOne) was removed from two [entities](Entity). - Removed(OneToManyEventDetails), -} - -impl OneToManyEvent { - /// Create a new [`Added`](OneToManyEvent::Added) [`Event`] - pub const fn added(many: Entity, one: Entity) -> Self { - Self::Added(OneToManyEventDetails::new(many, one)) - } - - /// Create a new [`Removed`](OneToManyEvent::Removed) [`Event`] - pub const fn removed(many: Entity, one: Entity) -> Self { - Self::Removed(OneToManyEventDetails::new(many, one)) - } - - /// Get the [`Entity`] that has the [`OneToMany`](crate::OneToMany) component. - pub const fn many(&self) -> Entity { - match self { - Self::Added(details) | Self::Removed(details) => details.many(), - } - } - - /// Get the [`Entity`] that has the [`ManyToOne`](crate::ManyToOne) component. - pub const fn one(&self) -> Entity { - match self { - Self::Added(details) | Self::Removed(details) => details.one(), - } - } -} - -impl core::fmt::Debug for OneToManyEvent { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Added(arg0) => f.debug_tuple("Added").field(arg0).finish(), - Self::Removed(arg0) => f.debug_tuple("Removed").field(arg0).finish(), - } - } -} - -impl PartialEq for OneToManyEvent { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Added(l0), Self::Added(r0)) | (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, - _ => false, - } - } -} - -impl Eq for OneToManyEvent {} - -impl Clone for OneToManyEvent { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for OneToManyEvent {} - -/// The details of a [`OneToManyEvent`]. -pub struct OneToManyEventDetails { - many: Entity, - one: Entity, - phantom_data: PhantomData, -} - -impl OneToManyEventDetails { - /// Create a new [`OneToManyEventDetails`] for a `many` and a `one` [`Entity`]. - /// The `many` [`Entity`] has the [`OneToMany`](crate::OneToMany) component, - /// while the `one` [`Entity`] has the [`ManyToOne`](crate::ManyToOne) component. - pub const fn new(many: Entity, one: Entity) -> Self { - Self { - many, - one, - phantom_data: PhantomData, - } - } - - /// Get the [`Entity`] that has the [`OneToMany`](crate::OneToMany) component. - pub const fn many(&self) -> Entity { - self.many - } - - /// Get the [`Entity`] that has the [`ManyToOne`](crate::ManyToOne) component. - pub const fn one(&self) -> Entity { - self.one - } -} - -impl core::fmt::Debug for OneToManyEventDetails { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("OneToManyEventDetails") - .field("many", &self.many) - .field("one", &self.one) - .finish() - } -} - -impl PartialEq for OneToManyEventDetails { - fn eq(&self, other: &Self) -> bool { - self.many == other.many && self.one == other.one - } -} - -impl Eq for OneToManyEventDetails {} - -impl Clone for OneToManyEventDetails { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for OneToManyEventDetails {} diff --git a/crates/bevy_hierarchy/src/one_to_many/mod.rs b/crates/bevy_hierarchy/src/one_to_many/mod.rs deleted file mode 100644 index 7c980bfd1dfbe..0000000000000 --- a/crates/bevy_hierarchy/src/one_to_many/mod.rs +++ /dev/null @@ -1,176 +0,0 @@ -mod event; -pub use event::OneToManyEvent; - -mod one; -pub use one::ManyToOne; - -mod many; -pub use many::OneToMany; - -#[cfg(test)] -mod tests { - use bevy_ecs::{event::Events, world::World}; - - use super::*; - - /// A familial relationship - struct Family; - - /// Shorthand for a Parent in a Family relationship - type Parent = ManyToOne; - - /// Shorthand for a Parent in a Family relationship - type Children = OneToMany; - - #[test] - fn simple_add_then_remove() { - let mut world = World::new(); - - world.register_component::(); - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn(Parent::new(a)).id(); - let c = world.spawn(Parent::new(a)).id(); - - world.flush(); - - assert_eq!( - world - .get::(a) - .map(|c| c.iter().copied().collect::>()), - Some(vec![b, c]) - ); - assert_eq!(world.get::(b), Some(&Parent::new(a))); - assert_eq!(world.get::(c), Some(&Parent::new(a))); - - world.entity_mut(a).remove::(); - - world.flush(); - - assert_eq!(world.get::(a), None); - assert_eq!(world.get::(b), None); - assert_eq!(world.get::(c), None); - } - - #[test] - fn partial_add_then_remove() { - let mut world = World::new(); - - world.register_component::(); - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn(Parent::new(a)).id(); - let c = world.spawn(Parent::new(a)).id(); - - world.flush(); - - assert_eq!( - world - .get::(a) - .map(|c| c.iter().copied().collect::>()), - Some(vec![b, c]) - ); - assert_eq!(world.get::(b), Some(&Parent::new(a))); - assert_eq!(world.get::(c), Some(&Parent::new(a))); - - world.entity_mut(c).remove::(); - - world.flush(); - - assert_eq!( - world - .get::(a) - .map(|c| c.iter().copied().collect::>()), - Some(vec![b]) - ); - assert_eq!(world.get::(b), Some(&Parent::new(a))); - assert_eq!(world.get::(c), None); - } - - #[test] - fn take_and_return() { - let mut world = World::new(); - - world.register_component::(); - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn(Parent::new(a)).id(); - let c = world.spawn_empty().id(); - - world.flush(); - - assert_eq!( - world - .get::(a) - .map(|c| c.iter().copied().collect::>()), - Some(vec![b]) - ); - assert_eq!(world.get::(b), Some(&Parent::new(a))); - assert_eq!(world.get::(c), None); - - let component = world.entity_mut(a).take::().unwrap(); - - let component = component.with(c); - - world.entity_mut(a).insert(component); - - world.flush(); - - assert_eq!( - world - .get::(a) - .map(|c| c.iter().copied().collect::>()), - Some(vec![b, c]) - ); - assert_eq!(world.get::(b), Some(&Parent::new(a))); - assert_eq!(world.get::(c), Some(&Parent::new(a))); - } - - #[test] - fn event_testing() { - let mut world = World::new(); - - world.register_component::(); - world.register_component::(); - world.init_resource::>>(); - - let a = world.spawn_empty().id(); - let b = world.spawn(Parent::new(a)).id(); - - world.flush(); - - assert_eq!( - world - .get::(a) - .map(|c| c.iter().copied().collect::>()), - Some(vec![b]) - ); - assert_eq!(world.get::(b), Some(&Parent::new(a))); - - assert_eq!( - world - .resource_mut::>>() - .drain() - .collect::>(), - vec![OneToManyEvent::::added(a, b)] - ); - - world.entity_mut(b).remove::(); - - world.flush(); - - assert_eq!(world.get::(a), None); - assert_eq!(world.get::(b), None); - - assert_eq!( - world - .resource_mut::>>() - .drain() - .collect::>(), - vec![OneToManyEvent::::removed(a, b)] - ); - } -} diff --git a/crates/bevy_hierarchy/src/one_to_one/mod.rs b/crates/bevy_hierarchy/src/one_to_one.rs similarity index 56% rename from crates/bevy_hierarchy/src/one_to_one/mod.rs rename to crates/bevy_hierarchy/src/one_to_one.rs index 2405642dec85d..5f194084f2a78 100644 --- a/crates/bevy_hierarchy/src/one_to_one/mod.rs +++ b/crates/bevy_hierarchy/src/one_to_one.rs @@ -1,16 +1,169 @@ -//! Provides access to the [`OneToOne`] component, allowing for one-to-one relationships -//! of an arbitrary type to be automatically managed in the ECS. +#[cfg(feature = "reflect")] +use bevy_ecs::reflect::{ + ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, + ReflectVisitEntitiesMut, +}; +use bevy_ecs::{ + component::Component, + entity::{Entity, VisitEntities, VisitEntitiesMut}, + traversal::Traversal, + world::{FromWorld, World}, +}; +use core::{fmt::Debug, marker::PhantomData, ops::Deref}; + +use crate::relationship::Relationship; + +/// Represents one half of a one-to-one relationship between two [entities](Entity). +/// +/// The type of relationship is denoted by the parameter `R`. +#[derive(Component)] +#[component( + on_insert = ::associate, + on_replace = ::disassociate, +)] +#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "reflect", + reflect( + Component, + MapEntities, + VisitEntities, + VisitEntitiesMut, + PartialEq, + Debug, + FromWorld + ) +)] +pub struct OneToOne { + entity: Entity, + #[cfg_attr(feature = "reflect", reflect(ignore))] + _phantom: PhantomData, +} + +impl Relationship for OneToOne { + type Other = OneToOne; + + fn has(&self, entity: Entity) -> bool { + self.entity == entity + } + + fn new(entity: Entity) -> Self { + Self { + entity, + _phantom: PhantomData, + } + } -mod component; -pub use component::OneToOne; + fn with(self, entity: Entity) -> Self { + Self { + entity, + _phantom: PhantomData, + } + } -mod event; -pub use event::OneToOneEvent; + fn without(self, entity: Entity) -> Option { + (self.entity != entity).then_some(self) + } + + fn iter(&self) -> impl ExactSizeIterator { + [self.entity].into_iter() + } +} + +impl PartialEq for OneToOne { + fn eq(&self, other: &Self) -> bool { + self.entity == other.entity + } +} + +impl Eq for OneToOne {} + +impl Debug for OneToOne { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "Has One {} ({}) With One ({})", + self.entity, + core::any::type_name::(), + core::any::type_name::() + ) + } +} + +impl VisitEntities for OneToOne { + fn visit_entities(&self, mut f: F) { + f(self.entity); + } +} + +impl VisitEntitiesMut for OneToOne { + fn visit_entities_mut(&mut self, mut f: F) { + f(&mut self.entity); + } +} + +// TODO: We need to impl either FromWorld or Default so OneToOne can be registered as Reflect. +// This is because Reflect deserialize by creating an instance and apply a patch on top. +// However OneToOne should only ever be set with a real user-defined entity. It's worth looking into +// better ways to handle cases like this. +impl FromWorld for OneToOne { + #[inline(always)] + fn from_world(_world: &mut World) -> Self { + Self { + entity: Entity::PLACEHOLDER, + _phantom: PhantomData, + } + } +} + +impl Deref for OneToOne { + type Target = Entity; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.entity + } +} + +/// This provides generalized hierarchy traversal for use in [event propagation]. +/// +/// [event propagation]: bevy_ecs::observer::Trigger::propagate +impl Traversal for &OneToOne { + fn traverse(item: Self::Item<'_>) -> Option { + Some(item.entity) + } +} + +impl OneToOne { + /// Gets the [`Entity`] ID of the other member of this one-to-one relationship. + #[inline(always)] + pub fn get(&self) -> Entity { + self.entity + } + + /// Gets the other [`Entity`] as a slice of length 1. + #[inline(always)] + pub fn as_slice(&self) -> &[Entity] { + core::slice::from_ref(&self.entity) + } + + /// Create a new relationship with the provided [`Entity`]. + #[inline(always)] + #[must_use] + pub fn new(other: Entity) -> Self { + Self { + entity: other, + _phantom: PhantomData, + } + } +} #[cfg(test)] mod tests { use bevy_ecs::{event::Events, world::World}; + use crate::RelationshipEvent; + use super::*; /// An example relationship between two entities @@ -177,7 +330,7 @@ mod tests { let mut world = World::new(); world.register_component::(); - world.init_resource::>>(); + world.init_resource::>>(); let a = world.spawn_empty().id(); let b = world.spawn(Friend::new(a)).id(); @@ -189,10 +342,10 @@ mod tests { assert_eq!( world - .resource_mut::>>() + .resource_mut::>>() .drain() .collect::>(), - vec![OneToOneEvent::::added(b, a)] + vec![RelationshipEvent::::added(b, a)] ); world.entity_mut(a).remove::(); @@ -204,10 +357,10 @@ mod tests { assert_eq!( world - .resource_mut::>>() + .resource_mut::>>() .drain() .collect::>(), - vec![OneToOneEvent::::removed(a, b)] + vec![RelationshipEvent::::removed(a, b)] ); } } diff --git a/crates/bevy_hierarchy/src/one_to_one/component.rs b/crates/bevy_hierarchy/src/one_to_one/component.rs deleted file mode 100644 index 2d26ea9460747..0000000000000 --- a/crates/bevy_hierarchy/src/one_to_one/component.rs +++ /dev/null @@ -1,188 +0,0 @@ -#[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; -use bevy_ecs::{ - component::{Component, ComponentId}, - entity::{Entity, VisitEntities, VisitEntitiesMut}, - event::Events, - traversal::Traversal, - world::{DeferredWorld, FromWorld, World}, -}; -use core::{fmt::Debug, marker::PhantomData, ops::Deref}; - -use super::OneToOneEvent; - -/// Represents one half of a one-to-one relationship between two [entities](Entity). -/// -/// The type of relationship is denoted by the parameter `R`. -#[derive(Component)] -#[component( - on_insert = Self::associate, - on_replace = Self::disassociate, -)] -#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - PartialEq, - Debug, - FromWorld - ) -)] -pub struct OneToOne { - entity: Entity, - #[cfg_attr(feature = "reflect", reflect(ignore))] - _phantom: PhantomData, -} - -impl OneToOne { - fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { - world.commands().queue(move |world: &mut World| { - let b = world - .get_entity(a_id) - .ok() - .and_then(|a| a.get::()) - .map(|a_relationship| a_relationship.entity) - .and_then(|b_id| world.get_entity_mut(b_id).ok()); - - let Some(mut b) = b else { return }; - - let b_id = b.id(); - - let b_points_to_a = b - .get::() - .is_some_and(|b_relationship| b_relationship.entity == a_id); - - if !b_points_to_a { - b.insert(Self::new(a_id)); - - if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToOneEvent::::added(a_id, b_id)); - } - } - }); - } - - fn disassociate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { - let Some(a_relationship) = world.get::(a_id) else { - unreachable!("component hook should only be called when component is available"); - }; - - let b_id = a_relationship.entity; - - world.commands().queue(move |world: &mut World| { - let a_points_to_b = world - .get_entity(a_id) - .ok() - .and_then(|a| a.get::()) - .is_some_and(|a_relationship| a_relationship.entity == b_id); - - let b_points_to_a = world - .get_entity(b_id) - .ok() - .and_then(|b| b.get::()) - .is_some_and(|b_relationship| b_relationship.entity == a_id); - - if b_points_to_a && !a_points_to_b { - if let Ok(mut b) = world.get_entity_mut(b_id) { - b.remove::(); - } - - if let Some(mut moved) = world.get_resource_mut::>>() { - moved.send(OneToOneEvent::::removed(a_id, b_id)); - } - } - }); - } -} - -impl PartialEq for OneToOne { - fn eq(&self, other: &Self) -> bool { - self.entity == other.entity - } -} - -impl Eq for OneToOne {} - -impl Debug for OneToOne { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("OneToOne") - .field(&self.entity) - .field(&core::any::type_name::()) - .finish() - } -} - -impl VisitEntities for OneToOne { - fn visit_entities(&self, mut f: F) { - f(self.entity); - } -} - -impl VisitEntitiesMut for OneToOne { - fn visit_entities_mut(&mut self, mut f: F) { - f(&mut self.entity); - } -} - -// TODO: We need to impl either FromWorld or Default so OneToOne can be registered as Reflect. -// This is because Reflect deserialize by creating an instance and apply a patch on top. -// However OneToOne should only ever be set with a real user-defined entity. It's worth looking into -// better ways to handle cases like this. -impl FromWorld for OneToOne { - #[inline(always)] - fn from_world(_world: &mut World) -> Self { - Self { - entity: Entity::PLACEHOLDER, - _phantom: PhantomData, - } - } -} - -impl Deref for OneToOne { - type Target = Entity; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.entity - } -} - -/// This provides generalized hierarchy traversal for use in [event propagation]. -/// -/// [event propagation]: bevy_ecs::observer::Trigger::propagate -impl Traversal for &OneToOne { - fn traverse(item: Self::Item<'_>) -> Option { - Some(item.entity) - } -} - -impl OneToOne { - /// Gets the [`Entity`] ID of the other member of this one-to-one relationship. - #[inline(always)] - pub fn get(&self) -> Entity { - self.entity - } - - /// Gets the other [`Entity`] as a slice of length 1. - #[inline(always)] - pub fn as_slice(&self) -> &[Entity] { - core::slice::from_ref(&self.entity) - } - - /// Create a new relationship with the provided [`Entity`]. - #[inline(always)] - #[must_use] - pub fn new(other: Entity) -> Self { - Self { - entity: other, - _phantom: PhantomData, - } - } -} diff --git a/crates/bevy_hierarchy/src/one_to_one/event.rs b/crates/bevy_hierarchy/src/one_to_one/event.rs deleted file mode 100644 index 09955c8855a2b..0000000000000 --- a/crates/bevy_hierarchy/src/one_to_one/event.rs +++ /dev/null @@ -1,123 +0,0 @@ -use core::marker::PhantomData; - -use bevy_ecs::{entity::Entity, event::Event}; - -/// A One-to-One [relationship](crate::OneToOne) [`Event`]. -#[derive(Event)] -pub enum OneToOneEvent { - /// A [relationship](crate::OneToOne) was added between two [entities](Entity). - Added(OneToOneEventDetails), - /// A [relationship](crate::OneToOne) was removed from two [entities](Entity). - Removed(OneToOneEventDetails), -} - -impl OneToOneEvent { - /// Create a new [`Added`](OneToOneEvent::Added) [`Event`] - pub const fn added(primary: Entity, secondary: Entity) -> Self { - Self::Added(OneToOneEventDetails::new(primary, secondary)) - } - - /// Create a new [`Removed`](OneToOneEvent::Removed) [`Event`] - pub const fn removed(primary: Entity, secondary: Entity) -> Self { - Self::Removed(OneToOneEventDetails::new(primary, secondary)) - } - - /// Get the primary [`Entity`] in this [`Event`]. - /// The primary is the _cause_ of the event, while the secondary is the relation. - pub const fn primary(&self) -> Entity { - match self { - Self::Added(details) | Self::Removed(details) => details.primary(), - } - } - - /// Get the secondary [`Entity`] in this [`Event`]. - /// The primary is the _cause_ of the event, while the secondary is the relation. - pub const fn secondary(&self) -> Entity { - match self { - Self::Added(details) | Self::Removed(details) => details.secondary(), - } - } -} - -impl core::fmt::Debug for OneToOneEvent { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Added(arg0) => f.debug_tuple("Added").field(arg0).finish(), - Self::Removed(arg0) => f.debug_tuple("Removed").field(arg0).finish(), - } - } -} - -impl PartialEq for OneToOneEvent { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Added(l0), Self::Added(r0)) | (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, - _ => false, - } - } -} - -impl Eq for OneToOneEvent {} - -impl Clone for OneToOneEvent { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for OneToOneEvent {} - -/// The details of a [`OneToOneEvent`]. -pub struct OneToOneEventDetails { - primary: Entity, - secondary: Entity, - phantom_data: PhantomData, -} - -impl OneToOneEventDetails { - /// Create a new [`OneToOneEventDetails`] for a `primary` and a `secondary` [`Entity`]. - /// The `primary` [`Entity`] is the cause of the [`Event`], while the `secondary` - /// is the other member of the relationship. - pub const fn new(primary: Entity, secondary: Entity) -> Self { - Self { - primary, - secondary, - phantom_data: PhantomData, - } - } - - /// Get the [`Entity`] that caused this [`Event`] to be triggered. - pub const fn primary(&self) -> Entity { - self.primary - } - - /// Get the [`Entity`] related to the `primary` [`Entity`]. - pub const fn secondary(&self) -> Entity { - self.secondary - } -} - -impl core::fmt::Debug for OneToOneEventDetails { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("OneToOneEventDetails") - .field("primary", &self.primary) - .field("secondary", &self.secondary) - .finish() - } -} - -impl PartialEq for OneToOneEventDetails { - fn eq(&self, other: &Self) -> bool { - self.primary == other.primary && self.secondary == other.secondary - } -} - -impl Eq for OneToOneEventDetails {} - -impl Clone for OneToOneEventDetails { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for OneToOneEventDetails {} diff --git a/crates/bevy_hierarchy/src/relationship.rs b/crates/bevy_hierarchy/src/relationship.rs new file mode 100644 index 0000000000000..938d4666ae5ff --- /dev/null +++ b/crates/bevy_hierarchy/src/relationship.rs @@ -0,0 +1,257 @@ +use bevy_ecs::{ + component::{Component, ComponentId}, + entity::Entity, + event::{Event, Events}, + world::{DeferredWorld, World}, +}; +use core::marker::PhantomData; +use smallvec::SmallVec; + +/// Trait representing a relationship [`Component`]. +/// A relationship consists of two [entities](Entity), one with this [`Component`], +/// and the other with the [`Other`](Relationship::Other). +pub(crate) trait Relationship: Component + Sized { + /// The other [`Component`] used to form this relationship. + type Other: Relationship; + + /// Whether this [`Relationship`] [`Component`] has the provided [`Entity`]. + fn has(&self, entity: Entity) -> bool; + + /// Create a new [`Relationship`] [`Component`] with the provided [`Entity`]. + fn new(entity: Entity) -> Self; + + /// Modify an existing [`Relationship`] [`Component`] to ensure it includes + /// the provided [`Entity`]. + fn with(self, entity: Entity) -> Self; + + /// Modify an existing [`Relationship`] [`Component`] to ensure it does not + /// include the provided [`Entity`]. + /// + /// Returns [`None`] if this [`Entity`] is the last member of this relationship. + fn without(self, entity: Entity) -> Option; + + /// Iterate over all [entities](Entity) this [`Relationship`] [`Component`] contains. + fn iter(&self) -> impl ExactSizeIterator; + + fn len(&self) -> usize { + self.iter().len() + } + + fn get(&self, index: usize) -> Option { + self.iter().nth(index) + } + + fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + world.commands().queue(move |world: &mut World| { + let b_ids_len = world + .get_entity(a_id) + .ok() + .and_then(|a| a.get::()) + .map(Self::len); + + let Some(b_ids_len) = b_ids_len else { return }; + + for b_id_index in 0..b_ids_len { + let b = world + .get_entity(a_id) + .ok() + .and_then(|a| a.get::()) + .map(|a_relationship| a_relationship.get(b_id_index).unwrap()) + .and_then(|b_id| world.get_entity_mut(b_id).ok()); + + let Some(mut b) = b else { return }; + + let _b_id = b.id(); + + let b_points_to_a = b + .get::() + .is_some_and(|b_relationship| b_relationship.has(a_id)); + + if !b_points_to_a { + let other = b + .take::() + .unwrap_or(Self::Other::new(a_id)) + .with(a_id); + + b.insert(other); + + if let Some(mut events) = + world.get_resource_mut::>>() + { + events.send(RelationshipEvent::::added(a_id, _b_id)); + } + } + } + }); + } + + fn disassociate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + let Some(a_relationship) = world.get::(a_id) else { + unreachable!("component hook should only be called when component is available"); + }; + + // Cloning to allow a user to `take` the component for modification + // [Entity; 7] chosen to keep b_ids at 64 bytes on 64 bit platforms. + let b_ids = a_relationship.iter().collect::>(); + + world.commands().queue(move |world: &mut World| { + for b_id in b_ids { + let a_points_to_b = world + .get_entity(a_id) + .ok() + .and_then(|a| a.get::()) + .is_some_and(|a_relationship| a_relationship.has(b_id)); + + let b_points_to_a = world + .get_entity(b_id) + .ok() + .and_then(|b| b.get::()) + .is_some_and(|b_relationship| b_relationship.has(a_id)); + + if b_points_to_a && !a_points_to_b { + if let Ok(mut b) = world.get_entity_mut(b_id) { + // Using a placeholder relationship to avoid triggering on_remove and on_insert + // hooks erroneously. + let mut placeholder = Self::Other::new(Entity::PLACEHOLDER); + let mut other = b.get_mut::().unwrap(); + let other = other.as_mut(); + + core::mem::swap(&mut placeholder, other); + + if let Some(mut new_other) = placeholder.without(a_id) { + core::mem::swap(&mut new_other, other); + } else { + b.remove::(); + } + } + + if let Some(mut events) = + world.get_resource_mut::>>() + { + events.send(RelationshipEvent::::removed(a_id, b_id)); + } + } + } + }); + } +} + +/// A relationship event. +#[derive(Event)] +pub enum RelationshipEvent { + /// A relationship was added between two [entities](Entity). + Added(RelationshipEventDetails), + /// A relationship was removed from two [entities](Entity). + Removed(RelationshipEventDetails), +} + +impl RelationshipEvent { + /// Create a new [`Added`](RelationshipEvent::Added) [`Event`] + pub const fn added(primary: Entity, foreign: Entity) -> Self { + Self::Added(RelationshipEventDetails::new(primary, foreign)) + } + + /// Create a new [`Removed`](RelationshipEvent::Removed) [`Event`] + pub const fn removed(primary: Entity, foreign: Entity) -> Self { + Self::Removed(RelationshipEventDetails::new(primary, foreign)) + } + + /// Get the primary [`Entity`] in this [`Event`]. + /// The primary is the _cause_ of the event, while the foreign is the relation. + pub const fn primary(&self) -> Entity { + match self { + Self::Added(details) | Self::Removed(details) => details.primary(), + } + } + + /// Get the foreign [`Entity`] in this [`Event`]. + /// The primary is the _cause_ of the event, while the foreign is the relation. + pub const fn foreign(&self) -> Entity { + match self { + Self::Added(details) | Self::Removed(details) => details.foreign(), + } + } +} + +impl core::fmt::Debug for RelationshipEvent { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Added(arg0) => f.debug_tuple("Added").field(arg0).finish(), + Self::Removed(arg0) => f.debug_tuple("Removed").field(arg0).finish(), + } + } +} + +impl PartialEq for RelationshipEvent { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Added(l0), Self::Added(r0)) | (Self::Removed(l0), Self::Removed(r0)) => l0 == r0, + _ => false, + } + } +} + +impl Eq for RelationshipEvent {} + +impl Clone for RelationshipEvent { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for RelationshipEvent {} + +/// The details of a [`RelationshipEvent`]. +pub struct RelationshipEventDetails { + primary: Entity, + foreign: Entity, + phantom_data: PhantomData, +} + +impl RelationshipEventDetails { + /// Create a new [`RelationshipEventDetails`] for a `primary` and a `foreign` [`Entity`]. + /// The `primary` [`Entity`] is the cause of the [`Event`], while the `foreign` + /// is the other member of the relationship. + pub const fn new(primary: Entity, foreign: Entity) -> Self { + Self { + primary, + foreign, + phantom_data: PhantomData, + } + } + + /// Get the [`Entity`] that caused this [`Event`] to be triggered. + pub const fn primary(&self) -> Entity { + self.primary + } + + /// Get the [`Entity`] related to the `primary` [`Entity`]. + pub const fn foreign(&self) -> Entity { + self.foreign + } +} + +impl core::fmt::Debug for RelationshipEventDetails { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("RelationshipEventDetails") + .field("primary", &self.primary) + .field("foreign", &self.foreign) + .finish() + } +} + +impl PartialEq for RelationshipEventDetails { + fn eq(&self, other: &Self) -> bool { + self.primary == other.primary && self.foreign == other.foreign + } +} + +impl Eq for RelationshipEventDetails {} + +impl Clone for RelationshipEventDetails { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for RelationshipEventDetails {} From 4932a9368b7c13492560de1e7f7c970f25ee5498 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 20 Oct 2024 09:32:27 +1100 Subject: [PATCH 16/25] Simplified `DespawnRecursive` Builder Pattern Co-Authored-By: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com> --- crates/bevy_hierarchy/src/despawn_recursive/command.rs | 10 +++++----- crates/bevy_hierarchy/src/despawn_recursive/ext.rs | 8 +++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/bevy_hierarchy/src/despawn_recursive/command.rs b/crates/bevy_hierarchy/src/despawn_recursive/command.rs index 59dc8d7d78d0e..e1faea328330b 100644 --- a/crates/bevy_hierarchy/src/despawn_recursive/command.rs +++ b/crates/bevy_hierarchy/src/despawn_recursive/command.rs @@ -50,7 +50,7 @@ pub struct DespawnRecursive { /// Whether this command will despawn the provided entity (`inclusive`) or just /// its descendants (`exclusive`). inclusive: bool, - /// Marker for the + /// Marker for the relationship type to be despawned. _phantom: PhantomData, } @@ -72,10 +72,10 @@ impl DespawnRecursive { self } - /// Control whether this [`Command`] should also despawn the target [`Entity`] (`true`) - /// or on its descendants (`false`). - pub const fn with_inclusion(mut self, inclusive: bool) -> Self { - self.inclusive = inclusive; + /// Control whether this [`Command`] should exclude the target [`Entity`], only despawning + /// its descendants. + pub const fn without_inclusion(mut self) -> Self { + self.inclusive = false; self } } diff --git a/crates/bevy_hierarchy/src/despawn_recursive/ext.rs b/crates/bevy_hierarchy/src/despawn_recursive/ext.rs index 4722fcaace7e5..cd7c98f7201af 100644 --- a/crates/bevy_hierarchy/src/despawn_recursive/ext.rs +++ b/crates/bevy_hierarchy/src/despawn_recursive/ext.rs @@ -46,8 +46,7 @@ impl DespawnRecursiveExt for EntityCommands<'_> { let entity = self.id(); self.commands().queue( DespawnRecursive::::new(entity) - .with_warn(warn) - .with_inclusion(true), + .with_warn(warn), ); } @@ -59,7 +58,7 @@ impl DespawnRecursiveExt for EntityCommands<'_> { self.commands().queue( DespawnRecursive::::new(entity) .with_warn(warn) - .with_inclusion(false), + .without_inclusion(), ); self } @@ -69,7 +68,6 @@ impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { fn despawn_recursive_with_option(self, warn: bool) { DespawnRecursive::::new(self.id()) .with_warn(warn) - .with_inclusion(true) .apply(self.into_world_mut()); } @@ -82,7 +80,7 @@ impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { self.world_scope(|world| { DespawnRecursive::::new(entity) .with_warn(warn) - .with_inclusion(false) + .without_inclusion() .apply(world); }); self From 4c4cde599500ff1358959bf298fdf22ce5aedcbc Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 20 Oct 2024 09:35:09 +1100 Subject: [PATCH 17/25] Add Warning to `swap` methods Co-Authored-By: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com> --- crates/bevy_hierarchy/src/many_to_many.rs | 4 ++++ crates/bevy_hierarchy/src/one_to_many.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/crates/bevy_hierarchy/src/many_to_many.rs b/crates/bevy_hierarchy/src/many_to_many.rs index ba00d66bc7edc..b480eb8a0238e 100644 --- a/crates/bevy_hierarchy/src/many_to_many.rs +++ b/crates/bevy_hierarchy/src/many_to_many.rs @@ -175,6 +175,10 @@ impl ManyToMany { } /// Swaps the entity at `a_index` with the entity at `b_index`. + /// + /// # Panics + /// + /// Will panic of either index is out-of-bounds. #[inline] pub fn swap(&mut self, a_index: usize, b_index: usize) { self.entities.swap(a_index, b_index); diff --git a/crates/bevy_hierarchy/src/one_to_many.rs b/crates/bevy_hierarchy/src/one_to_many.rs index f444b501faaad..e14f0c0b9d305 100644 --- a/crates/bevy_hierarchy/src/one_to_many.rs +++ b/crates/bevy_hierarchy/src/one_to_many.rs @@ -175,6 +175,10 @@ impl OneToMany { } /// Swaps the entity at `a_index` with the entity at `b_index`. + /// + /// # Panics + /// + /// Will panic of either index is out-of-bounds. #[inline] pub fn swap(&mut self, a_index: usize, b_index: usize) { self.entities.swap(a_index, b_index); From 60a08ec26f4101b10205288fb4ec27061eaa339f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 20 Oct 2024 09:47:07 +1100 Subject: [PATCH 18/25] Replaced references to `a` and `b` entities with `primary` and `foreign` This better aligns with relational database terminology, the closest analogue to the functionality provided here. Co-Authored-By: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com> --- crates/bevy_hierarchy/src/many_to_many.rs | 5 +- crates/bevy_hierarchy/src/one_to_one.rs | 5 +- crates/bevy_hierarchy/src/relationship.rs | 82 ++++++++++++----------- 3 files changed, 52 insertions(+), 40 deletions(-) diff --git a/crates/bevy_hierarchy/src/many_to_many.rs b/crates/bevy_hierarchy/src/many_to_many.rs index b480eb8a0238e..a94fb06c10e88 100644 --- a/crates/bevy_hierarchy/src/many_to_many.rs +++ b/crates/bevy_hierarchy/src/many_to_many.rs @@ -14,7 +14,10 @@ use crate::relationship::Relationship; /// Represents one half of a many-to-many relationship between an [`Entity`] and some number of other [entities](Entity). /// -/// The type of relationship is denoted by the parameter `R`. +/// The type of relationship is denoted by the parameters `FK` and `PK`, shorthand +/// for Primary Key and Foreign Key. +/// An undirected relationship would have equal `FK` and `PK` types. +/// Whereas, an directed relationship would have differing parameters. #[derive(Component)] #[component( on_insert = ::associate, diff --git a/crates/bevy_hierarchy/src/one_to_one.rs b/crates/bevy_hierarchy/src/one_to_one.rs index 5f194084f2a78..2d663cc02a801 100644 --- a/crates/bevy_hierarchy/src/one_to_one.rs +++ b/crates/bevy_hierarchy/src/one_to_one.rs @@ -15,7 +15,10 @@ use crate::relationship::Relationship; /// Represents one half of a one-to-one relationship between two [entities](Entity). /// -/// The type of relationship is denoted by the parameter `R`. +/// The type of relationship is denoted by the parameters `FK` and `PK`, shorthand +/// for Primary Key and Foreign Key. +/// An undirected relationship would have equal `FK` and `PK` types. +/// Whereas, an directed relationship would have differing parameters. #[derive(Component)] #[component( on_insert = ::associate, diff --git a/crates/bevy_hierarchy/src/relationship.rs b/crates/bevy_hierarchy/src/relationship.rs index 938d4666ae5ff..8d09746d1f7bd 100644 --- a/crates/bevy_hierarchy/src/relationship.rs +++ b/crates/bevy_hierarchy/src/relationship.rs @@ -8,8 +8,14 @@ use core::marker::PhantomData; use smallvec::SmallVec; /// Trait representing a relationship [`Component`]. +/// /// A relationship consists of two [entities](Entity), one with this [`Component`], /// and the other with the [`Other`](Relationship::Other). +/// These entities are referred to as `primary` and `foreign` to align with typical +/// relational database terminology. +/// The `primary` owns a component which contains some number of `foreign` entities. +/// This trait is designed to ensure that those `foreign` entities also own a component of type +/// [Other](Relationship::Other), where its `foreign` entities include the aforementioned `primary`. pub(crate) trait Relationship: Component + Sized { /// The other [`Component`] used to form this relationship. type Other: Relationship; @@ -41,94 +47,94 @@ pub(crate) trait Relationship: Component + Sized { self.iter().nth(index) } - fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { + fn associate(mut world: DeferredWorld<'_>, primary_id: Entity, _component: ComponentId) { world.commands().queue(move |world: &mut World| { - let b_ids_len = world - .get_entity(a_id) + let foreign_ids_len = world + .get_entity(primary_id) .ok() .and_then(|a| a.get::()) .map(Self::len); - let Some(b_ids_len) = b_ids_len else { return }; + let Some(foreign_ids_len) = foreign_ids_len else { return }; - for b_id_index in 0..b_ids_len { - let b = world - .get_entity(a_id) + for foreign_id_index in 0..foreign_ids_len { + let foreign = world + .get_entity(primary_id) .ok() - .and_then(|a| a.get::()) - .map(|a_relationship| a_relationship.get(b_id_index).unwrap()) - .and_then(|b_id| world.get_entity_mut(b_id).ok()); + .and_then(|primary| primary.get::()) + .map(|primary_relationship| primary_relationship.get(foreign_id_index).unwrap()) + .and_then(|foreign_id| world.get_entity_mut(foreign_id).ok()); - let Some(mut b) = b else { return }; + let Some(mut foreign) = foreign else { return }; - let _b_id = b.id(); + let foreign_id = foreign.id(); - let b_points_to_a = b + let foreign_points_to_primary = foreign .get::() - .is_some_and(|b_relationship| b_relationship.has(a_id)); + .is_some_and(|foreign_relationship| foreign_relationship.has(primary_id)); - if !b_points_to_a { - let other = b + if !foreign_points_to_primary { + let other = foreign .take::() - .unwrap_or(Self::Other::new(a_id)) - .with(a_id); + .unwrap_or(Self::Other::new(primary_id)) + .with(primary_id); - b.insert(other); + foreign.insert(other); if let Some(mut events) = world.get_resource_mut::>>() { - events.send(RelationshipEvent::::added(a_id, _b_id)); + events.send(RelationshipEvent::::added(primary_id, foreign_id)); } } } }); } - fn disassociate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { - let Some(a_relationship) = world.get::(a_id) else { + fn disassociate(mut world: DeferredWorld<'_>, primary_id: Entity, _component: ComponentId) { + let Some(primary_relationship) = world.get::(primary_id) else { unreachable!("component hook should only be called when component is available"); }; // Cloning to allow a user to `take` the component for modification // [Entity; 7] chosen to keep b_ids at 64 bytes on 64 bit platforms. - let b_ids = a_relationship.iter().collect::>(); + let foreign_ids = primary_relationship.iter().collect::>(); world.commands().queue(move |world: &mut World| { - for b_id in b_ids { - let a_points_to_b = world - .get_entity(a_id) + for foreign_id in foreign_ids { + let primary_points_to_foreign = world + .get_entity(primary_id) .ok() - .and_then(|a| a.get::()) - .is_some_and(|a_relationship| a_relationship.has(b_id)); + .and_then(|primary| primary.get::()) + .is_some_and(|primary_relationship| primary_relationship.has(foreign_id)); - let b_points_to_a = world - .get_entity(b_id) + let foreign_points_to_primary = world + .get_entity(foreign_id) .ok() - .and_then(|b| b.get::()) - .is_some_and(|b_relationship| b_relationship.has(a_id)); + .and_then(|foreign| foreign.get::()) + .is_some_and(|foreign_relationship| foreign_relationship.has(primary_id)); - if b_points_to_a && !a_points_to_b { - if let Ok(mut b) = world.get_entity_mut(b_id) { + if foreign_points_to_primary && !primary_points_to_foreign { + if let Ok(mut foreign) = world.get_entity_mut(foreign_id) { // Using a placeholder relationship to avoid triggering on_remove and on_insert // hooks erroneously. let mut placeholder = Self::Other::new(Entity::PLACEHOLDER); - let mut other = b.get_mut::().unwrap(); + let mut other = foreign.get_mut::().unwrap(); let other = other.as_mut(); core::mem::swap(&mut placeholder, other); - if let Some(mut new_other) = placeholder.without(a_id) { + if let Some(mut new_other) = placeholder.without(primary_id) { core::mem::swap(&mut new_other, other); } else { - b.remove::(); + foreign.remove::(); } } if let Some(mut events) = world.get_resource_mut::>>() { - events.send(RelationshipEvent::::removed(a_id, b_id)); + events.send(RelationshipEvent::::removed(primary_id, foreign_id)); } } } From f4c0adb58a0817a9048ddd1c5901c31b73f589c7 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 20 Oct 2024 09:47:31 +1100 Subject: [PATCH 19/25] Formatting --- crates/bevy_hierarchy/src/despawn_recursive/ext.rs | 6 ++---- crates/bevy_hierarchy/src/many_to_many.rs | 4 ++-- crates/bevy_hierarchy/src/one_to_many.rs | 4 ++-- crates/bevy_hierarchy/src/relationship.rs | 10 +++++++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/bevy_hierarchy/src/despawn_recursive/ext.rs b/crates/bevy_hierarchy/src/despawn_recursive/ext.rs index cd7c98f7201af..519fd5ac451b5 100644 --- a/crates/bevy_hierarchy/src/despawn_recursive/ext.rs +++ b/crates/bevy_hierarchy/src/despawn_recursive/ext.rs @@ -44,10 +44,8 @@ pub trait DespawnRecursiveExt: Sized { impl DespawnRecursiveExt for EntityCommands<'_> { fn despawn_recursive_with_option(mut self, warn: bool) { let entity = self.id(); - self.commands().queue( - DespawnRecursive::::new(entity) - .with_warn(warn), - ); + self.commands() + .queue(DespawnRecursive::::new(entity).with_warn(warn)); } fn despawn_descendants_with_option( diff --git a/crates/bevy_hierarchy/src/many_to_many.rs b/crates/bevy_hierarchy/src/many_to_many.rs index a94fb06c10e88..2d8b0485d84ff 100644 --- a/crates/bevy_hierarchy/src/many_to_many.rs +++ b/crates/bevy_hierarchy/src/many_to_many.rs @@ -178,9 +178,9 @@ impl ManyToMany { } /// Swaps the entity at `a_index` with the entity at `b_index`. - /// + /// /// # Panics - /// + /// /// Will panic of either index is out-of-bounds. #[inline] pub fn swap(&mut self, a_index: usize, b_index: usize) { diff --git a/crates/bevy_hierarchy/src/one_to_many.rs b/crates/bevy_hierarchy/src/one_to_many.rs index e14f0c0b9d305..0f52f8da8f6c0 100644 --- a/crates/bevy_hierarchy/src/one_to_many.rs +++ b/crates/bevy_hierarchy/src/one_to_many.rs @@ -175,9 +175,9 @@ impl OneToMany { } /// Swaps the entity at `a_index` with the entity at `b_index`. - /// + /// /// # Panics - /// + /// /// Will panic of either index is out-of-bounds. #[inline] pub fn swap(&mut self, a_index: usize, b_index: usize) { diff --git a/crates/bevy_hierarchy/src/relationship.rs b/crates/bevy_hierarchy/src/relationship.rs index 8d09746d1f7bd..fba2758197390 100644 --- a/crates/bevy_hierarchy/src/relationship.rs +++ b/crates/bevy_hierarchy/src/relationship.rs @@ -8,7 +8,7 @@ use core::marker::PhantomData; use smallvec::SmallVec; /// Trait representing a relationship [`Component`]. -/// +/// /// A relationship consists of two [entities](Entity), one with this [`Component`], /// and the other with the [`Other`](Relationship::Other). /// These entities are referred to as `primary` and `foreign` to align with typical @@ -55,7 +55,9 @@ pub(crate) trait Relationship: Component + Sized { .and_then(|a| a.get::()) .map(Self::len); - let Some(foreign_ids_len) = foreign_ids_len else { return }; + let Some(foreign_ids_len) = foreign_ids_len else { + return; + }; for foreign_id_index in 0..foreign_ids_len { let foreign = world @@ -98,7 +100,9 @@ pub(crate) trait Relationship: Component + Sized { // Cloning to allow a user to `take` the component for modification // [Entity; 7] chosen to keep b_ids at 64 bytes on 64 bit platforms. - let foreign_ids = primary_relationship.iter().collect::>(); + let foreign_ids = primary_relationship + .iter() + .collect::>(); world.commands().queue(move |world: &mut World| { for foreign_id in foreign_ids { From 0080d671122a0b7cf6c33c9fd2e41e06b82faed2 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 20 Oct 2024 10:01:23 +1100 Subject: [PATCH 20/25] Fixed Documentation --- .../src/despawn_recursive/command.rs | 2 +- crates/bevy_hierarchy/src/many_to_many.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/bevy_hierarchy/src/despawn_recursive/command.rs b/crates/bevy_hierarchy/src/despawn_recursive/command.rs index e1faea328330b..db72e482f9a2a 100644 --- a/crates/bevy_hierarchy/src/despawn_recursive/command.rs +++ b/crates/bevy_hierarchy/src/despawn_recursive/command.rs @@ -12,7 +12,7 @@ use bevy_utils::tracing::debug; /// If an [`Entity`] cannot be found, a warning will be emitted. /// /// The target [`Entity`] can be excluded from being despawned using -/// [`with_inclusion`](`DespawnRecursive::with_inclusion`). +/// [`without_inclusion`](`DespawnRecursive::without_inclusion`). /// /// Warnings can be disabled using [`with_warn`](`DespawnRecursive::with_warn`). /// diff --git a/crates/bevy_hierarchy/src/many_to_many.rs b/crates/bevy_hierarchy/src/many_to_many.rs index 2d8b0485d84ff..41b45fdbf2198 100644 --- a/crates/bevy_hierarchy/src/many_to_many.rs +++ b/crates/bevy_hierarchy/src/many_to_many.rs @@ -192,9 +192,9 @@ impl ManyToMany { /// /// For the underlying implementation, see [`slice::sort_by`]. /// - /// For the unstable version, see [`sort_unstable_by`](OneToMany::sort_unstable_by). + /// For the unstable version, see [`sort_unstable_by`](ManyToMany::sort_unstable_by). /// - /// See also [`sort_by_key`](OneToMany::sort_by_key), [`sort_by_cached_key`](OneToMany::sort_by_cached_key). + /// See also [`sort_by_key`](ManyToMany::sort_by_key), [`sort_by_cached_key`](ManyToMany::sort_by_cached_key). #[inline] pub fn sort_by(&mut self, compare: F) where @@ -208,9 +208,9 @@ impl ManyToMany { /// /// For the underlying implementation, see [`slice::sort_by_key`]. /// - /// For the unstable version, see [`sort_unstable_by_key`](OneToMany::sort_unstable_by_key). + /// For the unstable version, see [`sort_unstable_by_key`](ManyToMany::sort_unstable_by_key). /// - /// See also [`sort_by`](OneToMany::sort_by), [`sort_by_cached_key`](OneToMany::sort_by_cached_key). + /// See also [`sort_by`](ManyToMany::sort_by), [`sort_by_cached_key`](ManyToMany::sort_by_cached_key). #[inline] pub fn sort_by_key(&mut self, compare: F) where @@ -226,7 +226,7 @@ impl ManyToMany { /// /// For the underlying implementation, see [`slice::sort_by_cached_key`]. /// - /// See also [`sort_by`](OneToMany::sort_by), [`sort_by_key`](OneToMany::sort_by_key). + /// See also [`sort_by`](ManyToMany::sort_by), [`sort_by_key`](ManyToMany::sort_by_key). #[inline] pub fn sort_by_cached_key(&mut self, compare: F) where @@ -241,9 +241,9 @@ impl ManyToMany { /// /// For the underlying implementation, see [`slice::sort_unstable_by`]. /// - /// For the stable version, see [`sort_by`](OneToMany::sort_by). + /// For the stable version, see [`sort_by`](ManyToMany::sort_by). /// - /// See also [`sort_unstable_by_key`](OneToMany::sort_unstable_by_key). + /// See also [`sort_unstable_by_key`](ManyToMany::sort_unstable_by_key). #[inline] pub fn sort_unstable_by(&mut self, compare: F) where @@ -257,9 +257,9 @@ impl ManyToMany { /// /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. /// - /// For the stable version, see [`sort_by_key`](OneToMany::sort_by_key). + /// For the stable version, see [`sort_by_key`](ManyToMany::sort_by_key). /// - /// See also [`sort_unstable_by`](OneToMany::sort_unstable_by). + /// See also [`sort_unstable_by`](ManyToMany::sort_unstable_by). #[inline] pub fn sort_unstable_by_key(&mut self, compare: F) where From a11fcc2cfeb66857168a9ac060d0d11ab26f9fba Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Sun, 20 Oct 2024 10:37:52 +1100 Subject: [PATCH 21/25] Updated crate documentation to call out invariant footguns Co-Authored-By: Nuutti Kotivuori --- crates/bevy_hierarchy/src/lib.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index 667007470228e..6a6687d268b85 100755 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -5,7 +5,7 @@ html_favicon_url = "https://bevyengine.org/assets/icon.png" )] -//! Parent-child relationships for Bevy entities. +//! Relationships for Bevy entities. //! //! You should use the tools in this crate //! whenever you want to organize your entities in a hierarchical fashion, @@ -44,6 +44,22 @@ //! In most cases, these operations will invalidate the hierarchy. //! Instead, you should use the provided [hierarchical despawn extension methods]. //! +//! ## Generic relationships +//! +//! This crate also provides a number of components defining custom relationships, such as: +//! +//! * [`OneToOne`] +//! * [`OneToMany`] and [`ManyToOne`] +//! * [`ManyToMany`] +//! +//! These components will use component hooks to ensure both members of a relationship +//! have appropriate data for that relationship. +//! +//! Note that to maintain the invariants of a relationship, you must not mutate the component +//! in-place using methods like [`swap`](core::mem::swap), as these bypass the currently +//! available component hooks. To mutate a relationship, instead replace the component with an +//! updated value. +//! //! [command and world]: BuildChildren //! [diagnostic plugin]: ValidParentCheckPlugin //! [events]: HierarchyEvent From acde0c8b4b74e39851959812545d3cf470aab2d3 Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Sun, 20 Oct 2024 17:33:54 +1100 Subject: [PATCH 22/25] Update crates/bevy_hierarchy/src/many_to_many.rs --- crates/bevy_hierarchy/src/many_to_many.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_hierarchy/src/many_to_many.rs b/crates/bevy_hierarchy/src/many_to_many.rs index 41b45fdbf2198..c21ed3b0ff218 100644 --- a/crates/bevy_hierarchy/src/many_to_many.rs +++ b/crates/bevy_hierarchy/src/many_to_many.rs @@ -181,7 +181,7 @@ impl ManyToMany { /// /// # Panics /// - /// Will panic of either index is out-of-bounds. + /// Will panic if either index is out-of-bounds. #[inline] pub fn swap(&mut self, a_index: usize, b_index: usize) { self.entities.swap(a_index, b_index); From 10b620b6e21b04de63f567e93f3317abdd554848 Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Sun, 20 Oct 2024 17:34:00 +1100 Subject: [PATCH 23/25] Update crates/bevy_hierarchy/src/one_to_many.rs --- crates/bevy_hierarchy/src/one_to_many.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_hierarchy/src/one_to_many.rs b/crates/bevy_hierarchy/src/one_to_many.rs index 0f52f8da8f6c0..1f831e2e00733 100644 --- a/crates/bevy_hierarchy/src/one_to_many.rs +++ b/crates/bevy_hierarchy/src/one_to_many.rs @@ -178,7 +178,7 @@ impl OneToMany { /// /// # Panics /// - /// Will panic of either index is out-of-bounds. + /// Will panic if either index is out-of-bounds. #[inline] pub fn swap(&mut self, a_index: usize, b_index: usize) { self.entities.swap(a_index, b_index); From 794d460c64aed508001cb4be98ef6c05f071939a Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 21 Oct 2024 08:07:25 +1100 Subject: [PATCH 24/25] Removed Generic Relationships Co-Authored-By: iiYese <83026177+iiYese@users.noreply.github.com> --- crates/bevy_hierarchy/src/family.rs | 2 +- crates/bevy_hierarchy/src/lib.rs | 37 +-- crates/bevy_hierarchy/src/many_to_many.rs | 334 -------------------- crates/bevy_hierarchy/src/many_to_one.rs | 4 +- crates/bevy_hierarchy/src/one_to_one.rs | 369 ---------------------- 5 files changed, 11 insertions(+), 735 deletions(-) delete mode 100644 crates/bevy_hierarchy/src/many_to_many.rs delete mode 100644 crates/bevy_hierarchy/src/one_to_one.rs diff --git a/crates/bevy_hierarchy/src/family.rs b/crates/bevy_hierarchy/src/family.rs index 18f2ef20da784..a6f4d0fede3bd 100644 --- a/crates/bevy_hierarchy/src/family.rs +++ b/crates/bevy_hierarchy/src/family.rs @@ -1,4 +1,4 @@ -use crate::{ManyToOne, OneToMany, RelationshipEvent}; +use crate::{many_to_one::ManyToOne, one_to_many::OneToMany, relationship::RelationshipEvent}; /// A familial relationship #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index 6a6687d268b85..ea4746a5ed18a 100755 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -37,21 +37,6 @@ //! Similarly, unassigning a child in the parent //! will always unassign the parent in the child. //! -//! ## Despawning entities -//! -//! The commands and methods provided by `bevy_ecs` to despawn entities -//! are not capable of automatically despawning hierarchies of entities. -//! In most cases, these operations will invalidate the hierarchy. -//! Instead, you should use the provided [hierarchical despawn extension methods]. -//! -//! ## Generic relationships -//! -//! This crate also provides a number of components defining custom relationships, such as: -//! -//! * [`OneToOne`] -//! * [`OneToMany`] and [`ManyToOne`] -//! * [`ManyToMany`] -//! //! These components will use component hooks to ensure both members of a relationship //! have appropriate data for that relationship. //! @@ -60,6 +45,13 @@ //! available component hooks. To mutate a relationship, instead replace the component with an //! updated value. //! +//! ## Despawning entities +//! +//! The commands and methods provided by `bevy_ecs` to despawn entities +//! are not capable of automatically despawning hierarchies of entities. +//! In most cases, these operations will invalidate the hierarchy. +//! Instead, you should use the provided [hierarchical despawn extension methods]. +//! //! [command and world]: BuildChildren //! [diagnostic plugin]: ValidParentCheckPlugin //! [events]: HierarchyEvent @@ -69,20 +61,9 @@ extern crate alloc; +pub(crate) mod many_to_one; +pub(crate) mod one_to_many; pub(crate) mod relationship; -pub use relationship::*; - -mod one_to_one; -pub use one_to_one::OneToOne; - -mod one_to_many; -pub use one_to_many::OneToMany; - -mod many_to_one; -pub use many_to_one::ManyToOne; - -mod many_to_many; -pub use many_to_many::ManyToMany; mod family; pub use family::*; diff --git a/crates/bevy_hierarchy/src/many_to_many.rs b/crates/bevy_hierarchy/src/many_to_many.rs deleted file mode 100644 index c21ed3b0ff218..0000000000000 --- a/crates/bevy_hierarchy/src/many_to_many.rs +++ /dev/null @@ -1,334 +0,0 @@ -#[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; -use bevy_ecs::{ - component::Component, - entity::{Entity, VisitEntitiesMut}, -}; -use core::{fmt::Debug, marker::PhantomData, ops::Deref}; -use smallvec::{smallvec, SmallVec}; - -use crate::relationship::Relationship; - -/// Represents one half of a many-to-many relationship between an [`Entity`] and some number of other [entities](Entity). -/// -/// The type of relationship is denoted by the parameters `FK` and `PK`, shorthand -/// for Primary Key and Foreign Key. -/// An undirected relationship would have equal `FK` and `PK` types. -/// Whereas, an directed relationship would have differing parameters. -#[derive(Component)] -#[component( - on_insert = ::associate, - on_replace = ::disassociate, -)] -#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - PartialEq, - Debug, - FromWorld - ) -)] -pub struct ManyToMany { - // [Entity; 7] chosen to keep entities at 64 bytes on 64 bit platforms. - entities: SmallVec<[Entity; 7]>, - #[cfg_attr(feature = "reflect", reflect(ignore))] - _phantom: PhantomData, -} - -impl Relationship for ManyToMany { - type Other = ManyToMany; - - fn has(&self, entity: Entity) -> bool { - self.entities.contains(&entity) - } - - fn new(entity: Entity) -> Self { - Self { - entities: smallvec![entity], - _phantom: PhantomData, - } - } - - fn with(mut self, entity: Entity) -> Self { - if !self.has(entity) { - self.entities.push(entity); - } - - self - } - - fn without(mut self, entity: Entity) -> Option { - self.entities.retain(|&mut id| id != entity); - - (!self.entities.is_empty()).then_some(self) - } - - fn iter(&self) -> impl ExactSizeIterator { - self.entities.iter().copied() - } -} - -impl PartialEq for ManyToMany { - fn eq(&self, other: &Self) -> bool { - self.entities == other.entities - } -} - -impl Eq for ManyToMany {} - -impl Debug for ManyToMany { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "Has Many {:?} ({}) With Many ({})", - self.entities, - core::any::type_name::(), - core::any::type_name::() - ) - } -} - -impl VisitEntitiesMut for ManyToMany { - fn visit_entities_mut(&mut self, mut f: F) { - for entity in &mut self.entities { - f(entity); - } - } -} - -impl Deref for ManyToMany { - type Target = [Entity]; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.entities - } -} - -impl<'a, FK, PK> IntoIterator for &'a ManyToMany { - type Item = ::Item; - - type IntoIter = core::slice::Iter<'a, Entity>; - - #[inline(always)] - fn into_iter(self) -> Self::IntoIter { - self.entities.iter() - } -} - -impl FromIterator for ManyToMany { - fn from_iter>(iter: T) -> Self { - Self::from_smallvec(iter.into_iter().collect()) - } -} - -impl Default for ManyToMany { - fn default() -> Self { - Self::new() - } -} - -impl ManyToMany { - /// Gets the other [`Entity`] as a slice of length 1. - #[inline(always)] - pub fn as_slice(&self) -> &[Entity] { - &self.entities - } - - /// Create a new relationship. - #[inline(always)] - #[must_use] - pub fn new() -> Self { - Self::from_smallvec(SmallVec::new()) - } - - #[inline(always)] - #[must_use] - fn from_smallvec(entities: SmallVec<[Entity; 7]>) -> Self { - Self { - entities, - _phantom: PhantomData, - } - } - - /// Ensures the provided [`Entity`] is present in this relationship. - #[inline(always)] - #[must_use] - pub fn with(mut self, other: Entity) -> Self { - if !self.entities.contains(&other) { - self.entities.push(other); - } - self - } - - /// Ensures the provided [`Entity`] is _not_ present in this relationship. - #[inline(always)] - #[must_use] - pub fn without(mut self, other: Entity) -> Self { - self.entities.retain(|&mut e| e != other); - self - } - - /// Swaps the entity at `a_index` with the entity at `b_index`. - /// - /// # Panics - /// - /// Will panic if either index is out-of-bounds. - #[inline] - pub fn swap(&mut self, a_index: usize, b_index: usize) { - self.entities.swap(a_index, b_index); - } - - /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided comparator function. - /// - /// For the underlying implementation, see [`slice::sort_by`]. - /// - /// For the unstable version, see [`sort_unstable_by`](ManyToMany::sort_unstable_by). - /// - /// See also [`sort_by_key`](ManyToMany::sort_by_key), [`sort_by_cached_key`](ManyToMany::sort_by_cached_key). - #[inline] - pub fn sort_by(&mut self, compare: F) - where - F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, - { - self.entities.sort_by(compare); - } - - /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. - /// - /// For the underlying implementation, see [`slice::sort_by_key`]. - /// - /// For the unstable version, see [`sort_unstable_by_key`](ManyToMany::sort_unstable_by_key). - /// - /// See also [`sort_by`](ManyToMany::sort_by), [`sort_by_cached_key`](ManyToMany::sort_by_cached_key). - #[inline] - pub fn sort_by_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.entities.sort_by_key(compare); - } - - /// Sorts entities [stably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. Only evaluates each key at most - /// once per sort, caching the intermediate results in memory. - /// - /// For the underlying implementation, see [`slice::sort_by_cached_key`]. - /// - /// See also [`sort_by`](ManyToMany::sort_by), [`sort_by_key`](ManyToMany::sort_by_key). - #[inline] - pub fn sort_by_cached_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.entities.sort_by_cached_key(compare); - } - - /// Sorts entities [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided comparator function. - /// - /// For the underlying implementation, see [`slice::sort_unstable_by`]. - /// - /// For the stable version, see [`sort_by`](ManyToMany::sort_by). - /// - /// See also [`sort_unstable_by_key`](ManyToMany::sort_unstable_by_key). - #[inline] - pub fn sort_unstable_by(&mut self, compare: F) - where - F: FnMut(&Entity, &Entity) -> core::cmp::Ordering, - { - self.entities.sort_unstable_by(compare); - } - - /// Sorts entities [unstably](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability) - /// in place using the provided key extraction function. - /// - /// For the underlying implementation, see [`slice::sort_unstable_by_key`]. - /// - /// For the stable version, see [`sort_by_key`](ManyToMany::sort_by_key). - /// - /// See also [`sort_unstable_by`](ManyToMany::sort_unstable_by). - #[inline] - pub fn sort_unstable_by_key(&mut self, compare: F) - where - F: FnMut(&Entity) -> K, - K: Ord, - { - self.entities.sort_unstable_by_key(compare); - } -} - -#[cfg(test)] -mod tests { - use bevy_ecs::world::World; - - use super::ManyToMany; - - /// A familial relationship - struct Friendship; - - /// Shorthand for a group of friends - type Friends = ManyToMany; - - #[test] - fn simple_add_then_remove() { - let mut world = World::new(); - - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn_empty().id(); - let c = world.spawn(Friends::new().with(a).with(b)).id(); - - world.flush(); - - assert_eq!( - world - .get::(a) - .map(|c| c.iter().copied().collect::>()), - Some(vec![c]) - ); - assert_eq!( - world - .get::(b) - .map(|c| c.iter().copied().collect::>()), - Some(vec![c]) - ); - assert_eq!( - world - .get::(c) - .map(|c| c.iter().copied().collect::>()), - Some(vec![a, b]) - ); - - world.entity_mut(a).remove::(); - - world.flush(); - - assert_eq!(world.get::(a), None); - assert_eq!( - world - .get::(b) - .map(|c| c.iter().copied().collect::>()), - Some(vec![c]) - ); - assert_eq!( - world - .get::(c) - .map(|c| c.iter().copied().collect::>()), - Some(vec![b]) - ); - } -} diff --git a/crates/bevy_hierarchy/src/many_to_one.rs b/crates/bevy_hierarchy/src/many_to_one.rs index eac8877f4d905..c2291635963b6 100644 --- a/crates/bevy_hierarchy/src/many_to_one.rs +++ b/crates/bevy_hierarchy/src/many_to_one.rs @@ -11,9 +11,7 @@ use bevy_ecs::{ }; use core::{fmt::Debug, marker::PhantomData, ops::Deref}; -use crate::relationship::Relationship; - -use super::OneToMany; +use crate::{one_to_many::OneToMany, relationship::Relationship}; /// Represents one half of a one-to-many relationship between an [`Entity`] and some number of other [entities](Entity). /// diff --git a/crates/bevy_hierarchy/src/one_to_one.rs b/crates/bevy_hierarchy/src/one_to_one.rs deleted file mode 100644 index 2d663cc02a801..0000000000000 --- a/crates/bevy_hierarchy/src/one_to_one.rs +++ /dev/null @@ -1,369 +0,0 @@ -#[cfg(feature = "reflect")] -use bevy_ecs::reflect::{ - ReflectComponent, ReflectFromWorld, ReflectMapEntities, ReflectVisitEntities, - ReflectVisitEntitiesMut, -}; -use bevy_ecs::{ - component::Component, - entity::{Entity, VisitEntities, VisitEntitiesMut}, - traversal::Traversal, - world::{FromWorld, World}, -}; -use core::{fmt::Debug, marker::PhantomData, ops::Deref}; - -use crate::relationship::Relationship; - -/// Represents one half of a one-to-one relationship between two [entities](Entity). -/// -/// The type of relationship is denoted by the parameters `FK` and `PK`, shorthand -/// for Primary Key and Foreign Key. -/// An undirected relationship would have equal `FK` and `PK` types. -/// Whereas, an directed relationship would have differing parameters. -#[derive(Component)] -#[component( - on_insert = ::associate, - on_replace = ::disassociate, -)] -#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr( - feature = "reflect", - reflect( - Component, - MapEntities, - VisitEntities, - VisitEntitiesMut, - PartialEq, - Debug, - FromWorld - ) -)] -pub struct OneToOne { - entity: Entity, - #[cfg_attr(feature = "reflect", reflect(ignore))] - _phantom: PhantomData, -} - -impl Relationship for OneToOne { - type Other = OneToOne; - - fn has(&self, entity: Entity) -> bool { - self.entity == entity - } - - fn new(entity: Entity) -> Self { - Self { - entity, - _phantom: PhantomData, - } - } - - fn with(self, entity: Entity) -> Self { - Self { - entity, - _phantom: PhantomData, - } - } - - fn without(self, entity: Entity) -> Option { - (self.entity != entity).then_some(self) - } - - fn iter(&self) -> impl ExactSizeIterator { - [self.entity].into_iter() - } -} - -impl PartialEq for OneToOne { - fn eq(&self, other: &Self) -> bool { - self.entity == other.entity - } -} - -impl Eq for OneToOne {} - -impl Debug for OneToOne { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "Has One {} ({}) With One ({})", - self.entity, - core::any::type_name::(), - core::any::type_name::() - ) - } -} - -impl VisitEntities for OneToOne { - fn visit_entities(&self, mut f: F) { - f(self.entity); - } -} - -impl VisitEntitiesMut for OneToOne { - fn visit_entities_mut(&mut self, mut f: F) { - f(&mut self.entity); - } -} - -// TODO: We need to impl either FromWorld or Default so OneToOne can be registered as Reflect. -// This is because Reflect deserialize by creating an instance and apply a patch on top. -// However OneToOne should only ever be set with a real user-defined entity. It's worth looking into -// better ways to handle cases like this. -impl FromWorld for OneToOne { - #[inline(always)] - fn from_world(_world: &mut World) -> Self { - Self { - entity: Entity::PLACEHOLDER, - _phantom: PhantomData, - } - } -} - -impl Deref for OneToOne { - type Target = Entity; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.entity - } -} - -/// This provides generalized hierarchy traversal for use in [event propagation]. -/// -/// [event propagation]: bevy_ecs::observer::Trigger::propagate -impl Traversal for &OneToOne { - fn traverse(item: Self::Item<'_>) -> Option { - Some(item.entity) - } -} - -impl OneToOne { - /// Gets the [`Entity`] ID of the other member of this one-to-one relationship. - #[inline(always)] - pub fn get(&self) -> Entity { - self.entity - } - - /// Gets the other [`Entity`] as a slice of length 1. - #[inline(always)] - pub fn as_slice(&self) -> &[Entity] { - core::slice::from_ref(&self.entity) - } - - /// Create a new relationship with the provided [`Entity`]. - #[inline(always)] - #[must_use] - pub fn new(other: Entity) -> Self { - Self { - entity: other, - _phantom: PhantomData, - } - } -} - -#[cfg(test)] -mod tests { - use bevy_ecs::{event::Events, world::World}; - - use crate::RelationshipEvent; - - use super::*; - - /// An example relationship between two entities - struct Friendship; - - /// Shorthand for a best friend relationship - type Friend = OneToOne; - - #[test] - fn simple_add_then_remove() { - let mut world = World::new(); - - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn(Friend::new(a)).id(); - - world.flush(); - - assert_eq!(world.get::(a), Some(&Friend::new(b))); - assert_eq!(world.get::(b), Some(&Friend::new(a))); - - world.entity_mut(a).remove::(); - - world.flush(); - - assert_eq!(world.get::(a), None); - assert_eq!(world.get::(b), None); - } - - #[test] - fn triangular_break_up() { - let mut world = World::new(); - - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn(Friend::new(a)).id(); - let c = world.spawn_empty().id(); - - world.flush(); - - assert_eq!(world.get::(a), Some(&Friend::new(b))); - assert_eq!(world.get::(b), Some(&Friend::new(a))); - assert_eq!(world.get::(c), None); - - world.entity_mut(a).insert(Friend::new(c)); - - world.flush(); - - assert_eq!(world.get::(a), Some(&Friend::new(c))); - assert_eq!(world.get::(b), None); - assert_eq!(world.get::(c), Some(&Friend::new(a))); - } - - #[test] - fn repeated_adding() { - let mut world = World::new(); - - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn_empty().id(); - - world.entity_mut(a).insert(Friend::new(b)); - world.entity_mut(a).insert(Friend::new(b)); - world.entity_mut(a).insert(Friend::new(b)); - - world.flush(); - - assert_eq!(world.get::(a), Some(&Friend::new(b))); - assert_eq!(world.get::(b), Some(&Friend::new(a))); - } - - #[test] - fn swap() { - let mut world = World::new(); - - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn_empty().id(); - let c = world.spawn_empty().id(); - let d = world.spawn_empty().id(); - - world.entity_mut(a).insert(Friend::new(b)); - world.entity_mut(c).insert(Friend::new(d)); - - world.flush(); - - assert_eq!(world.get::(a), Some(&Friend::new(b))); - assert_eq!(world.get::(b), Some(&Friend::new(a))); - assert_eq!(world.get::(c), Some(&Friend::new(d))); - assert_eq!(world.get::(d), Some(&Friend::new(c))); - - world.entity_mut(a).insert(Friend::new(c)); - world.entity_mut(b).insert(Friend::new(d)); - - world.flush(); - - assert_eq!(world.get::(a), Some(&Friend::new(c))); - assert_eq!(world.get::(b), Some(&Friend::new(d))); - assert_eq!(world.get::(c), Some(&Friend::new(a))); - assert_eq!(world.get::(d), Some(&Friend::new(b))); - } - - #[test] - fn looped_add_and_remove() { - let mut world = World::new(); - - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn_empty().id(); - - for _ in 0..10_000 { - world.entity_mut(a).insert(Friend::new(b)); - world.entity_mut(a).remove::(); - } - - world.flush(); - - assert_eq!(world.get::(a), None); - assert_eq!(world.get::(b), None); - } - - #[test] - fn invalid_chaining() { - let mut world = World::new(); - - world.register_component::(); - - let a = world.spawn_empty().id(); - let b = world.spawn_empty().id(); - let c = world.spawn_empty().id(); - let d = world.spawn_empty().id(); - let e = world.spawn_empty().id(); - let f = world.spawn_empty().id(); - - world.entity_mut(a).insert(Friend::new(b)); - world.entity_mut(b).insert(Friend::new(c)); - world.entity_mut(c).insert(Friend::new(d)); - world.entity_mut(d).insert(Friend::new(e)); - world.entity_mut(e).insert(Friend::new(f)); - world.entity_mut(f).insert(Friend::new(a)); - - world.flush(); - - assert_eq!(world.get::(a), Some(&Friend::new(b))); - assert_eq!(world.get::(b), Some(&Friend::new(a))); - assert_eq!(world.get::(c), Some(&Friend::new(d))); - assert_eq!(world.get::(d), Some(&Friend::new(c))); - assert_eq!(world.get::(e), Some(&Friend::new(f))); - assert_eq!(world.get::(f), Some(&Friend::new(e))); - - // The pairing is caused by the first member of the pair (e.g., a, c, e) replacing - // the relationship on the second member of the pair (e.g, b, d, f). - // When the replacement occurs, it checks if the second member had a valid relationship - // with it's old data (e.g., b -> c, d -> e, etc.) and if it did not, then no action is taken. - } - - #[test] - fn event_testing() { - let mut world = World::new(); - - world.register_component::(); - world.init_resource::>>(); - - let a = world.spawn_empty().id(); - let b = world.spawn(Friend::new(a)).id(); - - world.flush(); - - assert_eq!(world.get::(a), Some(&Friend::new(b))); - assert_eq!(world.get::(b), Some(&Friend::new(a))); - - assert_eq!( - world - .resource_mut::>>() - .drain() - .collect::>(), - vec![RelationshipEvent::::added(b, a)] - ); - - world.entity_mut(a).remove::(); - - world.flush(); - - assert_eq!(world.get::(a), None); - assert_eq!(world.get::(b), None); - - assert_eq!( - world - .resource_mut::>>() - .drain() - .collect::>(), - vec![RelationshipEvent::::removed(a, b)] - ); - } -} From 402b01070a3afe0adc467272d1151bab3494d8df Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Mon, 21 Oct 2024 08:11:26 +1100 Subject: [PATCH 25/25] Fix CI --- crates/bevy_hierarchy/src/one_to_many.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_hierarchy/src/one_to_many.rs b/crates/bevy_hierarchy/src/one_to_many.rs index 1f831e2e00733..20b5687217f57 100644 --- a/crates/bevy_hierarchy/src/one_to_many.rs +++ b/crates/bevy_hierarchy/src/one_to_many.rs @@ -271,7 +271,7 @@ impl OneToMany { mod tests { use bevy_ecs::{event::Events, world::World}; - use crate::{ManyToOne, RelationshipEvent}; + use crate::{many_to_one::ManyToOne, relationship::RelationshipEvent}; use super::OneToMany;