cursive icon indicating copy to clipboard operation
cursive copied to clipboard

add wasm-backend using HTML canvas element

Open genieCS opened this issue 1 year ago • 7 comments

Add WebAssembly (WASM) backend to Cursive library

This PR adds a new backend to the Cursive library that allows it to be used in WebAssembly (WASM) environments. The new backend is implemented using the web-sys, js-sys crates and provides a way to run Cursive-based applications in the browser.

To use the new backend, simply enable the wasm-backend feature in your Cargo.toml file:

[dependencies]
cursive = { version = "0.16", features = ["wasm-backend"] }

I made sample project using this backend, so you can see how to use this new backend. repo link: https://github.com/genieCS/wretris project link: https://geniecs.github.io

genieCS avatar Jul 13 '23 06:07 genieCS

Just a shallow, quick review; currently breaks builds due to changing default features.

thank you for taking a look:) I addressed what you mentioned

genieCS avatar Jul 27 '23 07:07 genieCS

Thanks for the work!

My main concern is that the async features:

  • is non-additive since it changes the API
  • seems tied to wasm (we could imagine an async loop without wasm).

To avoid these two points, I'd be open to a major bump for API changes if needed, maybe even making async the default way to run the loop (or an option regardless of backend).

gyscos avatar Aug 17 '23 15:08 gyscos

@gyscos I made run as async. Could you have another look?

genieCS avatar Sep 02 '23 06:09 genieCS

(I'm finally back from a family trip and have at last some time to look at this PR in more depth.)

Thanks for the updates and reactivity!

Looking at the code more, I think we can actually avoid breaking changes by adding a parallel code-path for async (rather than replacing the existing one). The current "sync" API could be defined as simply calling block_on the async API, preserving compatibility with existing code, without duplicating (too much) logic (I wish this didn't require the futures crate though).

The "bare minimum" would be to have async fn post_events_async(), so users could re-build their own event loop (the various step and run methods are just convenient methods to remove some boilerplace, but could be re-implemented user-side).

EDIT: I tried to do that in a async-restricted branch, but I have no idea how to test it and see if it works.

We could then, later, add the convenient equivalent.

It also looks like the sleep behaviour should maybe be part of the backend instead - and this opens up the possibility of having other async methods from the backend (input? drawing? refresh?). At this point, post_events will no longer be the only method with an async equivalent. The main issue is having async in traits currently requires the async_trait crate. The heap allocation per method from async-trait doesn't matter much when it's just to call run (or even step), but it might not be ideal for the Backend trait, especially for the many print methods (potentially thousands per second). On the other hand we're using dynamic dispatch for the backend (which makes it incompatible with the current async_fn_in_trait proposal) so maybe we should just use async-trait here, only making sleep async at first, and maybe refresh/input later (but not the print methods).

As a side-question, I'm wondering if this wasm backend would work in a non-browser wasm environment (like wasmtime?). I suspect not, so I'd favor calling it wasm_browser or wasm_js or something else more indicative of the browser aspect.

Also, would it be possible to add a very simple example using this backend? I haven't found how to use wasm-pack with an example, so maybe a dedicated "example" crate would be simpler.

(I'm really sorry for the back-and-forth.)

gyscos avatar Sep 10 '23 21:09 gyscos

@gyscos thank you for your time and effort to review this PR.

  • "wasm_js" or "wasm_browser" idea sounds good to me. I'll think more to rename it.

  • Promise can be executed only if it runs forefront. In your async-restricted branch, it is wrapped by futures::executor::block_on so it would not work. How about having duplicate sets of sleep, post_events, run, step, ...etc with configuration option as my first draft?

regarding test, I'll make a simple project and let you know in a week

genieCS avatar Sep 18 '23 12:09 genieCS

@gyscos I made a simple project(https://github.com/genieCS/hello-wasm) for tests.

  1. in / path, run wasm-pack build
  2. in www/path, run npm run start if you see "Hello from Rust!" in web console, it works.

genieCS avatar Sep 23 '23 06:09 genieCS

@gyscos canvas is for browser but it would not be hard to make other wasm backends work. I'd like to work on implementing other backend as a separate PR.

regarding async_trait, I agree that it would be more proper to put sleep in backend. but for now, it's only sleep, so how about keep current status and when we have more to change Backend trait related to async, change together?

genieCS avatar Sep 23 '23 06:09 genieCS