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}