oxygengine_script_web/
state.rs

1use crate::interface::WebScriptInterface;
2use core::{
3    ecs::World,
4    state::{State, StateChange},
5};
6use js_sys::{Function, JsString, Reflect};
7use wasm_bindgen::{JsCast, JsValue};
8
9pub trait WebScriptStateScripted {
10    fn on_enter(&mut self, _world: &mut World) {}
11    fn on_exit(&mut self, _world: &mut World) {}
12    fn on_process(&mut self, _world: &mut World) -> Option<String> {
13        None
14    }
15}
16
17pub struct WebScriptBootState {
18    initial_state_name: String,
19}
20
21impl WebScriptBootState {
22    pub fn new(initial_state_name: &str) -> Self {
23        Self {
24            initial_state_name: initial_state_name.to_owned(),
25        }
26    }
27}
28
29impl State for WebScriptBootState {
30    fn on_process(&mut self, _: &mut World) -> StateChange {
31        if WebScriptInterface::is_ready() {
32            return if let Some(result) = WebScriptInterface::build_state(&self.initial_state_name) {
33                StateChange::Swap(Box::new(WebScriptState::new(result)))
34            } else if let Some(result) =
35                WebScriptInterface::build_state_scripted(&self.initial_state_name)
36            {
37                StateChange::Swap(Box::new(WebScriptStateWrapped::new(result)))
38            } else {
39                StateChange::Pop
40            };
41        }
42        StateChange::None
43    }
44}
45
46pub(crate) struct WebScriptStateWrapped {
47    inner: Box<dyn WebScriptStateScripted>,
48}
49
50impl WebScriptStateWrapped {
51    pub fn new(inner: Box<dyn WebScriptStateScripted>) -> Self {
52        Self { inner }
53    }
54}
55
56impl State for WebScriptStateWrapped {
57    fn on_enter(&mut self, world: &mut World) {
58        WebScriptInterface::set_world(world);
59        self.inner.on_enter(world);
60    }
61
62    fn on_process(&mut self, world: &mut World) -> StateChange {
63        WebScriptInterface::set_world(world);
64        WebScriptInterface::run_systems();
65        WebScriptInterface::maintain_entities(world);
66
67        if let Some(name) = self.inner.on_process(world) {
68            if let Some(result) = WebScriptInterface::build_state(&name) {
69                return StateChange::Swap(Box::new(WebScriptState::new(result)));
70            } else if let Some(result) = WebScriptInterface::build_state_scripted(&name) {
71                return StateChange::Swap(Box::new(WebScriptStateWrapped::new(result)));
72            }
73        }
74        StateChange::None
75    }
76
77    fn on_exit(&mut self, world: &mut World) {
78        self.inner.on_exit(world);
79        WebScriptInterface::unset_world();
80    }
81}
82
83pub(crate) struct WebScriptState {
84    context: JsValue,
85    on_enter: Option<Function>,
86    on_process: Option<Function>,
87    on_exit: Option<Function>,
88}
89
90impl WebScriptState {
91    pub fn new(context: JsValue) -> Self {
92        let on_enter = if let Ok(m) = Reflect::get(&context, &JsValue::from_str("onEnter")) {
93            m.dyn_ref::<Function>().cloned()
94        } else {
95            None
96        };
97        let on_process = if let Ok(m) = Reflect::get(&context, &JsValue::from_str("onProcess")) {
98            m.dyn_ref::<Function>().cloned()
99        } else {
100            None
101        };
102        let on_exit = if let Ok(m) = Reflect::get(&context, &JsValue::from_str("onExit")) {
103            m.dyn_ref::<Function>().cloned()
104        } else {
105            None
106        };
107        Self {
108            context,
109            on_enter,
110            on_process,
111            on_exit,
112        }
113    }
114}
115
116impl State for WebScriptState {
117    fn on_enter(&mut self, world: &mut World) {
118        WebScriptInterface::set_world(world);
119
120        if let Some(on_enter) = &self.on_enter {
121            drop(on_enter.call0(&self.context));
122        }
123    }
124
125    fn on_process(&mut self, world: &mut World) -> StateChange {
126        WebScriptInterface::set_world(world);
127        WebScriptInterface::run_systems();
128        WebScriptInterface::maintain_entities(world);
129
130        if let Some(on_process) = &self.on_process {
131            if let Ok(result) = on_process.call0(&self.context) {
132                if let Some(result) = result.dyn_ref::<JsString>() {
133                    let name = String::from(result);
134                    if let Some(result) = WebScriptInterface::build_state(&name) {
135                        return StateChange::Swap(Box::new(WebScriptState::new(result)));
136                    } else if let Some(result) = WebScriptInterface::build_state_scripted(&name) {
137                        return StateChange::Swap(Box::new(WebScriptStateWrapped::new(result)));
138                    }
139                }
140            }
141        }
142        StateChange::None
143    }
144
145    fn on_exit(&mut self, _: &mut World) {
146        if let Some(on_exit) = &self.on_exit {
147            drop(on_exit.call0(&self.context));
148        }
149
150        WebScriptInterface::unset_world();
151    }
152}