gyscos / cursive

A Text User Interface library for the Rust programming language
MIT License
4.26k stars 243 forks source link

add wasm-backend using HTML canvas element #741

Open genieCS opened 1 year ago

genieCS commented 1 year ago

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 commented 1 year ago

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

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

gyscos commented 1 year ago

Thanks for the work!

My main concern is that the async features:

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).

genieCS commented 1 year ago

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

gyscos commented 1 year ago

(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.)

genieCS commented 1 year ago

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

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

genieCS commented 1 year ago

@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 commented 1 year ago

@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?