How to produce a WASM interface with asynchronous access?

1.2k Views Asked by At

My objective is an interface that is useful in such a context:


import { init } from "my-wasm-package";

const run = () => {
  const tool = init();
  tool.run(); // starts running a computation in a loop, 
  // i.e. with `spawn_local`, mutating the `tool` object structure, 
  // but releasing the main thread at some intervals i.e. with `Delay(...).await`
  document.addEventListener('keyup', (e) => {
    tool.key_up(e.code); // it would send signals to the tool that 
  // would be handled in its run() main loop
  });
}

An example of it could be a game initialized in Rust wasm but controlled from JS, or any similar simulation i.e. a chip8 proccessor.

Is that reasonably achievable i.e. with worker thread? Without it?

1

There are 1 best solutions below

0
On

Found a way to do that without a worker thread. In my case, it did work well because the computation waited a considerable amount of time, yielding control back.

It's done by the main async run() method in loop like this:

pub async fn run(this: Arc<wasm_mutex::Mutex<CPU>>) {
        loop {
            Delay::new(Duration::new(1 / SPEED, 0)).await;
...

where Delay is from the wasm-timer crate implemented for wasm target and desktops both.

The CPU is being wrapped for usage from JS with some additional locking, where spawn_local is from wasm_bindgen_futures

#[wasm_bindgen]
pub struct WasmProgram {
    // #[wasm_bindgen(skip)] // skipped already, non-pub
    cpu: Arc<Mutex<CPU>>,
}

// TODO DRY macro locks?
#[wasm_bindgen]
impl WasmProgram {
    pub fn run(&self) {
        let clone = self.cpu.clone();
        spawn_local(async move {
            CPU::run(clone).await;
        })
    }
    pub fn stop(&mut self) {
        let clone = self.cpu.clone();
        spawn_local(async move {
            let mut guard = clone.lock().await;
            guard.stop();
        })
    }

    pub fn key_down(&mut self, k: usize) {
        let clone = self.cpu.clone();
        spawn_local(async move {
            let mut guard = clone.lock().await;
            guard.key_down(k);
        })
    }

    pub fn key_up(&mut self, k: usize) {
        let clone = self.cpu.clone();
        spawn_local(async move {
            let mut guard = clone.lock().await;
            guard.key_up(k);
        })
    }
}

It lets me use this interface from JS as described in the question.

// create new cpu at some point
cpu.run();
initKeyboardListeners(cpu); // where a bunch of document.addEventListener is passing to cpu.key_up / cpu.key_down