Skip to content

Add events that can be consumed, or can only be read once #20171

@Zeenobit

Description

@Zeenobit

What problem does this solve or what need does it fill?

I recently ran into a use case where I wanted an observer to "consume" an event because the underlying data is big and/or not cloneable. I can guarantee that this is the only observer that will ever listen to this event.

So I made a generic solution that I'm calling SingleEvent (or maybe UniqueEvent? ExclusiveEvent?).

What solution would you like?

I propose a new SingleEvent trait, along with SingleTrigger and add_single_observer:

trait SingleEvent: 'static + Send + Sync { }

A "single" observer takes SingleTrigger as its trigger type:

impl SingleEvent for Foo {}

fn my_single_observer(trigger: SingleTrigger<OnFoo>) {
    let mut data = trigger.event().consume().unwrap();
    // Data is mine now! :D
}

Single observers are registered via add_single_observer:

app.add_single_observer(my_single_observer)

This function should panic or raise errors if another observer is already registered for this event.

User invokes the event using trigger_single:

commands.trigger_single(Foo);

A SingleTrigger<T> is just a type alias:

type SingleTrigger<T> = Trigger<OnSingleEvent<T>>

And OnSingleEvent is just a Mutex:

#[derive(Event)]
pub struct OnSingleEvent<E: SingleEvent>(Mutex<Option<E>>);

I've released the full implementation of my proposed solution under my own public crate:
https://github.com/Zeenobit/moonshine_util/blob/main/src/event.rs

However, I believe it'd be better if this was part of Bevy proper:

  • We could enforce "single" observer registration more robustly
    • I'm using a dummy plugin as a "registry" in my solution, and the user could easily bypass this check.
  • We could maybe unify the API a bit to maybe use the same trigger() method to trigger single and regular events alike.
  • We could extend the feature to support single triggers on entities too (my solution only support world events).
  • It's a powerful tool for library authors since it allows them to "consume" user data from observers without relying on third party crates

What alternative(s) have you considered?

N/A

Additional context

See here for a usage of this pattern in moonshine-save:
https://github.com/Zeenobit/moonshine_save/blob/9f7f533086cae8bf6851ff8acfd142018b7f966e/src/save.rs#L206

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ECSEntities, components, systems, and eventsC-FeatureA new feature, making something new possibleS-Needs-Design-DocThis issue or PR is particularly complex, and needs an approved design doc before it can be mergedX-ControversialThere is active debate or serious implications around merging this PR

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions