Skip to content

Running *part* of your wasm in a web_sys::Worker? #2549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sgasse opened this issue May 9, 2021 · 5 comments
Closed

Running *part* of your wasm in a web_sys::Worker? #2549

sgasse opened this issue May 9, 2021 · 5 comments
Labels

Comments

@sgasse
Copy link
Contributor

sgasse commented May 9, 2021

Summary

What is the best architectural approach to have a 'pure' wasm web application that runs part of the calculations in a web worker?

Additional Details

I am still fairly new to Rust and especially web_sys. However I find the concept of wasm really exciting and I am currently trying out to build an easy web app. It needs to react to clicks on a canvas, some buttons etc., but then there are heavier calculations which should run in the background.

Initially, I followed the Wasm Game of Life tutorial and found a good first entry point there. After reading this guide, I designed my app to handle the user interaction part in JS. The background calculation was running actually in the foreground but returned very regularly, giving a false impression of responsiveness.

Next I read about web_sys and especially building wasm apps that can be deployed without a bundler. Now I rewrote the user interaction part almost purely in Rust and web_sys. Knowing that there is probably no native multiprocessing in wasm as of now, web workers are the only alternative.

My dream scenario would be to spawn a web_sys::Worker and hand it a struct with a method or a function callback to run. However it looks like a web worker expects a URL to execute (1, 2). Loading in any local JS via the URL works, but not a wasm module.

If I create a worker in my main wasm app

pub fn setup_worker() {
    Worker::new("worker.js");
    console::log_1(&"Created a new worker".into());
}

and have this minimal worker.js:

import Dummy from './pkg/my_wasm_app.js';

I get the error SyntaxError: import declarations may only appear at top level of a module. This might have JS reasons (not so familiar with JS ^^). Though maybe someone could point me in the right direction? Which one of the following options is possible? And which one is best?

  1. Setup a separate wasm project with target so that I can load an init. I think it is not possible to have two wasm targets in the same cargo.toml so it would be annoying to maintain two project folders for essentially one projects. Also I would need to figure out a better way to load it given that the above approach at least for some imported module was not allowed in the worker.
  2. Pass a struct to a worker that is being created and glue the interaction with Worker::set_onmessage etc. - this seems best to me but I am not sure if it is possible at all.
  3. Setup the worker code in JS (like e.g. here and just call it from web_sys. I don't like this option very much because it would mean having to dig deeper into JS for me.

Are there any good tutorials on this? I know about the Parallel Raytracing example but I could not really extract how to use the web_sys::Worker from it. Also this WASM Fractal Generator seems to have the worker and canvas interaction in JS, so it is not really what I am looking for.

Any hint/explanation would be greatly appreciated!

@sgasse sgasse added the question label May 9, 2021
@alexcrichton
Copy link
Contributor

Background work is unfortunately somewhat difficult, even today. This is largely due to the ecosystem being relatively immature around multithreading and how the multithreading story in a browser is so different from a native version. I'd recommend following the parallel raytrace example if you can, but that's all the advice that I would have. For other advice I'd try reaching out to others in other forums perhaps.

@sgasse
Copy link
Contributor Author

sgasse commented May 12, 2021

Alright, thank you any way @alexcrichton 🙂 I will take a closer look at the raytrace example then.

@sgasse
Copy link
Contributor Author

sgasse commented May 13, 2021

I could not let it go and finally figured it out! 😃 One of the main blockers for me was that Firefox does not support importing ES modules in workers - with my limited JS knowledge, it took me a while to nail down the problem as this.

To be able to go back to it, I created four example starting from a very basic example with modules (which only works in Chrome) over using --target no-modules to a minimal 'full' example in which I spawn a web worker with web_sys from Rust and interact with it in a useful way. You can find the example in this repo. Do you think the fourth example could be helpful for others as well @alexcrichton ? Then I would create a PR for wasm-bindgen/examples and put it up for review.

@alexcrichton
Copy link
Contributor

More examples always seems good to me!

@sgasse
Copy link
Contributor Author

sgasse commented May 14, 2021

Great, I opened a pull request :) Since it is my first contribution to wasm-bindgen, I hope I followed all the conventions that I could deduce from other examples. Would be great if you or someone else could review!

alexcrichton pushed a commit that referenced this issue May 18, 2021
This commit adds a parallel execution example in which we spawn a web
worker with `web_sys`, run WASM code in the web worker and interact
between the main thread and the web worker.

It is intended to add an easy starting point for parallel execution
with WASM, complementing the more involved parallel raytrace example.

Related to GitHub issue #2549

Co-authored-by: Simon Gasse <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants