Bevy: Web Support

Created on 5 Aug 2020  Â·  22Comments  Â·  Source: bevyengine/bevy

It should be possible to run Bevy Apps on the web

ci / build system enhancement platform web

Most helpful comment

I just closed #613 as bevy_webgl2 is available as external plugin now (and should work with current bevy master branch): https://github.com/mrk-its/bevy_webgl2

All 22 comments

yeap, plz, i am waiting for, i am using the pathfinder now

The most significant blocker for full web support is likely to be multithreading. An MVP for this probably needs to be single threaded.

Some thought should be put into understanding what Bevy changes would be required to support asynchronous thread waiting, as it is not possible to block the main thread in web browsers.

A couple alternative solutions may exist to this and I’ll leave them here for future discussion: Binaryen Asyncify, and rendering off the main thread using a worker and some combination of an offscreen canvas API and WebGPU.

@lwansbrough what is about run thread in web work?

@NateLing With the Canvas API we can transfer control from the main thread to a web worker. According to this comment WebGPU will (may already?) have the same capability to transfer control to a worker. _Edit: I see now that WebGPU uses a canvas like canvas.getContext('gpupresent'); so it works the same way in theory._ This transfer of control works like this for Canvas:

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);

from: https://developers.google.com/web/updates/2018/08/offscreen-canvas#unblock_main_thread (This page has several examples.)

Then I suppose Bevy would then spawn additional WebAssembly threads from this main worker. I don't know what Bevy needs in order to work, but it sort of seems to me like the required browser features exist.

Edit:

So the issue here actually seems to be what happens once you start doing work in workers. Work done by the Amethyst team showed that browsers will not execute tasks until control is returned to the main thread. You can follow that issue here and Azriel made a demo which you can find here.

Heya, one thing I'd like to share:

It's quite difficult to integrate with the browser event loop unless one uses async Rust, but it's also difficult to share source for both a native app and WASM app using winit (because winit's event loop is non-async).

So, is it possible to use winit, async, and WASM all at the same time?

The answer is yes -- before I went back to work, I made this thing: https://nginee.rs/examples/event_loop_rate_limit.html

Also, worth noting that that currently uses a single threaded pool to run the tasks (futures), I hadn't tried integrating with web workers yet

(not intending to promote -- please look at the code for how you would convince Rust to run async event handlers inside a synchronous winit event loop, and not freeze the browser)

An approach to working with async in the browser is to have a Vec of events that are passed to an async function. If the async function is blocked, then enqueue the event for the async function to receive later.

There's an incomplete but workable implementation of this idea in kapp: https://github.com/kettle11/kapp/blob/main/src/async_application.rs

It allows code like the following example to be written:

async fn run(app: Application, events: Events) {
    let mut _window = app.new_window().build().unwrap();

    // Loop forever!
    loop {
        match events.next().await {
            Event::WindowCloseRequested { .. } => app.quit(),
            Event::Draw { .. } => {}
            _ => {}
        }
    }
}

Yet another approach is to run the entire engine off the main thread. This makes blocking a non-issue, as WASM Threads (Web Workers) _are_ allowed to block.

This is an example using WebGPU to render in an offscreen canvas hosted by a web worker. This _should_ work once WebGPU contexts are supported by offscreen canvases. It sits beside a main thread canvas which is rendered using WebGPU. That part is working today in Chrome Canary with --enable-unsafe-webgpu enabled.

This is an active area of development. Support for WebGPU in offscreen canvases in Chrome is being tracked here and is currently blocked by a refactor.

FYI the issue fixed by https://github.com/uuid-rs/uuid/pull/477 was blocking compilation to WASI for several of Bevy's crates, but just got merged today.

I attempted porting https://github.com/amethyst/web_worker WorkerPool implementation as a replacement TaskPool - it looked very promising and the changes are straightforward.

The main issue I stumbled is that wasm_bindgen::JsValue and wasm_bindgen::closure::Closure are not Send nor Sync, thus such TaskPool cannot become a Resource. It may be an issue for any implementation using wasm-bindgen provided bindings.

You can look at my change at: https://github.com/smokku/bevy/commit/2ad15d8e1830b891e76b73c92dab28a2dd911ee5
Just an experimental PoC, so a bit messy at the moment. See README on how to compile.

The main issue I stumbled is that wasm_bindgen::JsValue and wasm_bindgen::closure::Closure are not Send nor Sync, thus such TaskPool cannot become a Resource. It may be an issue for any implementation using wasm-bindgen provided bindings.

Is wrapping each of them in an Arc a problem?

Is wrapping each of them in an Arc a problem?

Unfortunately the internals of these types are the issue. Arc protects the type, but not internals.

Single threaded implementation works fine though. https://github.com/smokku/bevy/commit/72d672515584e7c865915c649a8759dcf3460b1b

I will make it into a proper PR soon - just have a question:

  • Should I make the TaskPool changes a separate feature?
    i.e. Legion has parallel feature (enabled by default). I think there may be other contexts where single threaded implementation might be useful.

I'd say if making TaskPools a separate feature makes this easier, definitely go for it. But if we can somehow make TaskPools work on web, then being able to assume that TaskPools are always available (an all platforms) would be nice. Web is the only platform on my radar that might have issue with threads (that i know of).

This is really exciting. Great work :smile:

It definitely is not easier. Once a Cargo feature is enabled, it is no way to disable it. We would have to maintain parallel default features sets for different targets. Been there - ugly.
The code would be sprinkled with cfg macros anyway, no difference whether these check for feature or target.

So, I will just make the cfg on target arch and if there comes a need for parallel feature, we could change in future.

P.S. I would love to get wasm threads running too, but it currently is way over my league. This is just to get the wasm story started.

@smokku

The main issue I stumbled is that wasm_bindgen::JsValue and wasm_bindgen::closure::Closure are not Send nor Sync, thus such TaskPool cannot become a Resource. It may be an issue for any implementation using wasm-bindgen provided bindings.

Would transmuting the JsValue solve the problem? https://github.com/RSSchermer/web_glitz.rs/blob/8730f44108f89844eb303057f1b13118604a3e4f/web_glitz/src/util.rs#L10

Would transmuting the JsValue solve the problem?

It doesn’t look like it. JsValues do not document any guarantees (instead mentions it is subject to change), and JsValues are not repr(C).

I attempted porting https://github.com/amethyst/web_worker WorkerPool implementation as a replacement TaskPool - it looked very promising and the changes are straightforward.

The main issue I stumbled is that wasm_bindgen::JsValue and wasm_bindgen::closure::Closure are not Send nor Sync, thus such TaskPool cannot become a Resource. It may be an issue for any implementation using wasm-bindgen provided bindings.

The point where the TaskPool is converted to a resource is here (line 46). Send + Sync is required because it is expected that the TaskPool should be made available to all threads in the pool. In the context of the web, this implies that the TaskPool would be available as a resource to all web workers ("WASM threads").

The problem that arises is that as you discovered, JsValues are not Send/Sync. Why are JsValues not Send/Sync? Because JsValues (read: Javascript objects) are not allowed to be shared between web workers, with a few exceptions I believe (some primitives?) The documented ways around this are message passing via postMessage and of course the notorious SharedArrayBuffer.

But lets get back to why this is a problem to begin with: why do you need to be sending JsValues between threads? I took a look at your code. The issue is you have an internal reference to a collection of Worker types. Of course, these cannot be Send because they are JsValues. And it doesn't make any sense to copy these types, because they represent complex entities. But do they need to be sent at all? I believe it follows logically that if the task pool orchestration happens on the main thread (which it does I believe via Bevy's ParallelExecutor?), then the only context that needs access to the underlying workers is the main thread.

