Skip to content

feat: allow for Trigger to provide the Entity on which an event w… #18710

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

Closed
wants to merge 1 commit into from

Conversation

emfax
Copy link

@emfax emfax commented Apr 4, 2025

…as observed.

Trigger in an observer system has provided the target method which gives the entity on which an event was triggered. This may be different to the entity that is being observed. This change introduces a current_target method on Trigger, which returns the id of the entity being observed.

In some cases, code that uses the result of Trigger::target may now actually need Trigger::target.

Objective

  • Describe the objective or issue this PR addresses.
  • If you're fixing a specific issue, say "Fixes #X".

Solution

  • Describe the solution used to achieve the objective above.

Testing

  • Did you test these changes? If so, how?
  • Are there any parts that need more testing?
  • How can other people (reviewers) test your changes? Is there anything specific they need to know?
  • If relevant, what platforms did you test these changes on, and are there any important ones you can't test?

Showcase

This section is optional. If this PR does not include a visual change or does not add a new feature, you can delete this section.

  • Help others understand the result of this PR by showcasing your awesome work!
  • If this PR adds a new feature or public API, consider adding a brief pseudo-code snippet of it in action
  • If this PR includes a visual change, consider adding a screenshot, GIF, or video
    • If you want, you could even include a before/after comparison!
  • If the Migration Guide adequately covers the changes, you can delete this section

While a showcase should aim to be brief and digestible, you can use a toggleable section to save space on longer showcases:

Click to view showcase
println!("My super cool code.");

…as observed.

`Trigger` in an observer system has provided the `target` method which gives the
entity on which an event was triggered. This may be different to the entity that is
being observed. This change introduces a `current_target` method on `Trigger`,
which returns the id of the entity being observed.

In some cases, code that uses the result of `Trigger::target` may now actually need
`Trigger::target`.
Copy link
Contributor

github-actions bot commented Apr 4, 2025

Welcome, new contributor!

Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨

@alice-i-cecile alice-i-cecile added this to the 0.17 milestone Apr 4, 2025
@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Apr 4, 2025
@hukasu
Copy link
Contributor

hukasu commented Apr 4, 2025

How is this different fromTrigger::observer?

@emfax
Copy link
Author

emfax commented Apr 5, 2025

Trigger::observer returns the Entity of the observer. Even if you call observe on an EntityCommands, the observer is spawned as a separate entity. So Trigger::observer returns the Entity of the observer entity.

Trigger::current_target returns the Entity of the entity being observed. For example, calling EntityCommands::observe will create an observer that watches that particular entity (even though the observer itself is spawned as a separate entity). When the observer is triggered, current_target returns the Entity of the entity that is being watched.

Trigger::current_target is also different to Trigger::target since events can bubble up the hierarchy. The target might be an entity deeper in the hierarchy.

@hukasu
Copy link
Contributor

hukasu commented Apr 6, 2025

so when bubbling you would have access to the current entity, and the entity that started the bubbling?

@emfax
Copy link
Author

emfax commented Apr 14, 2025

Yes. That’s right

@SteveAlexander
Copy link

I've been using Relationships, event bubbling, and triggers / observers. I found the current Trigger::target and Trigger::observer to be confusing -- is the target the entity that the event was sent with, or the entity that is being observed? Turns out sometimes I want to know both the originally targeted entity and the related entity with the observer that the event was bubbled to.

So, this change is helpful to me.

May I suggest some different naming? I find "current_target" and "target" a bit confusing.

My suggestion is as follows:

Trigger::target = the entity that was triggered
Trigger::observer = the observer system that is currently processing the event
Trigger::observed = the entity that Trigger::observer is observing

With no event bubbling, Trigger::target == Trigger::observed.
With event bubbling, for a particular event, Trigger::target stays the same, and Trigger::observed changes for each observer the event gets bubbled to.

Copy link
Contributor

@chescock chescock left a comment

Choose a reason for hiding this comment

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

If I'm reading this right, then the value returned by target() today will be returned by current_target() after this change, and the target() method will change from returning the current target to the original target. Is that right?

If that's the case, then I think you need a migration guide, since it's a change to the behavior of the existing target() method. (And you may also need to update some target != Entity::PLACEHOLDER checks to use current_target.)

/// Returns the [`Entity`] that is currently being observed. It may be [`Entity::PLACEHOLDER`]
/// if the observer is a global observer.
///
/// To get the entity that was targeted by the event use [`Trigger::target`].
Copy link
Contributor

Choose a reason for hiding this comment

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

Given how confusing this has been, it might help to explicitly mention event propagation here. Maybe something along the lines of

    /// When [propagation](Trigger::propagate) is used,
    /// an observer may be invoked multiple times for a single event.
    /// [`Trigger::target`] will be the same for each invocation,
    /// while [`Trigger::current_target`] will be different.

@emfax
Copy link
Author

emfax commented Apr 22, 2025

Today, target returns the entity from which the event originated, so the result of target doesn’t actually change.

@chescock
Copy link
Contributor

Today, target returns the entity from which the event originated, so the result of target doesn’t actually change.

... okay, I must be reading this wrong somehow. Let me try to walk through it slowly.

Before the change, fn trigger_observers_with_data() had mut target, which was different on every call to Observers::invoke because of the target = traverse_to; assignment. After the change, it has target, which is the same on every call, and mut current_target, which is different every time. Is that right?

So within fn trigger_observers_with_data(), current_target is equal to the old target and changes every time, while target is a new variable holding the initial target. Is that right?

That calls Observers::invoke, which constructs an ObserverTrigger. The variable names all still match, so current_target is equal to the old target and target is a new variable. The two get(&target) calls were changed to get(&current_target) to preserve the meaning. Is that right?

Where am I going wrong? It sure looks like fn current_target() returns the value that used to be returned by fn target(), and fn target() is a new value with the initial target.

@SteveAlexander
Copy link

SteveAlexander commented Apr 23, 2025

Before the change, fn trigger_observers_with_data() had mut target, which was different on every call to Observers::invoke because of the target = traverse_to; assignment. After the change, it has target, which is the same on every call, and mut current_target, which is different every time. Is that right?

I think this is correct.

Said another way: currently, the value returned by fn target() is different as the event gets bubbled to different observers.

Where am I going wrong? It sure looks like fn current_target() returns the value that used to be returned by fn target(), and fn target() is a new value with the initial target.

Yes. My game uses fn target() to get the entity that's being observed, which may be different than the entity that was originally sent the event. This is due to event bubbling.

emfax wrote:

Today, target returns the entity from which the event originated, so the result of target doesn’t actually change.

I do not think this is correct. I am relying on "target" changing in my game code today. If it helps, I can post an example showing that it does in fact change, as event bubbling proceeds.

@emfax
Copy link
Author

emfax commented Apr 25, 2025

@SteveAlexander You're correct about what target() returns.

I was so sure that I was correct, I've been trying to figure this out for an hour. I was using the target field which derefs from the Pointer event I was testing with. Dammit.

So yes, the result of target() would change. Where should the migration guide go?

@SteveAlexander
Copy link

@emfax thanks. I can certainly recognise the situation of spending an hour chasing a mirage!

What do you think about the naming of target and current_target? Personally, I don't find it intuitive which one should be considered "current". Should it be the one that is receiving the event? Or the one that is the original target of the event?

I could make a case to myself that either would be the "current" one, and so I find it hard to remember which one I want. I think perhaps the confusion you spoke to in your comment above also suggests that the naming isn't as clear as it could be.

If the events were named differently, I think I'd find it easier to remember. For example, original_target and target -- "original" seems to me less ambiguous than "current". Or maybe the other names I suggested in my comment above.

@SteveAlexander
Copy link

I checked what javascript does, as that's an existing example of an event bubbling API. And it's consistent with the name and meanings for "target" and "current target" in this PR.

event.target – is the “target” element that initiated the event, it doesn’t change through the bubbling process.
event.currentTarget (=this) – the current element that handles the event (the one that has the handler on it)

(from https://javascript.info/bubbling-and-capturing)

@hukasu
Copy link
Contributor

hukasu commented Apr 25, 2025

#[test]
fn current_target() {
    struct Bubbling;

    impl Event for Bubbling {
        const AUTO_PROPAGATE: bool = true;
        type Traversal = &'static ChildOf;
    }

    #[derive(Debug, Default, PartialEq, Eq, Resource)]
    struct BubblingStorage {
        observer: Vec<Entity>,
        target: Vec<Entity>,
    }

    fn bubble(trigger: Trigger<Bubbling>, mut storage: ResMut<BubblingStorage>) {
        storage.observer.push(trigger.observer());
        storage.target.push(trigger.target());
    }

    let mut world = World::new();

    world.init_resource::<BubblingStorage>();

    let root = world.spawn_empty().id();
    let a = world.spawn(ChildOf(root)).id();
    let b = world.spawn(ChildOf(a)).id();
    let c = world.spawn(ChildOf(b)).id();
    let d = world.spawn(ChildOf(c)).id();

    let observer = world
        .spawn(
            Observer::new(bubble)
                .with_entity(root)
                .with_entity(a)
                .with_entity(b)
                .with_entity(c)
                .with_entity(d),
        )
        .id();

    world.trigger_targets(Bubbling, d);

    let expected = BubblingStorage {
        observer: vec![observer, observer, observer, observer, observer],
        target: vec![d, c, b, a, root],
    };

    assert_eq!(&expected, world.get_resource::<BubblingStorage>().unwrap());
}

this passes with the current implementation
on your branch add a current_target to the BubblingStorage, add to the bubble observer system, and adjust the expected to what you are trying

@chescock chescock added the M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide label Apr 25, 2025
Copy link
Contributor

It looks like your PR is a breaking change, but you didn't provide a migration guide.

Please review the instructions for writing migration guides, then expand or revise the content in the migration guides directory to reflect your changes.

@chescock
Copy link
Contributor

So yes, the result of target() would change. Where should the migration guide go?

I think if I add the "needs migration guide" label, then a bot will add a comment with instructions... yup, there is goes!

(If you wind up changing the names so that it no longer needs a migration guide, leave a comment and someone with triage access can remove the label.)

@emfax
Copy link
Author

emfax commented Apr 26, 2025

@SteveAlexander

I checked what javascript does, as that's an existing example of an event bubbling API. And it's consistent with the name and meanings for "target" and "current target" in this PR.

Yeah, I decided to use similar names to web here.

@alice-i-cecile
Copy link
Member

@emfax, I quite like this work. Could you take a crack at reviewing #19609, and then rebase this on top of that?

I think that we should go with a current_target / original_target split inside of ObserverTrigger, but keep the nice entity name as the method for On to get the current_target.

@alice-i-cecile alice-i-cecile removed the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Jun 15, 2025
@alice-i-cecile alice-i-cecile added the S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged label Jun 15, 2025
@alice-i-cecile
Copy link
Member

I've opened a PR that accomplishes the same thing plus a bit, so I'm going to close this out and finish up the work there.

@emfax
Copy link
Author

emfax commented Jun 15, 2025

@alice-i-cecile I’ve just seen this flurry of activity here.

Sorry I missed it. I would’ve got that stuff done.

@alice-i-cecile alice-i-cecile moved this from Observer overhaul to Observers - Completed in Alice's Work Planning Jun 15, 2025
github-merge-queue bot pushed a commit that referenced this pull request Jun 15, 2025
…19663)

# Objective

Getting access to the original target of an entity-event is really
helpful when working with bubbled / propagated events.

`bevy_picking` special-cases this, but users have requested this for all
sorts of bubbled events.

The existing naming convention was also very confusing. Fixes
#17112, but also see #18982.

## Solution

1. Rename `ObserverTrigger::target` -> `current_target`.
1. Store `original_target: Option<Entity>` in `ObserverTrigger`.
1. Wire it up so this field gets set correctly.
1. Remove the `target` field on the `Pointer` events from
`bevy_picking`.

Closes #18710, which attempted
the same thing. Thanks @emfax!

## Testing

I've modified an existing test to check that the entities returned
during event bubbling / propagation are correct.

## Notes to reviewers

It's a little weird / sad that you can no longer access this infromation
via the buffered events for `Pointer`. That said, you already couldn't
access any bubbled target. We should probably remove the `BufferedEvent`
form of `Pointer` to reduce confusion and overhead, but I didn't want to
do so here.

Observer events can be trivially converted into buffered events (write
an observer with an EventWriter), and I suspect that that is the better
migration if you want the controllable timing or performance
characteristics of buffered events for your specific use case.

## Future work

It would be nice to not store this data at all (and not expose any
methods) if propagation was disabled. That involves more trait
shuffling, and I don't think we should do it here for reviewability.

---------

Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged
Projects
Status: Observers - Completed
Development

Successfully merging this pull request may close these issues.

5 participants