diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index ad5819479d049..c3f28e8ffae38 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -1,21 +1,12 @@ -use crate::{Children, HierarchyEvent, Parent}; +use crate::{Children, OnParentChange, 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. @@ -64,7 +55,7 @@ fn remove_from_children(world: &mut World, parent: Entity, child: Entity) { /// /// Does nothing if `child` was already a child of `parent`. /// -/// Sends [`HierarchyEvent`]'s. +/// Triggers [`OnParentChange`]. fn update_old_parent(world: &mut World, child: Entity, parent: Entity) { let previous = update_parent(world, child, parent); if let Some(previous_parent) = previous { @@ -72,18 +63,18 @@ fn update_old_parent(world: &mut World, child: Entity, parent: Entity) { if previous_parent == parent { return; } + remove_from_children(world, previous_parent, child); - push_events( - world, - [HierarchyEvent::ChildMoved { - child, - previous_parent, - new_parent: parent, - }], + world.trigger_targets( + OnParentChange::Moved { + previous: previous_parent, + new: parent, + }, + child, ); } else { - push_events(world, [HierarchyEvent::ChildAdded { child, parent }]); + world.trigger_targets(OnParentChange::Added(parent), child); } } @@ -94,9 +85,8 @@ fn update_old_parent(world: &mut World, child: Entity, parent: Entity) { /// /// Does nothing for a child if it was already a child of `parent`. /// -/// Sends [`HierarchyEvent`]'s. +/// Triggers [`OnParentChange`]. 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. @@ -105,37 +95,32 @@ fn update_old_parents(world: &mut World, parent: Entity, children: &[Entity]) { } remove_from_children(world, previous, child); - events.push(HierarchyEvent::ChildMoved { + world.trigger_targets( + OnParentChange::Moved { + previous, + new: parent, + }, child, - previous_parent: previous, - new_parent: parent, - }); + ); } else { - events.push(HierarchyEvent::ChildAdded { child, parent }); + world.trigger_targets(OnParentChange::Added(parent), child); } } - 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 { + let Some(parent_children) = world.get::(parent).map(|c| c.0.clone()) else { return; - } - for event in &events { - if let &HierarchyEvent::ChildRemoved { child, .. } = event { - world.entity_mut(child).remove::(); + }; + + for child in children { + if parent_children.contains(child) { + world.trigger_targets(OnParentChange::Removed(parent), *child); + 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::() { @@ -546,26 +531,16 @@ impl ChildBuild for WorldChildBuilder<'_> { 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, - }], - ); + self.world + .trigger_targets(OnParentChange::Added(self.parent), entity); 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, - }], - ); + self.world + .trigger_targets(OnParentChange::Added(self.parent), entity); self.world.entity_mut(entity) } @@ -682,7 +657,7 @@ impl BuildChildren for EntityWorldMut<'_> { 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 }]); + world.trigger_targets(OnParentChange::Removed(parent), child); }); } self @@ -706,15 +681,15 @@ mod tests { use super::{BuildChildren, ChildBuild}; use crate::{ components::{Children, Parent}, - HierarchyEvent::{self, ChildAdded, ChildMoved, ChildRemoved}, + OnParentChange, }; use smallvec::{smallvec, SmallVec}; use bevy_ecs::{ component::Component, entity::Entity, - event::Events, - system::Commands, + observer::Trigger, + system::{Commands, ResMut, Resource}, world::{CommandQueue, World}, }; @@ -728,6 +703,9 @@ mod tests { assert_eq!(world.get::(parent).map(|c| &**c), children); } + #[derive(Resource, Default)] + struct TriggeredEvents(Vec<(OnParentChange, Entity)>); + /// Assert the number of children in the parent's [`Children`] component if it exists. fn assert_num_children(world: &World, parent: Entity, num_children: usize) { assert_eq!( @@ -738,15 +716,16 @@ mod tests { /// Used to omit a number of events that are not relevant to a particular test. fn omit_events(world: &mut World, number: usize) { - let mut events_resource = world.resource_mut::>(); - let mut events: Vec<_> = events_resource.drain().collect(); - events_resource.extend(events.drain(number..)); + let mut events_resource = world.resource_mut::(); + let mut events: Vec<_> = events_resource.0.drain(0..).collect(); + events_resource.0.extend(events.drain(number..)); } - fn assert_events(world: &mut World, expected_events: &[HierarchyEvent]) { + fn assert_events(world: &mut World, expected_events: &[(OnParentChange, Entity)]) { let events: Vec<_> = world - .resource_mut::>() - .drain() + .resource_mut::() + .0 + .drain(0..) .collect(); assert_eq!(events, expected_events); } @@ -754,7 +733,16 @@ mod tests { #[test] fn add_child() { let world = &mut World::new(); - world.insert_resource(Events::::default()); + + world.init_resource::(); + + world.add_observer( + |trigger: Trigger, mut triggered_events: ResMut| { + triggered_events + .0 + .push((trigger.event().clone(), trigger.entity())); + }, + ); let [a, b, c, d] = core::array::from_fn(|_| world.spawn_empty().id()); @@ -762,25 +750,14 @@ 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, &[(OnParentChange::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, - &[ChildAdded { - child: c, - parent: a, - }], - ); + assert_events(world, &[(OnParentChange::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); @@ -789,7 +766,16 @@ mod tests { #[test] fn set_parent() { let world = &mut World::new(); - world.insert_resource(Events::::default()); + + world.init_resource::(); + + world.add_observer( + |trigger: Trigger, mut triggered_events: ResMut| { + triggered_events + .0 + .push((trigger.event().clone(), trigger.entity())); + }, + ); let [a, b, c] = core::array::from_fn(|_| world.spawn_empty().id()); @@ -797,13 +783,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, &[(OnParentChange::Added(b), a)]); world.entity_mut(a).set_parent(c); @@ -812,11 +792,13 @@ mod tests { assert_children(world, c, Some(&[a])); assert_events( world, - &[ChildMoved { - child: a, - previous_parent: b, - new_parent: c, - }], + &[( + OnParentChange::Moved { + previous: b, + new: c, + }, + a, + )], ); } @@ -840,7 +822,16 @@ mod tests { #[test] fn remove_parent() { let world = &mut World::new(); - world.insert_resource(Events::::default()); + + world.init_resource::(); + + world.add_observer( + |trigger: Trigger, mut triggered_events: ResMut| { + triggered_events + .0 + .push((trigger.event().clone(), trigger.entity())); + }, + ); let [a, b, c] = core::array::from_fn(|_| world.spawn_empty().id()); @@ -851,24 +842,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, &[(OnParentChange::Removed(a), b)]); 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, &[(OnParentChange::Removed(a), c)]); } #[allow(dead_code)] @@ -1289,7 +1268,6 @@ mod tests { #[test] fn with_child() { let world = &mut World::new(); - world.insert_resource(Events::::default()); let a = world.spawn_empty().id(); let b = (); diff --git a/crates/bevy_hierarchy/src/events.rs b/crates/bevy_hierarchy/src/events.rs index 5a667fef7789b..83769a66d28b1 100644 --- a/crates/bevy_hierarchy/src/events.rs +++ b/crates/bevy_hierarchy/src/events.rs @@ -2,33 +2,60 @@ use bevy_ecs::{event::Event, prelude::Entity}; #[cfg(feature = "reflect")] use bevy_reflect::Reflect; -/// An [`Event`] that is fired whenever there is a change in the world's hierarchy. +/// A [`Trigger`] emitted for entities whenever they are added as a child to an entity, +/// removed from their parent, or moved from one parent to another. /// -/// [`Event`]: bevy_ecs::event::Event -#[derive(Event, Debug, Clone, PartialEq, Eq)] +/// [`Trigger`]: bevy_ecs::observer::Trigger +/// +/// # Example +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # use bevy_hierarchy::{BuildChildren, OnParentChange}; +/// # +/// let mut world = World::new(); +/// +/// // Add an observer to listen for hierarchy changes. +/// world.add_observer(move |trigger: Trigger| { +/// let entity = trigger.entity(); +/// match trigger.event() { +/// OnParentChange::Added(parent) => { +/// println!("Entity {entity} was added as a child to {parent}"); +/// } +/// OnParentChange::Removed(parent) => { +/// println!("Entity {entity} was removed from its parent {parent}"); +/// } +/// OnParentChange::Moved { previous, new } => { +/// println!("Entity {entity} was moved from parent {previous} to {new}"); +/// } +/// } +/// }); +/// +/// let parent1 = world.spawn_empty().id(); +/// let parent2 = world.spawn_empty().id(); +/// let child = world.spawn_empty().id(); +/// +/// // Triggers `OnParentChange::Added`. +/// world.entity_mut(parent1).add_child(child); +/// +/// // Triggers `OnParentChange::Moved`. +/// world.entity_mut(parent2).add_child(child); +/// +/// // Triggers `OnParentChange::Removed`. +/// world.entity_mut(child).remove_parent(); +/// ``` +#[derive(Event, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "reflect", derive(Reflect), reflect(Debug, PartialEq))] -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, +pub enum OnParentChange { + /// Emitted whenever the entity is added as a child to a parent. + Added(Entity), + /// Emitted whenever the child entity is removed from its parent. + Removed(Entity), + /// Emitted whenever the child entity is moved to a new parent. + Moved { + /// The parent the child was removed from. + previous: Entity, + /// The parent the child was added to. + new: Entity, }, } diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index ced37bd154f64..2df0d9e01d51a 100644 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -46,7 +46,7 @@ //! //! [command and world]: BuildChildren //! [diagnostic plugin]: ValidParentCheckPlugin -//! [events]: HierarchyEvent +//! [events]: OnParentChange //! [hierarchical despawn extension methods]: DespawnRecursiveExt //! [plugin]: HierarchyPlugin //! [query extension methods]: HierarchyQueryExt @@ -98,8 +98,6 @@ pub struct HierarchyPlugin; #[cfg(feature = "bevy_app")] impl Plugin for HierarchyPlugin { fn build(&self, app: &mut App) { - app.register_type::() - .register_type::() - .add_event::(); + app.register_type::().register_type::(); } }