Following this, one solution to the problem is to create a place for the Worker pool to live outside of the worker-based TaskPool instance, such as a static on the implementation. This removes the Worker pool from the memory that will be sent to each web worker, allowing the TaskPool to be Send + Sync.

One issue I can foresee is that workers may in the course of their execution be asked to create new workers. For example, a system may want to parallelized some asynchronous tasks. In this case (probably? I'm not familiar with the async task API), it would expect to be able to obtain a reference to the TaskPool so it can spin up new workers. It would likely be necessary for the web worker based TaskPool to defer worker creation to the main thread, so that these workers are not accidentally instantiated as grandchild workers (parented by the worker instead of the main thread.) Although again I'm not sure if this is a bad thing -- it probably depends on the expected lifetime of the workers.

Regarding rendering support: with this little change: https://github.com/mrk-its/bevy/commit/6145d85dc033b69825cd3bb125d8868c81f018ef (it removes spirv-reflect and bevy-glsl-to-spirv as @cart suggested some time ago) it is possible to build bevy with bevy_render. It allows enabling bevy_sprite or turning on HeadlessRenderResourceContext. Of course it doesn't enabling any rendering itself, but it allows to use regular bevy sprite components and reuse systems creating sprites / computing sprite positions, so web-specific rendering is much simpler now (this is how I'm doing it now: https://github.com/mrk-its/bevy-robbo/commit/3f7ec98512b15ba09ec5cfb379ca295b1bc2963a#diff-dd449560f9dded07a56207afe117d24e) I'm going to play now with adding very basic 2d rendering support for wasm32 target, with features required by bevy-robbo (SpriteAtlas support) (on start implemented with webgl2 on web_sys probably, as I'm most familar with it now).

Dropping by to say that https://github.com/chemicstry/wasm_thread exists, in case people were exploring this.

Dropping by to say SUPERMAN exists.

On Sun, Nov 8, 2020 at 12:35 PM Azriel Hoh notifications@github.com wrote:

Dropping by to say that https://github.com/chemicstry/wasm_thread exists,
in case people were exploring this.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/bevyengine/bevy/issues/88#issuecomment-723541547, or
unsubscribe
https://github.com/notifications/unsubscribe-auth/AHGK5THYHCWI7AK7ROKO5SDSOZC4HANCNFSM4PU7ZDMA
.

>

Raza Amir

Dropping by to say that https://github.com/chemicstry/wasm_thread exists, in case people were exploring this.

@azriel91 thanks for checking in and with a tip! We haven't kept this issue updated as well as we could.
There is a proof of concept of using wasm_thread in #631 thanks to @chemicstry :)

For an update on @mrk-its 's comment

I'm going to play now with adding very basic 2d rendering support for wasm32 target, with features required by bevy-robbo (SpriteAtlas support) (on start implemented with webgl2 on web_sys probably, as I'm most familar with it now).

The current progress of bevy_webgl2 is in #613. @mrk-its uploaded a showcase of bevy examples using it on https://mrk.sed.pl/bevy-showcase/ , where 2d rendering, 3d rendering, and gltf loading work!

I just closed #613 as bevy_webgl2 is available as external plugin now (and should work with current bevy master branch): https://github.com/mrk-its/bevy_webgl2

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TheArchitect4855 picture TheArchitect4855  Â·  3Comments

TehPers picture TehPers  Â·  4Comments

PradeepKumarRajamanickam picture PradeepKumarRajamanickam  Â·  4Comments

atsuzaki picture atsuzaki  Â·  4Comments

ahfuckme picture ahfuckme  Â·  4Comments