Disclaimer: I am not a veteran Rust programmer, so over time I may find out the code can be written/refactored in a better way. If I encounter this, I will post a follow-up with my findings. I welcome your feedback if you have Rust experience.
Armed with my foreknowledge of C and C++, I set out to learn the language and master yet another experiment.
There are several steps to set up your environment to build Rust apps and specifically target WebAssembly. To learn how, I followed the excellent online book/tutorial:
I suggest you start there, and it will be easier to make sense out of the moving pieces in my solution:
Implementation of plasma effect using WebAssembly compiled from Rust.
Plasma WASM Rust
This is a port of my
Go implementation of plasma to Rust. It uses
wasm-pack-template. Inspect the Azure pipelines definition for build details.
For a full walkthrough, read: Plasma gets Rust-y: Another WebAssembly Experiment.
This repository is continuously built and deployed using free Azure Pipelines. If you're interested in how it was setup and configured to build automatically and deploy to low cost Azure Storage Static Websites, read Deploy WebAssembly from GitHub to Azure Storage Static Websites with Azure Pipelines.
If you’re new to Rust, notice that there is no need for an explicit
return statement. The value in the method is implicitly returned.
This is the code to generate the sine table.
Part of the power of Rust is how it handles threading and avoids conflicts and race conditions. Variables by default are immutable, so the
mut keyword is needed to indicate that the value of
idx will change. The code iterates from 0 to 511 (the end range is exclusive, the beginning inclusive) and maps the value to a formula that generates the sine information. It is cast as a 32-bit integer and
collect() is called to turn it into the collection (
Note: the explicit return is based on my habits from other languages, I could have simplified the code by removing a few lines and implicitly returning the table.
A similar range iterator is used to generate the palette data.
The final piece of code is a
tick method that advances through the sine table with every frame. Like the other experiments I ran, this code essentially builds out a buffer of pixel data based on the current cycle.
self is passed in with
mut because the buffer will be updated. The buffer itself is mutable as it’s being constructed.
In the root directory. The assets are placed in a
The first thing I noticed was the size. My Go experiment resulted in a 2 megabyte
.wasm file. The corresponding Rust file is only 65 kilobytes! That’s a massive difference in size that is very important to consider for consumer-facing apps.
The second thing I noticed was the
plasma_wasm_rust.js file. To build with Go you use a standard
wasm_exec.js that is copied “as is” for generic bindings. The Rust environment generates code specific to your own app, including bindings to the methods and structures that were explicitly exposed and marked with
www folder is a small Node.js web app that is used to deliver the project to browsers. It is linked to the assets from the Wasm build and will build a distribution with all the files you need. The HTML shell contains some basic styling and boilerplate to bootstrap the application. Everything unique is contained in the
The bootstrap file imports the
index.js file and generates additional code to load the Wasm environment when the project is built.
The custom code starts by importing the Wasm classes for Plasma and memory management. The
memory module is very important … stay tuned. The following code creates an instance of the plasma structure, grabs the width and height and configures the canvas.
The rendering loop is called for each animation frame (when the browser is ready to repaint). It advances the sine table, then calls a method to draw it, and repeats.
Finally, to “draw” the plasma, use the following code.
memory module allows direct access to the existing memory. The array is created by pointing straight at the memory allocated by Wasm, passing in a pointer to the pixel buffer and the size of the data. This buffer can then be passed “as is” into image data that is drawn on the canvas.