Skip to main content

nightshade/
states.rs

1//! App-builder sugar over nightshade_ecs's state machine (the `state` feature). The
2//! machinery lives in [`nightshade_ecs::state`]; this wires it onto the [`App`] builder
3//! and the [`Stage::First`] transition step, and adapts `current_state` to the
4//! engine [`World`] so call sites name only the state type.
5//!
6//! ```ignore
7//! impl Plugin for MyGamePlugin {
8//!     fn build(&self, app: &mut App) {
9//!         app.insert_state(Screen::Title);
10//!         app.on_enter(Screen::Title, build_title_ui);
11//!         app.on_exit(Screen::Title, teardown_title_ui);
12//!         app.add_system(Stage::Update, while_in(Screen::Playing, tick));
13//!     }
14//! }
15//!
16//! fn tick(world: &mut World) {
17//!     if world.res::<crate::platform::input::resources::Input>().keyboard.just_pressed(KeyCode::Space) {
18//!         next_state(world, Screen::Title);
19//!     }
20//! }
21//! ```
22
23use crate::app::{App, Stage};
24use crate::ecs::world::World;
25
26/// The current value of state type `S`, adapting
27/// [`nightshade_ecs::state::current_state`] to the engine [`World`] so call sites name
28/// only `S`.
29pub fn current_state<S: Copy + PartialEq + Send + Sync + 'static>(world: &World) -> S {
30    nightshade_ecs::state::current_state::<S, World>(world)
31}
32
33impl App {
34    /// Inserts the [`State`](nightshade_ecs::state::State) and
35    /// [`NextState`](nightshade_ecs::state::NextState) resources for `S` at `initial`
36    /// and registers the transition-apply step onto [`Stage::First`], so a
37    /// transition requested during a frame lands at the start of the next and
38    /// no frame observes a mid-frame flip. Enter hooks for `initial` run on the
39    /// first frame. Panics if a state of type `S` was already inserted.
40    pub fn insert_state<S: Copy + PartialEq + Send + Sync + 'static>(
41        &mut self,
42        initial: S,
43    ) -> &mut Self {
44        nightshade_ecs::state::insert_state(&mut self.world, initial);
45        self.add_system(
46            Stage::First,
47            nightshade_ecs::state::apply_state_transition::<S, World>,
48        );
49        self
50    }
51
52    /// Runs `systems` once each time `S` enters `state`, after the transition
53    /// step, reading the emitted
54    /// [`StateTransition`](nightshade_ecs::state::StateTransition) through its own
55    /// cursor so each entry fires exactly once. Takes a single system or a
56    /// tuple. Requires [`insert_state`](App::insert_state) first.
57    pub fn on_enter<S: Copy + PartialEq + Send + Sync + 'static, Marker>(
58        &mut self,
59        state: S,
60        systems: impl nightshade_ecs::state::IntoGroupRunner<World, Marker>,
61    ) -> &mut Self {
62        self.add_system(
63            Stage::First,
64            nightshade_ecs::state::on_enter(state, systems),
65        );
66        self
67    }
68
69    /// Runs `systems` once each time `S` leaves `state`, after the transition
70    /// step. Takes a single system or a tuple. Requires
71    /// [`insert_state`](App::insert_state) first.
72    pub fn on_exit<S: Copy + PartialEq + Send + Sync + 'static, Marker>(
73        &mut self,
74        state: S,
75        systems: impl nightshade_ecs::state::IntoGroupRunner<World, Marker>,
76    ) -> &mut Self {
77        self.add_system(Stage::First, nightshade_ecs::state::on_exit(state, systems));
78        self
79    }
80}