bevy_wasm 0.10.1

Run WASM systems in Bevy
Documentation
use std::{
    collections::VecDeque,
    sync::{Arc, RwLock},
};

use anyhow::Result;
use bevy::{
    prelude::{Component, Resource},
    utils::{HashMap, Instant},
};
use js_sys::{
    Function, Reflect,
    WebAssembly::{self, Instance},
};
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};

use bevy_wasm_shared::version::Version;
use web_sys::console;

use crate::{mod_state::ModState, SharedResource};

use self::linker::build_linker;

mod linker;

#[derive(Resource)]
pub struct WasmRuntime {
    protocol_version: Version,
}

impl WasmRuntime {
    pub fn new(protocol_version: Version) -> Self {
        Self { protocol_version }
    }

    pub fn create_instance(&self, wasm_bytes: &[u8]) -> Result<WasmInstance> {
        let memory = Arc::new(RwLock::new(None));
        let mod_state = Arc::new(RwLock::new(ModState {
            startup_time: Instant::now(),
            app_ptr: 0,
            events_in: VecDeque::new(),
            events_out: Vec::new(),
            shared_resource_values: HashMap::new(),
        }));
        let imports = build_linker(self.protocol_version, mod_state.clone(), memory.clone());
        let promise = WebAssembly::instantiate_buffer(wasm_bytes, &imports);
        let instance = Arc::new(RwLock::new(None));
        let then = Closure::new({
            let instance = instance.clone();
            move |value| {
                let instance_value: WebAssembly::Instance =
                    Reflect::get(&value, &"instance".into())
                        .and_then(|x| x.dyn_into())
                        .unwrap();
                let exports = instance_value.exports();
                let memory_value: WebAssembly::Memory = Reflect::get(&exports, &"memory".into())
                    .and_then(|x| x.dyn_into())
                    .unwrap();
                let build_app: Function = Reflect::get(exports.as_ref(), &"build_app".into())
                    .and_then(|x| x.dyn_into())
                    .expect("build_app export wasn't a function");
                *instance.write().unwrap() = Some(instance_value);
                *memory.write().unwrap() = Some(memory_value);
                build_app.call0(&JsValue::undefined()).unwrap();
            }
        });
        let catch = Closure::new({
            move |value| {
                console::warn_1(&value);
            }
        });
        _ = promise.then(&then).catch(&catch);
        Ok(WasmInstance {
            instance,
            mod_state,
            _then: then,
            _catch: catch,
        })
    }
}

#[derive(Component)]
pub struct WasmInstance {
    instance: Arc<RwLock<Option<Instance>>>,
    mod_state: Arc<RwLock<ModState>>,
    _then: Closure<dyn FnMut(JsValue)>,
    _catch: Closure<dyn FnMut(JsValue)>,
}

unsafe impl Send for WasmInstance {}
unsafe impl Sync for WasmInstance {}

impl WasmInstance {
    pub fn tick(&mut self, events_in: &[Arc<[u8]>]) -> Result<Vec<Box<[u8]>>> {
        let Some(instance) = self.instance.read().unwrap().clone() else { return Ok(Vec::new()) };
        for event in events_in.iter() {
            self.mod_state
                .write()
                .unwrap()
                .events_in
                .push_back(event.clone());
        }

        let app_ptr = self.mod_state.read().unwrap().app_ptr;

        let exports = instance.exports();

        let update: Function = Reflect::get(exports.as_ref(), &"update".into())
            .and_then(|x| x.dyn_into())
            .expect("build_app export wasn't a function");
        match update.call1(&JsValue::undefined(), &JsValue::from_f64(app_ptr as f64)) {
            Ok(_) => {}
            Err(e) => console::error_1(&e),
        }

        let serialized_events_out = std::mem::take(&mut self.mod_state.write().unwrap().events_out);

        Ok(serialized_events_out)
    }

    pub fn update_resource_value<T: SharedResource>(&mut self, bytes: Arc<[u8]>) {
        self.mod_state
            .write()
            .unwrap()
            .shared_resource_values
            .insert(T::TYPE_UUID, bytes);
    }
}