Skip to content

Provide access to the original target of entity-events in observers #19663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 48 additions & 18 deletions crates/bevy_ecs/src/observer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,21 @@ impl<'w, E, B: Bundle> On<'w, E, B> {
impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> {
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer.
///
/// Note that if event propagation is enabled, this may not be the same as the original target of the event,
/// which can be accessed via [`On::original_target`].
///
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
pub fn target(&self) -> Entity {
self.trigger.target.unwrap_or(Entity::PLACEHOLDER)
self.trigger.current_target.unwrap_or(Entity::PLACEHOLDER)
}

/// Returns the original [`Entity`] that the `event` was targeted at when it was first triggered.
///
/// If event propagation is not enabled, this will always return the same value as [`On::target`].
///
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
pub fn original_target(&self) -> Entity {
self.trigger.original_target.unwrap_or(Entity::PLACEHOLDER)
}

/// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities.
Expand Down Expand Up @@ -483,8 +495,15 @@ pub struct ObserverTrigger {
pub event_type: ComponentId,
/// The [`ComponentId`]s the trigger targeted.
components: SmallVec<[ComponentId; 2]>,
/// The entity the trigger targeted.
pub target: Option<Entity>,
/// The entity that the entity-event targeted, if any.
///
/// Note that if event propagation is enabled, this may not be the same as [`ObserverTrigger::original_target`].
pub current_target: Option<Entity>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Is there a reason to call this current_target even though On has target? I suppose it is clearer, just mildly inconsistent

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I decided for clarity over consistency here, but I can swap it.

/// The entity that the entity-event was originally targeted at, if any.
///
/// If event propagation is enabled, this will be the first entity that the event was targeted at,
/// even if the event was propagated to other entities.
pub original_target: Option<Entity>,
/// The location of the source code that triggered the observer.
pub caller: MaybeLocation,
}
Expand Down Expand Up @@ -573,7 +592,8 @@ impl Observers {
pub(crate) fn invoke<T>(
mut world: DeferredWorld,
event_type: ComponentId,
target: Option<Entity>,
current_target: Option<Entity>,
original_target: Option<Entity>,
components: impl Iterator<Item = ComponentId> + Clone,
data: &mut T,
propagate: &mut bool,
Expand Down Expand Up @@ -601,7 +621,8 @@ impl Observers {
observer,
event_type,
components: components.clone().collect(),
target,
current_target,
original_target,
caller,
},
data.into(),
Expand All @@ -612,7 +633,7 @@ impl Observers {
observers.map.iter().for_each(&mut trigger_observer);

// Trigger entity observers listening for this kind of trigger
if let Some(target_entity) = target {
if let Some(target_entity) = current_target {
if let Some(map) = observers.entity_observers.get(&target_entity) {
map.iter().for_each(&mut trigger_observer);
}
Expand All @@ -626,7 +647,7 @@ impl Observers {
.iter()
.for_each(&mut trigger_observer);

if let Some(target_entity) = target {
if let Some(target_entity) = current_target {
if let Some(map) = component_observers.entity_map.get(&target_entity) {
map.iter().for_each(&mut trigger_observer);
}
Expand Down Expand Up @@ -752,6 +773,7 @@ impl World {
world.trigger_observers_with_data::<_, ()>(
event_id,
None,
None,
core::iter::empty::<ComponentId>(),
event_data,
false,
Expand Down Expand Up @@ -863,6 +885,7 @@ impl World {
world.trigger_observers_with_data::<_, E::Traversal>(
event_id,
None,
None,
targets.components(),
event_data,
false,
Expand All @@ -876,6 +899,7 @@ impl World {
world.trigger_observers_with_data::<_, E::Traversal>(
event_id,
Some(target_entity),
Some(target_entity),
targets.components(),
event_data,
E::AUTO_PROPAGATE,
Expand Down Expand Up @@ -1543,21 +1567,27 @@ mod tests {
let mut world = World::new();
world.init_resource::<Order>();

let parent = world
.spawn_empty()
.observe(|_: On<EventPropagating>, mut res: ResMut<Order>| {
let parent = world.spawn_empty().id();
let child = world.spawn(ChildOf(parent)).id();

world.entity_mut(parent).observe(
move |trigger: On<EventPropagating>, mut res: ResMut<Order>| {
res.observed("parent");
})
.id();

let child = world
.spawn(ChildOf(parent))
.observe(|_: On<EventPropagating>, mut res: ResMut<Order>| {
assert_eq!(trigger.target(), parent);
assert_eq!(trigger.original_target(), child);
},
);

world.entity_mut(child).observe(
move |trigger: On<EventPropagating>, mut res: ResMut<Order>| {
res.observed("child");
})
.id();
assert_eq!(trigger.target(), child);
assert_eq!(trigger.original_target(), child);
},
);

// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// TODO: ideally this flush is not necessary, but right now observe() returns EntityWorldMut
// and therefore does not automatically flush.
world.flush();
world.trigger_targets(EventPropagating, child);
Expand Down
18 changes: 12 additions & 6 deletions crates/bevy_ecs/src/world/deferred_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,7 @@ impl<'w> DeferredWorld<'w> {
self.reborrow(),
event,
target,
target,
components,
&mut (),
&mut false,
Expand All @@ -762,7 +763,8 @@ impl<'w> DeferredWorld<'w> {
pub(crate) unsafe fn trigger_observers_with_data<E, T>(
&mut self,
event: ComponentId,
target: Option<Entity>,
current_target: Option<Entity>,
original_target: Option<Entity>,
components: impl Iterator<Item = ComponentId> + Clone,
data: &mut E,
mut propagate: bool,
Expand All @@ -773,32 +775,36 @@ impl<'w> DeferredWorld<'w> {
Observers::invoke::<_>(
self.reborrow(),
event,
target,
current_target,
original_target,
components.clone(),
data,
&mut propagate,
caller,
);
let Some(mut target) = target else { return };
let Some(mut current_target) = current_target else {
return;
};

loop {
if !propagate {
return;
}
if let Some(traverse_to) = self
.get_entity(target)
.get_entity(current_target)
.ok()
.and_then(|entity| entity.get_components::<T>())
.and_then(|item| T::traverse(item, data))
{
target = traverse_to;
current_target = traverse_to;
} else {
break;
}
Observers::invoke::<_>(
self.reborrow(),
event,
Some(target),
Some(current_target),
original_target,
components.clone(),
data,
&mut propagate,
Expand Down
35 changes: 4 additions & 31 deletions crates/bevy_picking/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ use crate::{
#[entity_event(traversal = PointerTraversal, auto_propagate)]
#[reflect(Component, Debug, Clone)]
pub struct Pointer<E: Debug + Clone + Reflect> {
/// The original target of this picking event, before bubbling
pub target: Entity,
/// The pointer that triggered this event
pub pointer_id: PointerId,
/// The location of the pointer during this event
Expand Down Expand Up @@ -126,9 +124,8 @@ impl<E: Debug + Clone + Reflect> core::ops::Deref for Pointer<E> {

impl<E: Debug + Clone + Reflect> Pointer<E> {
/// Construct a new `Pointer<E>` event.
pub fn new(id: PointerId, location: Location, target: Entity, event: E) -> Self {
pub fn new(id: PointerId, location: Location, event: E) -> Self {
Self {
target,
pointer_id: id,
pointer_location: location,
event,
Expand Down Expand Up @@ -497,12 +494,7 @@ pub fn pointer_events(
};

// Always send Out events
let out_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Out { hit: hit.clone() },
);
let out_event = Pointer::new(pointer_id, location.clone(), Out { hit: hit.clone() });
commands.trigger_targets(out_event.clone(), hovered_entity);
event_writers.out_events.write(out_event);

Expand All @@ -514,7 +506,6 @@ pub fn pointer_events(
let drag_leave_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
DragLeave {
button,
dragged: *drag_target,
Expand Down Expand Up @@ -556,7 +547,6 @@ pub fn pointer_events(
let drag_enter_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
DragEnter {
button,
dragged: *drag_target,
Expand All @@ -569,12 +559,7 @@ pub fn pointer_events(
}

// Always send Over events
let over_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Over { hit: hit.clone() },
);
let over_event = Pointer::new(pointer_id, location.clone(), Over { hit: hit.clone() });
commands.trigger_targets(over_event.clone(), hovered_entity);
event_writers.over_events.write(over_event);
}
Expand All @@ -600,7 +585,6 @@ pub fn pointer_events(
let pressed_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Press {
button,
hit: hit.clone(),
Expand Down Expand Up @@ -628,7 +612,6 @@ pub fn pointer_events(
let click_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Click {
button,
hit: hit.clone(),
Expand All @@ -642,7 +625,6 @@ pub fn pointer_events(
let released_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Release {
button,
hit: hit.clone(),
Expand All @@ -659,7 +641,6 @@ pub fn pointer_events(
let drag_drop_event = Pointer::new(
pointer_id,
location.clone(),
*dragged_over,
DragDrop {
button,
dropped: drag_target,
Expand All @@ -673,7 +654,6 @@ pub fn pointer_events(
let drag_end_event = Pointer::new(
pointer_id,
location.clone(),
drag_target,
DragEnd {
button,
distance: drag.latest_pos - drag.start_pos,
Expand All @@ -686,7 +666,6 @@ pub fn pointer_events(
let drag_leave_event = Pointer::new(
pointer_id,
location.clone(),
*dragged_over,
DragLeave {
button,
dragged: drag_target,
Expand Down Expand Up @@ -727,7 +706,6 @@ pub fn pointer_events(
let drag_start_event = Pointer::new(
pointer_id,
location.clone(),
*press_target,
DragStart {
button,
hit: hit.clone(),
Expand All @@ -746,7 +724,6 @@ pub fn pointer_events(
let drag_event = Pointer::new(
pointer_id,
location.clone(),
*drag_target,
Drag {
button,
distance: location.position - drag.start_pos,
Expand All @@ -769,7 +746,6 @@ pub fn pointer_events(
let drag_over_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
DragOver {
button,
dragged: *drag_target,
Expand All @@ -791,7 +767,6 @@ pub fn pointer_events(
let move_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Move {
hit: hit.clone(),
delta,
Expand All @@ -811,7 +786,6 @@ pub fn pointer_events(
let scroll_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Scroll {
unit,
x,
Expand All @@ -831,8 +805,7 @@ pub fn pointer_events(
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
{
let cancel_event =
Pointer::new(pointer_id, location.clone(), hovered_entity, Cancel { hit });
let cancel_event = Pointer::new(pointer_id, location.clone(), Cancel { hit });
commands.trigger_targets(cancel_event.clone(), hovered_entity);
event_writers.cancel_events.write(cancel_event);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/ecs/error_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ fn fallible_observer(
mut step: Local<f32>,
) -> Result {
let mut transform = world
.get_mut::<Transform>(trigger.target)
.get_mut::<Transform>(trigger.target())
.ok_or("No transform found.")?;

*step = if transform.translation.x > 3. {
Expand Down
1 change: 0 additions & 1 deletion examples/ui/directional_navigation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,6 @@ fn interact_with_focused_button(
if let Some(focused_entity) = input_focus.0 {
commands.trigger_targets(
Pointer::<Click> {
target: focused_entity,
// We're pretending that we're a mouse
pointer_id: PointerId::Mouse,
// This field isn't used, so we're just setting it to a placeholder value
Expand Down
Loading