oxygengine-script-web 0.12.5

Web scripting module for Oxygen Engine
Documentation
use crate::interface::WebScriptInterface;
use core::{
    ecs::World,
    state::{State, StateChange},
};
use js_sys::{Function, JsString, Reflect};
use wasm_bindgen::{JsCast, JsValue};

pub trait WebScriptStateScripted {
    fn on_enter(&mut self, _world: &mut World) {}
    fn on_exit(&mut self, _world: &mut World) {}
    fn on_process(&mut self, _world: &mut World) -> Option<String> {
        None
    }
}

pub struct WebScriptBootState {
    initial_state_name: String,
}

impl WebScriptBootState {
    pub fn new(initial_state_name: &str) -> Self {
        Self {
            initial_state_name: initial_state_name.to_owned(),
        }
    }
}

impl State for WebScriptBootState {
    fn on_process(&mut self, _: &mut World) -> StateChange {
        if WebScriptInterface::is_ready() {
            return if let Some(result) = WebScriptInterface::build_state(&self.initial_state_name) {
                StateChange::Swap(Box::new(WebScriptState::new(result)))
            } else if let Some(result) =
                WebScriptInterface::build_state_scripted(&self.initial_state_name)
            {
                StateChange::Swap(Box::new(WebScriptStateWrapped::new(result)))
            } else {
                StateChange::Pop
            };
        }
        StateChange::None
    }
}

pub(crate) struct WebScriptStateWrapped {
    inner: Box<dyn WebScriptStateScripted>,
}

impl WebScriptStateWrapped {
    pub fn new(inner: Box<dyn WebScriptStateScripted>) -> Self {
        Self { inner }
    }
}

impl State for WebScriptStateWrapped {
    fn on_enter(&mut self, world: &mut World) {
        WebScriptInterface::set_world(world);
        self.inner.on_enter(world);
    }

    fn on_process(&mut self, world: &mut World) -> StateChange {
        WebScriptInterface::set_world(world);
        WebScriptInterface::run_systems();
        WebScriptInterface::maintain_entities(world);

        if let Some(name) = self.inner.on_process(world) {
            if let Some(result) = WebScriptInterface::build_state(&name) {
                return StateChange::Swap(Box::new(WebScriptState::new(result)));
            } else if let Some(result) = WebScriptInterface::build_state_scripted(&name) {
                return StateChange::Swap(Box::new(WebScriptStateWrapped::new(result)));
            }
        }
        StateChange::None
    }

    fn on_exit(&mut self, world: &mut World) {
        self.inner.on_exit(world);
        WebScriptInterface::unset_world();
    }
}

pub(crate) struct WebScriptState {
    context: JsValue,
    on_enter: Option<Function>,
    on_process: Option<Function>,
    on_exit: Option<Function>,
}

impl WebScriptState {
    pub fn new(context: JsValue) -> Self {
        let on_enter = if let Ok(m) = Reflect::get(&context, &JsValue::from_str("onEnter")) {
            m.dyn_ref::<Function>().cloned()
        } else {
            None
        };
        let on_process = if let Ok(m) = Reflect::get(&context, &JsValue::from_str("onProcess")) {
            m.dyn_ref::<Function>().cloned()
        } else {
            None
        };
        let on_exit = if let Ok(m) = Reflect::get(&context, &JsValue::from_str("onExit")) {
            m.dyn_ref::<Function>().cloned()
        } else {
            None
        };
        Self {
            context,
            on_enter,
            on_process,
            on_exit,
        }
    }
}

impl State for WebScriptState {
    fn on_enter(&mut self, world: &mut World) {
        WebScriptInterface::set_world(world);

        if let Some(on_enter) = &self.on_enter {
            drop(on_enter.call0(&self.context));
        }
    }

    fn on_process(&mut self, world: &mut World) -> StateChange {
        WebScriptInterface::set_world(world);
        WebScriptInterface::run_systems();
        WebScriptInterface::maintain_entities(world);

        if let Some(on_process) = &self.on_process {
            if let Ok(result) = on_process.call0(&self.context) {
                if let Some(result) = result.dyn_ref::<JsString>() {
                    let name = String::from(result);
                    if let Some(result) = WebScriptInterface::build_state(&name) {
                        return StateChange::Swap(Box::new(WebScriptState::new(result)));
                    } else if let Some(result) = WebScriptInterface::build_state_scripted(&name) {
                        return StateChange::Swap(Box::new(WebScriptStateWrapped::new(result)));
                    }
                }
            }
        }
        StateChange::None
    }

    fn on_exit(&mut self, _: &mut World) {
        if let Some(on_exit) = &self.on_exit {
            drop(on_exit.call0(&self.context));
        }

        WebScriptInterface::unset_world();
    }
}