bevy_event_extras 0.1.0

Event utilities for the Bevy game engine
Documentation
use bevy::{ecs::schedule::ScheduleLabel, prelude::*};

/// Extension trait for [`App`] that adds ergonomic helpers for observer-based event propagation.
///
/// - [`trigger_on`](AppExt::trigger_on)
/// - [`add_propagator`](AppExt::add_propagator)
/// - [`add_multicondition_propagator`](AppExt::add_multicondition_propagator)
///
/// # Example
///
/// ```rust
/// use bevy::prelude::*;
/// use bevy_event_extras::*;
///
/// #[derive(Event)] struct AppStarted;
/// #[derive(Event)] struct AssetsReady;
///
/// App::new()
///     .trigger_on(PreStartup, || AppStarted)
///     .add_propagator(|_: On<AppStarted>| AssetsReady)
/// ```
pub trait AppExt {
    /// Triggers an event on the given schedule.
    ///
    /// ```rust
    /// #[derive(Event)]
    /// struct InitGame;
    ///
    /// app.trigger_on(PreStartup, || InitGame);
    /// ```
    fn trigger_on<'a, O: Event<Trigger<'a>: Default>>(
        &mut self,
        schedule: impl ScheduleLabel,
        event: impl Fn() -> O + 'static + Send + Sync,
    ) -> &mut Self;

    /// Triggers a new event after reading a previous event.
    ///
    /// ```rust
    /// #[derive(Event)]
    /// struct Condition;
    ///
    /// #[derive(Event)]
    /// struct DoThing;
    ///
    /// app.add_propagator(|_: On<Condition>| DoThing)
    /// ```
    fn add_propagator<'a, T: Event, O: Event<Trigger<'a>: Default>>(
        &mut self,
        event: impl Fn(On<T>) -> O + 'static + Send + Sync,
    ) -> &mut Self;

    /// Triggers a new event after all events defined in the [`Conditions`] have fired.
    ///
    /// ```rust
    /// #[derive(Event)] struct Condition1;
    /// #[derive(Event)] struct Condition2;
    /// #[derive(Event)] struct DoThing;
    ///
    /// app.add_multicondition_propagator(|conditions| {
    ///     conditions
    ///         .add::<Condition1>()
    ///         .add::<Condition2>()
    ///         .then_call(DoThing)
    /// })
    /// ```
    fn add_multicondition_propagator(
        &mut self,
        builder: impl Fn(Conditions) -> PropagatorResult,
    ) -> &mut Self;
}

/// Tracks which conditions have been met for a [`add_multicondition_propagator`](AppExt::add_multicondition_propagator) entry.
#[derive(Component, Deref, DerefMut)]
struct PropagatorTodo(Vec<bool>);

/// Builder for multi-condition event propagation. See [`AppExt::add_multicondition_propagator`].
pub struct Conditions<'a> {
    todo: PropagatorTodo,
    todo_ent: Entity,
    app: &'a mut App,
}

/// Opaque result type returned by [`Conditions::then_call`].
pub struct PropagatorResult {
    _marker: (),
}

impl<'a> Conditions<'a> {
    fn new(app: &'a mut App) -> Self {
        Self {
            todo_ent: app.world_mut().spawn_empty().id(),
            app,
            todo: PropagatorTodo(vec![]),
        }
    }

    /// Registers event `T` as a required condition.
    pub fn add<T: Event>(mut self) -> Self {
        let index = self.todo.0.len();
        let entity = self.todo_ent.clone();
        self.app
            .add_observer(move |_: On<T>, mut todo_q: Query<&mut PropagatorTodo>| {
                if let Ok(mut todo) = todo_q.get_mut(entity) {
                    todo.0[index] = true;
                }
            });

        self.todo.push(false);
        self
    }

    /// Specifies the event to trigger when all conditions are met. Resets flags after each trigger.
    pub fn then_call<'b, E: Event<Trigger<'b>: Default> + Clone>(
        self,
        event: E,
    ) -> PropagatorResult {
        let entity = self.todo_ent;
        let mut todo = Some(self.todo);

        self.app
            .add_systems(Startup, move |mut commands: Commands| {
                if let Some(t) = todo.take() {
                    commands.entity(entity).insert(t);
                }
            })
            .add_systems(
                PreUpdate,
                move |mut todo_q: Query<&mut PropagatorTodo>, mut commands: Commands| {
                    if let Ok(mut todo) = todo_q.get_mut(entity) {
                        if todo.iter().all(|done| *done) {
                            todo.fill_with(|| false);
                            commands.trigger(event.clone());
                        }
                    }
                },
            );

        PropagatorResult { _marker: () }
    }
}

impl AppExt for App {
    fn trigger_on<'a, O: Event<Trigger<'a>: Default>>(
        &mut self,
        schedule: impl ScheduleLabel,
        event: impl Fn() -> O + 'static + Send + Sync,
    ) -> &mut Self {
        self.add_systems(schedule, move |mut commands: Commands| {
            commands.trigger(event())
        });

        self
    }

    fn add_propagator<'a, T: Event, O: Event<Trigger<'a>: Default>>(
        &mut self,
        event: impl Fn(On<T>) -> O + 'static + Send + Sync,
    ) -> &mut Self {
        self.add_observer(move |t: On<T>, mut commands: Commands| commands.trigger(event(t)));

        self
    }

    fn add_multicondition_propagator(
        &mut self,
        builder: impl Fn(Conditions) -> PropagatorResult,
    ) -> &mut Self {
        builder(Conditions::new(self));
        self
    }
}