Path: blob/main/crates/bevy_state/src/state_scoped_events.rs
6595 views
use alloc::vec::Vec;1use core::marker::PhantomData;23use bevy_app::{App, SubApp};4use bevy_ecs::{5event::{BufferedEvent, EventReader, Events},6resource::Resource,7system::Commands,8world::World,9};10use bevy_platform::collections::HashMap;1112use crate::state::{OnEnter, OnExit, StateTransitionEvent, States};1314fn clear_event_queue<E: BufferedEvent>(w: &mut World) {15if let Some(mut queue) = w.get_resource_mut::<Events<E>>() {16queue.clear();17}18}1920#[derive(Copy, Clone)]21enum TransitionType {22OnExit,23OnEnter,24}2526#[derive(Resource)]27struct StateScopedEvents<S: States> {28/// Keeps track of which events need to be reset when the state is exited.29on_exit: HashMap<S, Vec<fn(&mut World)>>,30/// Keeps track of which events need to be reset when the state is entered.31on_enter: HashMap<S, Vec<fn(&mut World)>>,32}3334impl<S: States> StateScopedEvents<S> {35fn add_event<E: BufferedEvent>(&mut self, state: S, transition_type: TransitionType) {36let map = match transition_type {37TransitionType::OnExit => &mut self.on_exit,38TransitionType::OnEnter => &mut self.on_enter,39};40map.entry(state).or_default().push(clear_event_queue::<E>);41}4243fn cleanup(&self, w: &mut World, state: S, transition_type: TransitionType) {44let map = match transition_type {45TransitionType::OnExit => &self.on_exit,46TransitionType::OnEnter => &self.on_enter,47};48let Some(fns) = map.get(&state) else {49return;50};51for callback in fns {52(*callback)(w);53}54}55}5657impl<S: States> Default for StateScopedEvents<S> {58fn default() -> Self {59Self {60on_exit: HashMap::default(),61on_enter: HashMap::default(),62}63}64}6566fn clear_events_on_exit<S: States>(67mut c: Commands,68mut transitions: EventReader<StateTransitionEvent<S>>,69) {70let Some(transition) = transitions.read().last() else {71return;72};73if transition.entered == transition.exited {74return;75}76let Some(exited) = transition.exited.clone() else {77return;78};7980c.queue(move |w: &mut World| {81w.resource_scope::<StateScopedEvents<S>, ()>(|w, events| {82events.cleanup(w, exited, TransitionType::OnExit);83});84});85}8687fn clear_events_on_enter<S: States>(88mut c: Commands,89mut transitions: EventReader<StateTransitionEvent<S>>,90) {91let Some(transition) = transitions.read().last() else {92return;93};94if transition.entered == transition.exited {95return;96}97let Some(entered) = transition.entered.clone() else {98return;99};100101c.queue(move |w: &mut World| {102w.resource_scope::<StateScopedEvents<S>, ()>(|w, events| {103events.cleanup(w, entered, TransitionType::OnEnter);104});105});106}107108fn clear_events_on_state_transition<E: BufferedEvent, S: States>(109app: &mut SubApp,110_p: PhantomData<E>,111state: S,112transition_type: TransitionType,113) {114if !app.world().contains_resource::<StateScopedEvents<S>>() {115app.init_resource::<StateScopedEvents<S>>();116}117app.world_mut()118.resource_mut::<StateScopedEvents<S>>()119.add_event::<E>(state.clone(), transition_type);120match transition_type {121TransitionType::OnExit => app.add_systems(OnExit(state), clear_events_on_exit::<S>),122TransitionType::OnEnter => app.add_systems(OnEnter(state), clear_events_on_enter::<S>),123};124}125126/// Extension trait for [`App`] adding methods for registering state scoped events.127pub trait StateScopedEventsAppExt {128/// Clears an [`BufferedEvent`] when exiting the specified `state`.129///130/// Note that event cleanup is ambiguously ordered relative to131/// [`DespawnOnExit`](crate::prelude::DespawnOnExit) entity cleanup,132/// and the [`OnExit`] schedule for the target state.133/// All of these (state scoped entities and events cleanup, and `OnExit`)134/// occur within schedule [`StateTransition`](crate::prelude::StateTransition)135/// and system set `StateTransitionSystems::ExitSchedules`.136fn clear_events_on_exit<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self;137138/// Clears an [`BufferedEvent`] when entering the specified `state`.139///140/// Note that event cleanup is ambiguously ordered relative to141/// [`DespawnOnEnter`](crate::prelude::DespawnOnEnter) entity cleanup,142/// and the [`OnEnter`] schedule for the target state.143/// All of these (state scoped entities and events cleanup, and `OnEnter`)144/// occur within schedule [`StateTransition`](crate::prelude::StateTransition)145/// and system set `StateTransitionSystems::EnterSchedules`.146fn clear_events_on_enter<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self;147}148149impl StateScopedEventsAppExt for App {150fn clear_events_on_exit<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {151clear_events_on_state_transition(152self.main_mut(),153PhantomData::<E>,154state,155TransitionType::OnExit,156);157self158}159160fn clear_events_on_enter<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {161clear_events_on_state_transition(162self.main_mut(),163PhantomData::<E>,164state,165TransitionType::OnEnter,166);167self168}169}170171impl StateScopedEventsAppExt for SubApp {172fn clear_events_on_exit<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {173clear_events_on_state_transition(self, PhantomData::<E>, state, TransitionType::OnExit);174self175}176177fn clear_events_on_enter<E: BufferedEvent>(&mut self, state: impl States) -> &mut Self {178clear_events_on_state_transition(self, PhantomData::<E>, state, TransitionType::OnEnter);179self180}181}182183#[cfg(test)]184mod tests {185use super::*;186use crate::app::StatesPlugin;187use bevy_ecs::event::BufferedEvent;188use bevy_state::prelude::*;189190#[derive(States, Default, Clone, Hash, Eq, PartialEq, Debug)]191enum TestState {192#[default]193A,194B,195}196197#[derive(BufferedEvent, Debug)]198struct StandardEvent;199200#[derive(BufferedEvent, Debug)]201struct StateScopedEvent;202203#[test]204fn clear_event_on_exit_state() {205let mut app = App::new();206app.add_plugins(StatesPlugin);207app.init_state::<TestState>();208209app.add_event::<StandardEvent>();210app.add_event::<StateScopedEvent>()211.clear_events_on_exit::<StateScopedEvent>(TestState::A);212213app.world_mut().write_event(StandardEvent).unwrap();214app.world_mut().write_event(StateScopedEvent).unwrap();215assert!(!app.world().resource::<Events<StandardEvent>>().is_empty());216assert!(!app217.world()218.resource::<Events<StateScopedEvent>>()219.is_empty());220221app.world_mut()222.resource_mut::<NextState<TestState>>()223.set(TestState::B);224app.update();225226assert!(!app.world().resource::<Events<StandardEvent>>().is_empty());227assert!(app228.world()229.resource::<Events<StateScopedEvent>>()230.is_empty());231}232233#[test]234fn clear_event_on_enter_state() {235let mut app = App::new();236app.add_plugins(StatesPlugin);237app.init_state::<TestState>();238239app.add_event::<StandardEvent>();240app.add_event::<StateScopedEvent>()241.clear_events_on_enter::<StateScopedEvent>(TestState::B);242243app.world_mut().write_event(StandardEvent).unwrap();244app.world_mut().write_event(StateScopedEvent).unwrap();245assert!(!app.world().resource::<Events<StandardEvent>>().is_empty());246assert!(!app247.world()248.resource::<Events<StateScopedEvent>>()249.is_empty());250251app.world_mut()252.resource_mut::<NextState<TestState>>()253.set(TestState::B);254app.update();255256assert!(!app.world().resource::<Events<StandardEvent>>().is_empty());257assert!(app258.world()259.resource::<Events<StateScopedEvent>>()260.is_empty());261}262}263264265