bracket_state_machine/
state.rs

1use bracket_lib::prelude::*;
2use std::time::Duration;
3
4pub type StateTransition<S, R> = Transition<S, R>;
5type UpdateReturn<S, R> = (StateTransition<S, R>, TransitionControl);
6
7pub trait State {
8    type State: ?Sized;
9    type StateResult;
10
11    #[must_use = "it may trigger a state change"]
12    fn update(
13        &mut self,
14        term: &mut BTerm,
15        state: &mut Self::State,
16        pop_result: &Option<Self::StateResult>,
17        dt: Duration,
18    ) -> UpdateReturn<Self::State, Self::StateResult>
19    where
20        Self::State: std::marker::Sized,
21        Self::StateResult: std::marker::Sized;
22
23    fn render(&self, term: &mut BTerm, state: &Self::State, active: bool);
24
25    fn clear(&self, _state: &Self::State, _term: &mut BTerm) {
26        BACKEND_INTERNAL
27            .lock()
28            .consoles
29            .iter_mut()
30            .for_each(|c| c.console.cls());
31    }
32
33    fn is_transparent(&self) -> bool {
34        true
35    }
36}
37
38/// Return value for `update` callback sent into [run] that controls the main event loop.
39pub enum RunControl {
40    // Quit the run loop.
41    Quit,
42    // Call `update` again next frame.
43    Update,
44    // Wait for an input event before the next update; this will likely draw the mode before
45    // waiting.
46    WaitForEvent,
47}
48
49pub enum Transition<S, R> {
50    Stay,
51    Pop(R),
52    Terminate,
53    Push(Box<dyn State<State = S, StateResult = R>>),
54    Switch(Box<dyn State<State = S, StateResult = R>>),
55}
56
57/// Desired behavior for the next update, to be returned from an `update` call.
58#[derive(Debug)]
59pub enum TransitionControl {
60    /// Run the next update immediately, without waiting for the next frame.
61    Immediate,
62    /// Wait a frame before the next update; this will likely draw the mode for a frame.
63    Update,
64    /// Wait for an input event before the next update; this will likely draw the mode before
65    /// waiting.
66    WaitForEvent,
67}
68
69pub struct StateMachine<S, R> {
70    state: S,
71    wait_for_event: bool,
72    pop_result: Option<R>,
73    active_mouse_pos: Point,
74    states: Vec<Box<dyn State<State = S, StateResult = R>>>,
75}
76
77impl<S, R> StateMachine<S, R> {
78    // TODO implement From<State>
79    /// creates a state machine with an initial state
80    pub fn new<T: State<State = S, StateResult = R> + 'static>(
81        system_state: S,
82        init_state: T,
83    ) -> Self {
84        StateMachine {
85            pop_result: None,
86            state: system_state,
87            wait_for_event: false,
88            active_mouse_pos: Point::zero(),
89            states: vec![Box::new(init_state)],
90        }
91    }
92
93    fn clear_consoles(&mut self, term: &mut BTerm) {
94        if let Some(top_state) = self.states.last_mut() {
95            top_state.clear(&self.state, term);
96        }
97    }
98
99    fn internal_tick(&mut self, ctx: &mut BTerm) -> RunControl {
100        while !self.states.is_empty() {
101            let (transition, transition_update) = {
102                let top_mode = self.states.last_mut().unwrap();
103                top_mode.update(
104                    ctx,
105                    &mut self.state,
106                    &self.pop_result,
107                    Duration::from_millis(ctx.frame_time_ms as u64),
108                )
109            };
110
111            self.pop_result = None;
112            match transition {
113                Transition::Stay => {}
114                Transition::Switch(state) => {
115                    self.states.pop();
116                    self.states.push(state);
117                }
118                Transition::Push(state) => {
119                    self.states.push(state);
120                }
121                Transition::Pop(state_result) => {
122                    self.pop_result = Some(state_result);
123                    self.states.pop();
124                }
125                Transition::Terminate => {
126                    self.states.clear();
127                }
128            }
129
130            // Draw modes in the stack from the bottom-up.
131            if !self.states.is_empty() && !matches!(transition_update, TransitionControl::Immediate)
132            {
133                let draw_from = self
134                    .states
135                    .iter()
136                    .rposition(|mode| !mode.is_transparent())
137                    .unwrap_or(0);
138
139                let top = self.states.len().saturating_sub(1);
140
141                self.clear_consoles(ctx);
142
143                // Draw non-top modes with `active` set to `false`.
144                for mode in self.states.iter_mut().skip(usize::max(draw_from, 1)) {
145                    mode.render(ctx, &self.state, false);
146                }
147
148                // Draw top mode with `active` set to `true`.
149                self.states[top].render(ctx, &self.state, true);
150
151                render_draw_buffer(ctx).expect("Render draw buffer error");
152            }
153
154            match transition_update {
155                TransitionControl::Immediate => (),
156                TransitionControl::Update => return RunControl::Update,
157                TransitionControl::WaitForEvent => return RunControl::WaitForEvent,
158            }
159        }
160
161        RunControl::Quit
162    }
163}
164
165impl<S: 'static, R: 'static> GameState for StateMachine<S, R> {
166    fn tick(&mut self, ctx: &mut BTerm) {
167        if ctx.quitting {
168            ctx.quit();
169        }
170
171        if self.wait_for_event {
172            let new_mouse = ctx.mouse_point();
173
174            // Handle Keys & Mouse Clicks
175            if ctx.key.is_some() || ctx.left_click {
176                self.wait_for_event = false;
177            }
178
179            // Handle Mouse Movement
180            if new_mouse != self.active_mouse_pos {
181                self.wait_for_event = false;
182                self.active_mouse_pos = new_mouse;
183            }
184        } else {
185            self.active_mouse_pos = ctx.mouse_point();
186
187            match self.internal_tick(ctx) {
188                RunControl::Update => {}
189                RunControl::Quit => ctx.quit(),
190                RunControl::WaitForEvent => self.wait_for_event = true,
191            }
192        }
193    }
194}