gamestate.rs 12.5 KB
Newer Older
1
2
use std::cell::RefCell;
use std::rc::Rc;
Rahix's avatar
Rahix committed
3

4
5
use crate::utils;
use wasm_bindgen::closure;
Rahix's avatar
Rahix committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

pub enum Transition {
    /// Schedule a call of [`State::update`] with `requestAnimationFrame`.
    Loop,
    /// Sleep and wait for any registered event-handler to fire.
    Sleep,
    /// Either `Loop` or `Sleep`, whatever happened last.
    Keep,
    /// Replace this state with a new one.
    Replace(Box<dyn State>),
    /// Push a new state on the stack.
    Push(Box<dyn State>),
    /// Pop this state from the stack and return to the last one.
    Pop,
}

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
impl std::fmt::Debug for Transition {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Transition::Loop => "Loop",
                Transition::Sleep => "Sleep",
                Transition::Keep => "Keep",
                Transition::Replace(_) => "Replace(..)",
                Transition::Push(_) => "Push(..)",
                Transition::Pop => "Pop",
            }
        )
    }
}

Rahix's avatar
Rahix committed
39
40
41
42
43
44
45
46
47
48
49
50
51
52
impl Transition {
    pub fn replace<S: State + 'static>(new: S) -> Transition {
        Transition::Replace(Box::new(new))
    }

    pub fn push<S: State + 'static>(new: S) -> Transition {
        Transition::Push(Box::new(new))
    }
}

pub trait State {
    fn init(&mut self, init: StateInitializer) -> Transition;
    fn deinit(&mut self) {}

53
    fn update(&mut self, _timestamp: f64) -> Transition {
Rahix's avatar
Rahix committed
54
55
56
        Transition::Keep
    }
    #[allow(unused_variables)]
57
    fn event(&mut self, event: Event) -> Transition {
Rahix's avatar
Rahix committed
58
59
60
61
        Transition::Keep
    }
}

62
63
#[derive(Debug, Clone)]
pub enum Event<'a> {
64
65
66
67
    MouseClick {
        target: &'a str,
        coords: nalgebra::Point2<f32>,
    },
68
69
70
71
72
73
74
75
76
77
78
    KeyDown(&'a str),
    KeyUp(&'a str),
}

struct EventHandlers {
    pub mouse_handlers:
        std::collections::HashMap<String, closure::Closure<dyn FnMut(wasm_bindgen::JsValue)>>,
    pub key_handlers: Option<(
        closure::Closure<dyn FnMut(wasm_bindgen::JsValue)>,
        closure::Closure<dyn FnMut(wasm_bindgen::JsValue)>,
    )>,
Rahix's avatar
Rahix committed
79
80
}

81
82
83
84
85
impl EventHandlers {
    pub fn new() -> EventHandlers {
        EventHandlers {
            mouse_handlers: std::collections::HashMap::new(),
            key_handlers: None,
Rahix's avatar
Rahix committed
86
87
88
        }
    }

89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    pub fn register_onclick<F: FnMut(&web_sys::MouseEvent) + 'static>(
        &mut self,
        id: &str,
        mut f: F,
    ) {
        use wasm_bindgen::JsCast;

        let f = closure::Closure::wrap(Box::new(move |event: wasm_bindgen::JsValue| {
            let event = event
                .dyn_ref::<web_sys::MouseEvent>()
                .expect("no mouse event?");

            f(event)
        }) as Box<dyn FnMut(wasm_bindgen::JsValue)>);

        if let Ok(el) = utils::get_element_by_id::<web_sys::SvgElement>(id) {
            el.set_onclick(Some(f.as_ref().unchecked_ref()));
        } else if let Ok(el) = utils::get_element_by_id::<web_sys::HtmlElement>(id) {
            el.set_onclick(Some(f.as_ref().unchecked_ref()));
        } else {
            panic!("Cannot find appropriate element {:?}", id);
        }
Rahix's avatar
Rahix committed
111

112
113
        self.mouse_handlers.insert(id.to_owned(), f);
    }
Rahix's avatar
Rahix committed
114

115
116
117
118
119
120
121
122
123
124
    pub fn register_keyevents<F: FnMut(bool, &web_sys::KeyboardEvent) + 'static>(&mut self, f: F) {
        use wasm_bindgen::JsCast;

        let f = Rc::new(RefCell::new(f));
        let f2 = f.clone();
        let fdown = closure::Closure::wrap(Box::new(move |event: wasm_bindgen::JsValue| {
            let event = event
                .dyn_ref::<web_sys::KeyboardEvent>()
                .expect("no keyboard event?");

125
126
127
128
129
            // Drop keyDown events which are repating
            if event.repeat() {
                return;
            }

130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
            f.borrow_mut()(true, event)
        }) as Box<dyn FnMut(wasm_bindgen::JsValue)>);
        let fup = closure::Closure::wrap(Box::new(move |event: wasm_bindgen::JsValue| {
            let event = event
                .dyn_ref::<web_sys::KeyboardEvent>()
                .expect("no keyboard event?");

            f2.borrow_mut()(false, event)
        }) as Box<dyn FnMut(wasm_bindgen::JsValue)>);

        let body = utils::document().body().unwrap();
        body.set_onkeydown(Some(fdown.as_ref().unchecked_ref()));
        body.set_onkeyup(Some(fup.as_ref().unchecked_ref()));

        self.key_handlers = Some((fdown, fup));
Rahix's avatar
Rahix committed
145
146
    }

