oxygengine_script_web/
interface.rs

1use crate::{
2    component::WebScriptComponent,
3    scriptable::{
4        scriptable_js_to_value, scriptable_value_merge, scriptable_value_to_js, Scriptable,
5        ScriptableValue,
6    },
7    state::WebScriptStateScripted,
8    web_api::EntityId,
9};
10use core::ecs::{Builder, Component, Entity, EntityBuilder, Resource, World, WorldExt};
11use js_sys::{Function, JsString, Object, Reflect};
12use std::{
13    collections::{HashMap, HashSet},
14    sync::Mutex,
15};
16use wasm_bindgen::{JsCast, JsValue};
17
18lazy_static! {
19    static ref INTERFACE: Mutex<WebScriptInterface> = Mutex::new(WebScriptInterface::default());
20}
21
22pub trait ComponentModify<R> {
23    fn modify_component(&mut self, source: R);
24}
25
26impl<T, R> ComponentModify<R> for T
27where
28    T: Component + From<R>,
29{
30    fn modify_component(&mut self, source: R) {
31        *self = source.into();
32    }
33}
34
35struct ComponentBridge {
36    add_to_entity: Box<dyn FnMut(EntityBuilder, JsValue) -> EntityBuilder>,
37    read_data: Box<dyn FnMut(&World, Entity) -> JsValue>,
38    write_data: Box<dyn FnMut(&mut World, Entity, JsValue)>,
39}
40
41impl ComponentBridge {
42    fn on_add_to_entity<'a>(
43        &mut self,
44        data: JsValue,
45        builder: EntityBuilder<'a>,
46    ) -> EntityBuilder<'a> {
47        (self.add_to_entity)(builder, data)
48    }
49
50    fn on_read_data(&mut self, world: &World, entity: Entity) -> JsValue {
51        (self.read_data)(world, entity)
52    }
53
54    fn on_write_data(&mut self, world: &mut World, entity: Entity, value: JsValue) {
55        (self.write_data)(world, entity, value)
56    }
57}
58
59pub trait ResourceModify<R> {
60    fn modify_resource(&mut self, source: R);
61}
62
63pub trait ResourceAccess {
64    fn access_resource(&mut self, value: ScriptableValue) -> ScriptableValue;
65}
66
67struct ResourceBridge {
68    read_data: Box<dyn FnMut(&World) -> JsValue>,
69    write_data: Box<dyn FnMut(&mut World, JsValue)>,
70    access_data: Box<dyn FnMut(&World, JsValue) -> JsValue>,
71}
72
73impl ResourceBridge {
74    fn on_read_data(&mut self, world: &World) -> JsValue {
75        (self.read_data)(world)
76    }
77
78    fn on_write_data(&mut self, world: &mut World, value: JsValue) {
79        (self.write_data)(world, value)
80    }
81
82    fn on_access_data(&mut self, world: &World, value: JsValue) -> JsValue {
83        (self.access_data)(world, value)
84    }
85}
86
87pub struct WebScriptInterface {
88    ready: bool,
89    // TODO: check if this pointer can be pinned.
90    world_ptr: Option<*mut World>,
91    resources: HashMap<String, JsValue>,
92    resources_bridge: HashMap<String, ResourceBridge>,
93    component_factory: HashMap<String, Function>,
94    scriptable_components: HashMap<String, Box<dyn Scriptable>>,
95    components_bridge: HashMap<String, ComponentBridge>,
96    state_factory: HashMap<String, Function>,
97    scriptable_state_factory: HashMap<String, Box<dyn FnMut() -> Box<dyn WebScriptStateScripted>>>,
98    systems: HashMap<String, (JsValue, Function)>,
99    systems_cache: Option<Vec<(JsValue, Function)>>,
100    index_generator: u64,
101    generation: u32,
102    entities_to_create: Vec<(JsValue, EntityId)>,
103    entities_to_destroy: HashSet<EntityId>,
104    entities_map: HashMap<EntityId, Entity>,
105    entities_cache: Vec<Entity>,
106}
107
108unsafe impl Send for WebScriptInterface {}
109unsafe impl Sync for WebScriptInterface {}
110
111impl Default for WebScriptInterface {
112    fn default() -> Self {
113        Self {
114            ready: false,
115            world_ptr: None,
116            resources: HashMap::new(),
117            resources_bridge: HashMap::new(),
118            component_factory: HashMap::new(),
119            scriptable_components: HashMap::new(),
120            components_bridge: HashMap::new(),
121            state_factory: HashMap::new(),
122            scriptable_state_factory: HashMap::new(),
123            systems: HashMap::new(),
124            systems_cache: None,
125            index_generator: 0,
126            generation: 1,
127            entities_to_create: vec![],
128            entities_to_destroy: HashSet::new(),
129            entities_map: HashMap::new(),
130            entities_cache: vec![],
131        }
132    }
133}
134
135impl WebScriptInterface {
136    pub fn is_ready() -> bool {
137        if let Ok(interface) = INTERFACE.lock() {
138            interface.ready
139        } else {
140            false
141        }
142    }
143
144    pub fn is_invalid() -> bool {
145        if let Ok(interface) = INTERFACE.lock() {
146            interface.world_ptr.is_none()
147        } else {
148            true
149        }
150    }
151
152    pub fn register_scriptable_resource<S>(&mut self, name: &str, resource: S)
153    where
154        S: 'static + Scriptable,
155    {
156        if !self.ready {
157            if let Ok(resource) = resource.to_js() {
158                self.resources.insert(name.to_owned(), resource);
159            }
160        }
161    }
162
163    pub fn register_resource_bridge<'a, S, M>(&mut self, name: &str)
164    where
165        S: Resource + Send + Sync + ResourceModify<M> + ResourceAccess,
166        M: Scriptable + From<&'a S>,
167    {
168        self.resources_bridge.insert(
169            name.to_owned(),
170            ResourceBridge {
171                read_data: Box::new(|world| {
172                    let r: &S = &world.read_resource::<S>();
173                    // TODO: this is very hacky - it extends reference lifetime for that time that
174                    // resource is converted into proxy object.
175                    // CHANGE IT IN THE FUTURE.
176                    let data = M::from(unsafe { std::mem::transmute(r) });
177                    if let Ok(data) = data.to_js() {
178                        data
179                    } else {
180                        JsValue::UNDEFINED
181                    }
182                }),
183                write_data: Box::new(|world, value| {
184                    if let Ok(data) = M::from_js(value) {
185                        let r: &mut S = &mut world.write_resource::<S>();
186                        r.modify_resource(data);
187                    }
188                }),
189                access_data: Box::new(|world, value| {
190                    if let Ok(data) = scriptable_js_to_value(value) {
191                        let r: &mut S = &mut world.write_resource::<S>();
192                        let v = r.access_resource(data);
193                        if let Ok(v) = scriptable_value_to_js(&v) {
194                            return v;
195                        }
196                    }
197                    JsValue::UNDEFINED
198                }),
199            },
200        );
201    }
202
203    pub fn register_scriptable_component<S>(&mut self, name: &str, component: S)
204    where
205        S: 'static + Scriptable,
206    {
207        if !self.ready {
208            self.scriptable_components
209                .insert(name.to_owned(), Box::new(component));
210        }
211    }
212
213    pub fn register_component_bridge<S, M>(&mut self, name: &str, template: S)
214    where
215        S: Scriptable + Component + Clone + Send + Sync + ComponentModify<M>,
216        S::Storage: Default,
217        M: Scriptable + From<S>,
218    {
219        self.components_bridge.insert(
220            name.to_owned(),
221            ComponentBridge {
222                add_to_entity: Box::new(move |builder, data| {
223                    let template_medium: M = template.clone().into();
224                    if let Ok(template_scriptable) = template_medium.to_scriptable() {
225                        if let Ok(data_scriptable) = scriptable_js_to_value(data) {
226                            let merged_scriptable =
227                                scriptable_value_merge(&template_scriptable, &data_scriptable);
228                            if let Ok(data) = M::from_scriptable(&merged_scriptable) {
229                                let mut template = template.clone();
230                                template.modify_component(data);
231                                return builder.with(template);
232                            }
233                        }
234                    }
235                    builder
236                }),
237                read_data: Box::new(|world, entity| {
238                    if let Some(data) = world.read_storage::<S>().get(entity) {
239                        let data: M = data.clone().into();
240                        if let Ok(data) = data.to_js() {
241                            return data;
242                        }
243                    }
244                    JsValue::UNDEFINED
245                }),
246                write_data: Box::new(|world, entity, value| {
247                    if let Ok(data) = M::from_js(value) {
248                        if let Some(component) = world.write_storage::<S>().get_mut(entity) {
249                            component.modify_component(data);
250                        }
251                    }
252                }),
253            },
254        );
255    }
256
257    pub fn register_scriptable_state_factory<S>(&mut self, name: &str, factory: S)
258    where
259        S: 'static + FnMut() -> Box<dyn WebScriptStateScripted>,
260    {
261        if !self.ready {
262            self.scriptable_state_factory
263                .insert(name.to_owned(), Box::new(factory));
264        }
265    }
266
267    pub fn read_scriptable_resource<T>(name: &str) -> Option<T>
268    where
269        T: Scriptable,
270    {
271        if let Some(resource) = Self::get_resource(name) {
272            if let Ok(resource) = T::from_js(resource) {
273                return Some(resource);
274            }
275        }
276        None
277    }
278
279    pub fn read_js_resource(name: &str) -> Option<ScriptableValue> {
280        if let Some(resource) = Self::get_resource(name) {
281            if let Ok(resource) = scriptable_js_to_value(resource) {
282                return Some(resource);
283            }
284        }
285        None
286    }
287
288    pub fn write_scriptable_resource<T>(name: &str, value: &T)
289    where
290        T: Scriptable,
291    {
292        if let Ok(resource) = value.to_js() {
293            Self::set_resource(name, resource);
294        }
295    }
296
297    pub fn write_js_resource(name: &str, value: &ScriptableValue) {
298        if let Ok(resource) = scriptable_value_to_js(value) {
299            Self::set_resource(name, resource);
300        }
301    }
302
303    pub(crate) fn register_resource(name: &str, resource: JsValue) {
304        if let Ok(mut interface) = INTERFACE.lock() {
305            if !interface.ready {
306                interface.resources.insert(name.to_owned(), resource);
307            }
308        }
309    }
310
311    pub(crate) fn register_component_factory(name: &str, factory: Function) {
312        if let Ok(mut interface) = INTERFACE.lock() {
313            if !interface.ready {
314                interface.component_factory.insert(name.to_owned(), factory);
315            }
316        }
317    }
318
319    pub(crate) fn register_system(name: &str, system: JsValue) {
320        if let Ok(mut interface) = INTERFACE.lock() {
321            if !interface.ready {
322                if let Ok(m) = Reflect::get(&system, &JsValue::from_str("onRun")) {
323                    if let Some(m) = m.dyn_ref::<Function>() {
324                        interface
325                            .systems
326                            .insert(name.to_owned(), (system, m.clone()));
327                    }
328                }
329            }
330        }
331    }
332
333    pub(crate) fn register_state_factory(name: &str, factory: Function) {
334        if let Ok(mut interface) = INTERFACE.lock() {
335            if !interface.ready {
336                interface.state_factory.insert(name.to_owned(), factory);
337            }
338        }
339    }
340
341    pub(crate) fn with<F, R>(mut f: F) -> R
342    where
343        F: FnMut(&mut Self) -> R,
344        R: Default,
345    {
346        if let Ok(mut interface) = INTERFACE.lock() {
347            f(&mut interface)
348        } else {
349            R::default()
350        }
351    }
352
353    pub(crate) fn start() {
354        if let Ok(mut interface) = INTERFACE.lock() {
355            interface.ready = true;
356            interface.systems_cache = Some(interface.systems.values().cloned().collect::<Vec<_>>());
357        }
358    }
359
360    pub(crate) fn set_world(world: &mut World) {
361        if let Ok(mut interface) = INTERFACE.lock() {
362            interface.world_ptr = Some(world as *mut World);
363        }
364    }
365
366    pub(crate) fn unset_world() {
367        if let Ok(mut interface) = INTERFACE.lock() {
368            interface.world_ptr = None;
369        }
370    }
371
372    pub(crate) fn world() -> Option<*mut World> {
373        if let Ok(interface) = INTERFACE.lock() {
374            interface.world_ptr
375        } else {
376            None
377        }
378    }
379
380    pub(crate) fn get_entity(index: usize) -> Option<Entity> {
381        if let Ok(interface) = INTERFACE.lock() {
382            interface.entities_cache.get(index).copied()
383        } else {
384            None
385        }
386    }
387
388    pub(crate) fn get_resource(name: &str) -> Option<JsValue> {
389        if let Ok(interface) = INTERFACE.lock() {
390            if let Some(resource) = interface.resources.get(name) {
391                return Some(resource.clone());
392            }
393        }
394        None
395    }
396
397    pub(crate) fn set_resource(name: &str, value: JsValue) -> bool {
398        if let Ok(mut interface) = INTERFACE.lock() {
399            if interface.ready && interface.resources.contains_key(name) {
400                interface.resources.insert(name.to_owned(), value);
401                return true;
402            }
403        }
404        false
405    }
406
407    pub(crate) fn read_resource_bridge(name: &str) -> Option<JsValue> {
408        if let Ok(mut interface) = INTERFACE.lock() {
409            if !interface.ready || interface.world_ptr.is_none() {
410                return None;
411            }
412            let world = interface.world_ptr.unwrap();
413            if let Some(bridge) = interface.resources_bridge.get_mut(name) {
414                return Some(bridge.on_read_data(unsafe { world.as_ref().unwrap() }));
415            }
416        }
417        None
418    }
419
420    pub(crate) fn write_resource_bridge(name: &str, value: JsValue) {
421        if let Ok(mut interface) = INTERFACE.lock() {
422            if !interface.ready || interface.world_ptr.is_none() {
423                return;
424            }
425            let world = interface.world_ptr.unwrap();
426            if let Some(bridge) = interface.resources_bridge.get_mut(name) {
427                bridge.on_write_data(unsafe { world.as_mut().unwrap() }, value);
428            }
429        }
430    }
431
432    pub(crate) fn access_resource_bridge(name: &str, value: JsValue) -> JsValue {
433        if let Ok(mut interface) = INTERFACE.lock() {
434            if !interface.ready || interface.world_ptr.is_none() {
435                return JsValue::UNDEFINED;
436            }
437            let world = interface.world_ptr.unwrap();
438            if let Some(bridge) = interface.resources_bridge.get_mut(name) {
439                return bridge.on_access_data(unsafe { world.as_mut().unwrap() }, value);
440            }
441        }
442        JsValue::UNDEFINED
443    }
444
445    pub(crate) fn read_component_bridge(name: &str, entity: Entity) -> Option<JsValue> {
446        if let Ok(mut interface) = INTERFACE.lock() {
447            interface.world_ptr?;
448            let world = interface.world_ptr.unwrap();
449            if let Some(bridge) = interface.components_bridge.get_mut(name) {
450                return Some(bridge.on_read_data(unsafe { world.as_ref().unwrap() }, entity));
451            }
452        }
453        None
454    }
455
456    pub(crate) fn write_component_bridge(name: &str, entity: Entity, value: JsValue) {
457        if let Ok(mut interface) = INTERFACE.lock() {
458            if interface.world_ptr.is_none() {
459                return;
460            }
461            let world = interface.world_ptr.unwrap();
462            if let Some(bridge) = interface.components_bridge.get_mut(name) {
463                bridge.on_write_data(unsafe { world.as_mut().unwrap() }, entity, value);
464            }
465        }
466    }
467
468    pub(crate) fn build_state(name: &str) -> Option<JsValue> {
469        if let Ok(interface) = INTERFACE.lock() {
470            if let Some(factory) = interface.state_factory.get(name) {
471                if let Ok(result) = factory.call0(&JsValue::UNDEFINED) {
472                    return Some(result);
473                }
474            }
475        }
476        None
477    }
478
479    pub(crate) fn build_state_scripted(name: &str) -> Option<Box<dyn WebScriptStateScripted>> {
480        if let Ok(mut interface) = INTERFACE.lock() {
481            if let Some(factory) = interface.scriptable_state_factory.get_mut(name) {
482                return Some(factory());
483            }
484        }
485        None
486    }
487
488    pub(crate) fn create_entity(data: JsValue) -> EntityId {
489        if let Ok(mut interface) = INTERFACE.lock() {
490            if interface.index_generator == std::u64::MAX {
491                interface.index_generator = 0;
492                interface.generation += 1;
493            }
494            let index = interface.index_generator;
495            interface.index_generator += 1;
496            let id = EntityId::new(index, interface.generation);
497            interface.entities_to_create.push((data, id));
498            id
499        } else {
500            EntityId::default()
501        }
502    }
503
504    pub(crate) fn destroy_entity(id: EntityId) {
505        if let Ok(mut interface) = INTERFACE.lock() {
506            interface.entities_to_destroy.insert(id);
507        }
508    }
509
510    pub(crate) fn run_systems() {
511        let meta = if let Ok(mut interface) = INTERFACE.lock() {
512            std::mem::replace(&mut interface.systems_cache, None)
513        } else {
514            return;
515        };
516        if let Some(meta) = &meta {
517            for (context, on_run) in meta {
518                drop(on_run.call0(&context));
519            }
520        }
521        if let Ok(mut interface) = INTERFACE.lock() {
522            interface.systems_cache = meta;
523        }
524    }
525
526    pub(crate) fn maintain_entities(world: &mut World) {
527        if let Ok(mut interface) = INTERFACE.lock() {
528            let entities_to_destroy =
529                std::mem::replace(&mut interface.entities_to_destroy, HashSet::new());
530            for id in entities_to_destroy {
531                if let Some(entity) = interface.entities_map.remove(&id) {
532                    interface.entities_cache.retain(|e| *e != entity);
533                    drop(world.delete_entity(entity));
534                }
535            }
536
537            let entities_to_create = std::mem::replace(&mut interface.entities_to_create, vec![]);
538            for (data, id) in entities_to_create {
539                interface.build_entity(world, id, data);
540            }
541        }
542    }
543
544    fn build_entity(&mut self, world: &mut World, id: EntityId, data: JsValue) {
545        let mut builder = world.create_entity();
546        let mut components = HashMap::new();
547        if !data.is_null() && !data.is_undefined() {
548            if let Some(object) = Object::try_from(&data) {
549                let keys = Object::keys(&object)
550                    .iter()
551                    .map(|key| key.dyn_ref::<JsString>().map(String::from))
552                    .collect::<Vec<_>>();
553                let values = Object::values(&object).iter().collect::<Vec<_>>();
554                for (key, value) in keys.into_iter().zip(values.into_iter()) {
555                    if let Some(key) = key {
556                        if let Some(factory) = self.component_factory.get(&key) {
557                            if let Ok(v) = factory.call0(&JsValue::UNDEFINED) {
558                                if let Some(d) = Object::try_from(&v) {
559                                    let v = if let Some(o) = Object::try_from(&value) {
560                                        Object::assign(d, o).into()
561                                    } else {
562                                        v
563                                    };
564                                    components.insert(key, Some(v));
565                                }
566                            }
567                        } else if let Some(scriptable) = self.scriptable_components.get(&key) {
568                            if let Ok(v) = scriptable.to_js() {
569                                if let Some(d) = Object::try_from(&v) {
570                                    let v = if let Some(o) = Object::try_from(&value) {
571                                        Object::assign(d, o).into()
572                                    } else {
573                                        v
574                                    };
575                                    components.insert(key, Some(v));
576                                }
577                            }
578                        } else if let Some(bridge) = self.components_bridge.get_mut(&key) {
579                            builder = bridge.on_add_to_entity(value, builder);
580                            components.insert(key, None);
581                        }
582                    }
583                }
584            }
585        }
586        builder = builder.with(WebScriptComponent::new(id, components));
587        let entity = builder.build();
588        self.entities_map.insert(id, entity);
589        self.entities_cache.push(entity);
590    }
591}