Posted on

One of the main rust ecosystem is the webassembly technology. wasm-bindgen and wasm-pack eases a lot the development process and includes a good set of libraries to work with different concepts (webgl, websys, websockets, web workers for example)... Here we can check a easy example about how to implement a web worker, it is incredible easy:

In the Rust side, we have this:

#[wasm_bindgen(start)]
pub fn init() -> Result<(), JsValue> {
    wasm_logger::init(wasm_logger::Config::default());
    std::panic::set_hook(Box::new(console_error_panic_hook::hook));
    Ok(())
}

#[wasm_bindgen]
pub fn run() -> Result<(), JsValue> {
    log::info!("Starting main function");
    let worker = web_sys::Worker::new("./worker.js").unwrap();
    let onmessage = get_on_msg_callback();
    // my callback from the worker
    worker.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
    //send message to worker
    worker
        .post_message(&JsValue::from(1.0))
        .expect("failed to post");
    std::mem::forget(onmessage);
    Ok(())
}

We define two functions, the first one with #[wasm_bindgen(start)] marks the function as the initialization function that it is called in the loading step. run is tagged with #[wasm_bindgen] that it exposes the function outside the rust code. If you check run, the set_onmessage accepts a closure function as a callback from Javascript. Furthemore, from post_message, we can send messages to the Web Worker in the Javascript side.

From the javascript Web Worker we can trigger message in Rust. Here the Rust code:

#[wasm_bindgen]
pub struct MyWorker {}

#[wasm_bindgen]
impl MyWorker {
    pub fn new() -> MyWorker {
        MyWorker {}
    }

    pub fn hello_world(&mut self, name: String) -> String {
        wasm_logger::init(wasm_logger::Config::default());
        log::info!("Hello world my friend {:?}", name);
        return "Hello world response".to_string();
    }
}

///// Create a closure to act on the message returned by the worker
fn get_on_msg_callback() -> Closure<dyn FnMut(MessageEvent)> {
    Closure::new(move |event: MessageEvent| {
        log::info!("Received response: {:?}", &event.data());
    })
}

You can check, how we created a worker function with name hello_world that it can be called from javascript.

For the Javascript part, we have the index.html and the worker.js:

<!DOCTYPE html>
<html>

<head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
    <style>
        body {
            margin: 0px;
            padding: 0px;
        }
        canvas#my_canvas {
			position: absolute;
            width: 100%;
            height: 100%;
        }
    </style>
</head>

<body>

<div style="max-height: 256px;max-width:256px;overflow: scroll;">
    <canvas id="my_canvas"></canvas>
</div>

<script src="pkg/my_webgl_app.js"></script>

<script>
    const {main} = wasm_bindgen;
    async function run() {
            await wasm_bindgen();
            main();
    }
    run();
</script>

</body>
</html>
// worker.js
importScripts("./pkg/my_webgl_app.js"); //generated wasm code, the name depends on project

console.log("Initializing worker");

// In the worker, we have a different struct that we want to use as in
// `index.js`.

async function init_wasm_in_worker() {
  self.onmessage = async (event) => {
    await wasm_bindgen("./pkg/my_webgl_app_bg.wasm");
    const { MyWorker } = wasm_bindgen;
    var my_worker = MyWorker.new();

    var hello = my_worker.hello_world("john");

    console.log("please ");
    console.log(hello);

    // Send response back to be handled by callback in main thread.

    setInterval(() => {
      //postMessage();
      self.postMessage("returns message");
    }, 500);
  };
}

init_wasm_in_worker();