147
    pub fn deregister_all(&mut self) {
Rahix's avatar
Rahix committed
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
        if let Some(_) = self.key_handlers {
            let body = utils::document().body().unwrap();
            body.set_onkeydown(None);
            body.set_onkeyup(None);
        }

        for id in self.mouse_handlers.keys() {
            if let Ok(el) = utils::get_element_by_id::<web_sys::SvgElement>(id) {
                el.set_onclick(None);
            } else if let Ok(el) = utils::get_element_by_id::<web_sys::HtmlElement>(id) {
                el.set_onclick(None);
            } else {
                panic!("Cannot find appropriate element {:?}", id);
            }
        }
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
    }
}

#[derive(Clone)]
pub struct StateMachineHandle(Rc<RefCell<StateMachine>>);

impl StateMachineHandle {
    pub fn new(state_machine: &Rc<RefCell<StateMachine>>) -> StateMachineHandle {
        StateMachineHandle(state_machine.clone())
    }

    pub fn do_transition(&self, t: Transition) {
        StateMachine::do_transition(&self.0, t)
    }

178
179
    pub fn do_event(&self, event: Event) {
        let t = StateMachine::active(&self.0).event(event);
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
        self.do_transition(t)
    }
}

pub struct StateInitializer<'a> {
    handle: StateMachineHandle,
    event_handlers: &'a mut EventHandlers,
}

impl<'a> StateInitializer<'a> {
    fn new(
        state_machine: &Rc<RefCell<StateMachine>>,
        event_handlers: &'a mut EventHandlers,
    ) -> StateInitializer<'a> {
        StateInitializer {
            handle: StateMachineHandle::new(state_machine),
            event_handlers,
        }
    }
Rahix's avatar
Rahix committed
199

200
201
202
203
204
205
206
207
    pub fn register_onclick(&mut self, id: &str) {
        let handle = self.handle.clone();
        let id2 = id.to_owned();
        let canvas = utils::get_element_by_id::<web_sys::HtmlCanvasElement>("game-canvas").unwrap();
        self.event_handlers.register_onclick(id, move |event| {
            let click = nalgebra::Point2::new(event.client_x() as f64, event.client_y() as f64);
            let canvas_click = utils::screen_to_contain_canvas(&canvas, click);
            let canvas_click = nalgebra::Point2::new(canvas_click.x as f32, canvas_click.y as f32);
208
209
210
211
            handle.do_event(Event::MouseClick {
                target: &id2,
                coords: canvas_click,
            });
212
213
214
215
216
217
218
219
        });
    }

    pub fn register_keyevents(&mut self) {
        let handle = self.handle.clone();
        self.event_handlers
            .register_keyevents(move |keydown, event| {
                if keydown {
220
                    handle.do_event(Event::KeyDown(&event.key()));
221
                } else {
222
                    handle.do_event(Event::KeyUp(&event.key()));
Rahix's avatar
Rahix committed
223
                }
224
            });
Rahix's avatar
Rahix committed
225
    }
226
227
228
229

    pub fn get_handle(&self) -> StateMachineHandle {
        self.handle.clone()
    }
Rahix's avatar
Rahix committed
230
231
}

232
/// "Container" for a stored state
Rahix's avatar
Rahix committed
233
234
struct StateStorage {
    pub state: Box<dyn State>,
235
236
    pub do_loop: Option<bool>,
    pub event_handlers: EventHandlers,
Rahix's avatar
Rahix committed
237
238
239
240
241
242
243
244
245
246
}

impl StateStorage {
    pub fn new<S: State + 'static>(state: S) -> StateStorage {
        StateStorage::with_boxed(Box::new(state))
    }

    pub fn with_boxed(state: Box<dyn State>) -> StateStorage {
        StateStorage {
            state,
247
248
            do_loop: None,
            event_handlers: EventHandlers::new(),
Rahix's avatar
Rahix committed
249
250
        }
    }
251
252
253
254
255
256
257
258
259

    // ----------

    pub fn init(&mut self, sm: &Rc<RefCell<StateMachine>>) -> Transition {
        self.state
            .init(StateInitializer::new(sm, &mut self.event_handlers))
    }

    pub fn deinit(&mut self) {
Rahix's avatar
Rahix committed
260
261
        self.state.deinit();
        self.event_handlers.deregister_all();
262
263
    }

264
265
    pub fn update(&mut self, timestamp: f64) -> Transition {
        self.state.update(timestamp)
266
267
    }

268
269
    fn event(&mut self, event: Event) -> Transition {
        self.state.event(event)
270
    }
