TwoRS
Easily render 2D graphics in a canvas using WASM - entirely powered by Rust!!! 🦀
😎 Why Rust/WASM?
WASM's use case is either the reuse of code written in another language or to offload heavy computations to the near-native execution speeds of WASM.
However this is not free - traversing the WASM boundary means going trough some glue code and copying data from JS to the WASM module memory and back. In the case of strings it's even more expensive - because JS strings are UTF-16 and Rust strings use UTF-8 any string passtrough needs to go trough a copy AND a re-encode.
twors doesn't have any complicated physics to offload to WASM at this point in time - neither
is it making use of some advanced pre-existing Rust library. So why Rust then?
⭐ Simply because Rust is the best!!! ⭐
⚡ Quick start
- To quickly play with
tworsyou can make use of theexamples/playgroundcrate in this repo - it contains code for moving a rectangle via the WASD keys.
# edit the source code in examples/playground and manually refresh http://localhost:8080
- To add
tworsto your own crate - check out the Installation and build guide
✨ Features
✅ TwoRS's goal is to be simple. It aims to provide:
- An adaptive canvas resolution (on canvas resize)
- A main loop with precalculated delta time
- Keyboard/mouse input handling
- A convenient component system for code organization
- Basic component support with
on_initandon_updatelifecycle methods - Transform
- Translation & scale
- Rotation
- Inheritance (so a component moves with its parent)
- Dynamic component instantiation and removal
- Interacting with components from other components via
get_component_by_id
- Basic component support with
- Rectangle-based 2D collision detection
- Utilities
- Linear/smooth interpolation (for animations over time)
- Bezier curves
- Factories for various shapes & styling
- Square & rectangle
- ...
- Benchmark
❌ TwoRS does not currently have (and aim) to provide:
- a physics engine
- an algorithm library (e.g. pathfinding)
- a sound library
- 🎨 *a UI library (check the note below)
🎨 * a UI library
- If your project requirements allow it you can implement your UI in Rust via framework like
Dioxus, Leptos,
Sycamore and Yew to name a few.
- These, however, are the Rust analog to JS frameworks like React - they will not allow you to create a UI that's rendered directly in the canvas. If the UI can live in the DOM then they are a great choice!
- Still - UI is an important part of any application and not having support for canvas UI might
be a dealbreaker. I might open source another library (based on this one) for simple canvas UI
support after I am done with the initial feature set of
TwoRS
🛠️ Installation and build
Compiling a WASM library is a bit more involved than simply executing cargo run, but
it's pretty straightforward if you know the steps.
We'll basically:
- set up our crate for WASM compilation
- add the bare minimum code to run the
tworsengine - add an
index.htmlwith some JavaScript (just enough to run our WASM library) - serve all of this goodness via an HTTP server
Once we do this all of the remaining code can be written entirely in Rust!
You are of course free to mix and match as you like.
Step 1 - Create a lib crate
In order to compile Rust into WASM via wasm-pack
(an amazing WASM compilation helper) it's necessary to have a lib crate first
Step 2 - Install twors, wasm_bindgen and console_log
- A browser-capable logging backend crate like
console_log is needed to enable
logging in the browser console
- You will also need a logging frontend crate like log to be able to print messages yourself.
- wasm_bindgen will generate the necessary JS/Rust
glue to enhance what can go trough the WASM/JS boundary.
- Without it our Rust code that's called from JS (and vice-versa) would be able to only take numbers as arguments and return numbers as a result. With this crate we enable a lot more types in our JS/WASM boundary API.
Step 3 - Create and run the twors engine.
- For now we won't add any components, so we'll see a blank canvas when we open our application in the browser.
use ;
use wasm_bindgen;
use HashMap;
use console_log;
// The "wasm_bindgen" attribute will generate glue both on the JS and on the WASM sides.
// Passing Rust types like `&str` from JS is thanks to the magic of `wasm_bindgen`.
Step 4 - Change the crate type to a dynamic library intended to be loaded from another language
Add a lib section to your Cargo.toml file (if you don't already have one) and set
the crate-type property to cdylib.
The wasm32-unknown-unknown compile target (what wasm-pack makes the Rust compiler use) will
detect this and produce a WASM library.
[lib]
crate-type = ["cdylib"]
Step 5 - Install wasm-pack and build your WASM library
The following wasm-pack --build command will produce a pkg folder in your crate's root -
this folder will contain the WASM library as well as the JS part of the glue code that's
needed for the WASM-JS communication.
Step 6 - Copy examples/assets/index.html to the pkg folder in your crate's root
We have compiled our WASM library - now we need to call it from JS. The example index.html
will:
- create a full screen canvas
- run our Rust
entrymethod (that we created earlier) by passing it the canvas ID.
# navigate to your crate root and execute the following
NOTE: If you don't have the
cpcommand (e.g. if you are using Windows CMD) you can always copy theindex.htmlmanually.
Step 7 - Serve the pkg directory at the crate root
You will need to serve the WASM library with the application/wasm MIME type or the browser
will refuse to run it. A great server that does this automatically is
miniserve
🧱 Fundamentals
- TODO
💻 Development
Experiments and manual testing during development can be done in the examples/playground crate.
# convenience scripts - see "Makefile.toml" for full list of commands
# run local pre-commit checks - will be run on "build" automatically
# other commands
Notes
- Make sure to use
wasm_assert!instead ofassert!in non-test code to see error messages in the browser console.