Passion projects and ruminations related to IT development.
Related
As I venture further into my frontend programming journey, I come across WebAssembly. It is a binary instruction format with multiple programming languages able to “transpile” to it. I want to use it to outsource computationally expensive algorithms and code that is more intuitive to write in other programming languages. In this case, we wish to tackle simple automata in Rust. We are used to testing in Rust, so we can iterate a lot faster than resorting to a Javascript implementation.
For the frontend we will turn once again to Elm, expanding our knowledge to include: custom elements, SVG interactivity and URL parsing. Let’s get going.
We assume the read is familiar with chapter 2 of New Kind of Science. This will make reading the rest of the article a lot easier.
We first want to present some definitions to the reader, assuming you read chapter 2 of NKS you will recognise the following concepts:
To illustrate, if we have only dead and live cells with the window size of 3, we get 8 combinations:
dead-dead-dead
dead-dead-live
dead-live-dead
dead-live-live
live-dead-dead
live-dead-live
live-live-dead
live-live-live
If we enumerate them and give them outcomes:
0 dead-dead-dead -> 0
1 dead-dead-live -> 1
2 dead-live-dead -> 1
3 dead-live-live -> 0
4 live-dead-dead -> 0
5 live-dead-live -> 1
6 live-live-dead -> 1
7 live-live-live -> 0
we can construct the rule index as follows:
(0 << 0) + (1 << 1) + (1 << 2) + (0 << 3) + (0 << 4) + (1 << 5) + (1 << 6) + (0 << 7)
= 0 + 2 + 4 + 0 + 0 + 32 + 64 + 0
= 102
In our frontend we visualize this rule using our combinations view (white = dead, black = live):
Interactive design for rule index manipulation
By clicking the individual combinations, you can toggle their outcome. It will also update the text box containing the numerical value of the rule index.
The Rust implementation ended up being small enough. We use the same bitvec library as we did for an earlier project for solving Sudoku puzzles. There was the option to use other libraries such as fixedbitset, however I opted for the extra available bitwise functionality offered by bitvec.
We decided to adopt the following assumptions:
The bitvec library offers some nice macros to represent bit vectors, which we made handy use of in our unit testing module. In the end, our implementation stores the rule as the rule index, the integer is sufficient to be able to perform the necessary computations. There is no need to unpack it and come up with elaborate data structures, unlike in later automata. The code to apply the rule is
Since we update the cells in place, we use a single integer as a cache to keep track of old values. You will also notice that the first window constructed will have its rightmost cell be the first in the universe. All others cells will be the last (window - 1) of the universe, yet we are using the outcome and writing it to the first cell of the universe. This of course is incorrect, and we end up course correcting at the very end by shifting the whole universe to the left. Doing it this way means we don’t have to fiddle with indices too much.
Adopting WebAssembly for our project was easy enough with the excellent tutorial (already showcasing Conway’s Game of Life) provided by the creators of rustwasm. On top of that, we made handy use of an already existing template to generate our skeleton to include Elm and Rust.
That said, there are some small caveats to keep in mind:
We had the option of using web-sys to go a step further and build our DOM in Rust, or draw on the canvas. In the end we opted to split that functionality and leave DOM manipulation and canvas drawing to Javascript instead.
Compared to our QuizCraft project we lose complexity in the form of multipage functionality and gain some in the form of Javascript interoperability and WebAssembly (not directly in Elm but in Javascript).
The novel element this project introduces in our Elm learning path is the custom element. It replaces ports as our gateway to Javascript interaction. A custom element is a Javascript class implementing the HTMLElement (or related) interface. As the developer it is up to you to declare a number of element attributes. These attributes can then be changed through Elm, and the custom element will in turn invoke functions on the Javascript side allowing us to react to changes in the attributes.
Let’s look at the Elm code first:
The mapCustomAttribute
is a helper function to introduce some more structure, but it can be seen that we are talking about a single HTML “cellular-automata” element. Here is what it looks like in HTML, with the two canvas elements as its children:
HTML custom element
On the Javascript side we showcase the skeleton code for the custom element:
As seen above, you must specify the expected attributes in the observedAttributes
method, a change in an attribute not part of this list will not trigger the attributeChangedCallback
method.
We also included a custom method named notifyState
as an example of how to relay information from Javascript back to Elm using custom elements. In this case our Elm app syncs its running state (referring to the animation state) with the Javascript code responsible for managing the canvas drawing. When Elm passes a desired-state
of paused
, then the Javascript code will halt the rendering of any animation frame. If this was successful, it will fire a custom event named state-change
, informing Elm that the pausing was done successfully. On Elm’s side, we already displayed the onStateChange
event as a HTML attribute for the node element. All we need now is a decoder:
We now have full interactivity between Elm and Javascript. The custom events makes it easy to extend upon instead of having to onboard new ports or include logic to differentiate between events on one port.
The rule index text field in our app makes it easy to configure the automata rule, but we also want people to simply share a URL with other people with whatever interesting rule index they encounter. By inputting the URL, the app will automatically load the rule based on the supplied rule index as a query parameter. We solve this by updating the URL whenever the rule index is modified in the app. Since we want URL manipulation, we resort to the Browser.Navigation
module to update the browser address bar whenever the rule index changes with:
And in reverse we need a parser to extract the rule index from the URL and load it when the page loads:
You will notice that aside from the rule index, we also include the window size as a query parameter. Since we are using a third party library for the rule index (represented as an unsigned integer of 64 bits) we used a custom parser. The init
function is modified to use the parser to operate on the supplied URL and provide sensible defaults in case the parameters prove faulty.
We used the elm/svg
library to draw our combinations interface pictured earlier in the Math section. The solution to incorporate interactivity is dead simple, since each combination can either have on (live) or off (dead) for its outcome. With this boolean nature, we do not have to add a lot of events to individial SVG elements and we can attach a message to the entire SVG (each combination is its own SVG):
All that is needed is an onClick
attribute. In later automata, we will need to account for a whole array of effects for a combination and we won’t be able to resort to such a simple solution no more.
Whilst I have a lot of good things to say about Elm, we knew we’d find fault with the language at some point, as we would with any other language. So this is a small rant about third party packages and how they are dealt with. Third party packages can be registered on Elm packages, the site however is not an artifact store and it all it really does aside from displaying documentation is to link to a Github repository containing the actual Elm code.
This poses a huge problem, which we encountered when trying to apply malaire/elm-uint64. Using elm install
will complain about the package source code not being available and a quick look at the Source link provided on the Elm packages website does indeed confirm the repository does not exist. The original author deleted it citing displeasure with the way Elm was being managed. Luckily, another Elm developer happened to have the package code stored locally and pushed it to Github, resubmitting it to Elm packages: MartinSStewart/elm-uint64.
This does make me more wary of any third party Elm libraries and the Elm ecosystem as a whole.
For hosting we opted to leverage our AWS knowledge and simply create a S3 bucket configured to serve as a static website hosting solution using CDK. The code is available here.
In addition, we deploy a ServiceCatalog portfolio and product to easily onboard new Elm+Rust WebAssembly projects. The product will take a Github repo (assuming you set up an OAuth2 Codestar connection to your Github account) and a URL path (S3 path in bucket) under which the web app will be made available. In doing so, we can reuse our deployment strategy much quicker in the future for additional automata web apps.
We had a good idea of what new elements we needed to use to get this web app made, and no big surprises presented themselves along the way. We used custom elements for Javascript interoperability. We used SVG for drawing UI elements. URL parsing proved straightforward enough.
The simplicity of the model representing a simple automata rule allowed us to focus on these new elements, and passing information from Elm to Rust/WebAssembly became a non-blocker. The resuls can be seen on our Vault.