Rahix's avatar
Rahix committed
271
272
273
274
}

pub struct StateMachine {
    states: Vec<StateStorage>,
275
    loop_handler: Option<closure::Closure<dyn FnMut(f64)>>,
Rahix's avatar
Rahix committed
276
    loop_scheduled: bool,
Rahix's avatar
Rahix committed
277
278
279
280
}

impl StateMachine {
    pub fn launch<S: State + 'static>(initial: S) {
281
        let this = Rc::new(RefCell::new(StateMachine {
Rahix's avatar
Rahix committed
282
            states: vec![StateStorage::new(initial)],
283
            loop_handler: None,
Rahix's avatar
Rahix committed
284
            loop_scheduled: false,
Rahix's avatar
Rahix committed
285
286
        }));

287
288
289
        // Sadly Rc::new_cyclic() isn't stable yet ...
        let loop_handler = {
            let this = this.clone();
290
            closure::Closure::wrap(Box::new(move |timestamp| {
Rahix's avatar
Rahix committed
291
292
293
294
295
296
297
298
299
300
                {
                    let mut sm = this.borrow_mut();
                    if !sm.loop_scheduled {
                        // Running without being scheduled??
                        crate::console_warn!("Loop handler running without being scheduled!");
                        return;
                    }
                    sm.loop_scheduled = false;
                }

301
302
303
304
305
306
307
                // First, check if the event is still wanted by the current state
                let t = {
                    let mut active = StateMachine::active(&this);
                    if active.do_loop != Some(true) {
                        // If not, just pretend nothing ever happened.
                        return;
                    }
Rahix's avatar
Rahix committed
308

309
                    active.update(timestamp)
310
                };
Rahix's avatar
Rahix committed
311

312
                StateMachine::do_transition(&this, t);
313
            }) as Box<dyn FnMut(f64)>)
314
315
        };
        this.borrow_mut().loop_handler = Some(loop_handler);
Rahix's avatar
Rahix committed
316

317
318
        let t = StateMachine::active(&this).init(&this);
        StateMachine::do_transition(&this, t);
Rahix's avatar
Rahix committed
319
320
    }

321
322
    fn active(this: &Rc<RefCell<StateMachine>>) -> std::cell::RefMut<StateStorage> {
        std::cell::RefMut::map(this.borrow_mut(), |sm| sm.states.last_mut().unwrap())
Rahix's avatar
Rahix committed
323
324
    }

325
326
    pub fn do_event(this: &Rc<RefCell<StateMachine>>, event: Event) {
        let t = StateMachine::active(this).event(event);
327
        StateMachine::do_transition(this, t);
Rahix's avatar
Rahix committed
328
329
    }

330
331
332
333
334
335
336
337
338
339
340
341
342
343
    pub fn do_transition(this: &Rc<RefCell<StateMachine>>, t: Transition) {
        // Iteratively perform all transitions until there is no more left to do and hopefully
        // events/loop frames are scheduled.
        let mut next = Some(t);
        while let Some(t) = next {
            next = match t {
                Transition::Keep => {
                    if StateMachine::active(this)
                        .do_loop
                        .expect("no previous `Loop` or `Sleep` transition")
                    {
                        Some(Transition::Loop)
                    } else {
                        Some(Transition::Sleep)
Rahix's avatar
Rahix committed
344
                    }
Rahix's avatar
Rahix committed
345
                }
346
347
348
                Transition::Loop => {
                    use wasm_bindgen::JsCast;
                    StateMachine::active(this).do_loop = Some(true);
Rahix's avatar
Rahix committed
349
350
351
352
353
354
355
356
357
358
359

                    // Only schedule if a loop was not already scheduled.  Otherwise we'll end up
                    // with a lot of requestAnimationFrame() loops running in parallel.
                    if !this.borrow().loop_scheduled {
                        let mut sm = this.borrow_mut();
                        let loop_handler = sm.loop_handler.as_ref().unwrap();
                        utils::window()
                            .request_animation_frame(loop_handler.as_ref().unchecked_ref())
                            .unwrap();
                        sm.loop_scheduled = true;
                    }
360
                    None
Rahix's avatar
Rahix committed
361
                }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
                Transition::Sleep => {
                    StateMachine::active(this).do_loop = Some(false);
                    // We hope that at least some event handler/active promise is present which will
                    // wake us up again in the future. Otherwise, we'll get stuck from here ...
                    None
                }
                Transition::Replace(new) => {
                    let mut active = StateMachine::active(this);
                    active.deinit();
                    *active = StateStorage::with_boxed(new);
                    Some(active.init(this))
                }
                Transition::Push(new) => {
                    StateMachine::active(this).deinit();
                    this.borrow_mut().states.push(StateStorage::with_boxed(new));
                    Some(StateMachine::active(this).init(this))
                }
                Transition::Pop => {
                    StateMachine::active(this).deinit();
                    this.borrow_mut().states.pop().unwrap();
                    Some(StateMachine::active(this).init(this))
                }
            };
Rahix's avatar
Rahix committed
385
386
387
        }
    }
}