Two weeks ago, Alex Crichton’s PR adding a target for
WebAssembly to the Rust compiler was merged. There are many differences
between this target and the Emscripten one, but the important one for me is that
it doesn’t depend on external stuff like the Emscripten SDK (which
IIRC used to be a pain to get working on Windows, but seems to be better
now).

After seeing the examples on hellorust.com, I thought it would
be interesting to try to adapt my game Rocket to work
on the browser through the wasm32-unknown-unknown target. The project
was a great way to figure out how far you can go when porting a project that
is a bit more complex than a hello world. I was pleasantly surprised by the
fact that most of the code could be reused. Particularly, the game logic code
was barely touched at all.

TLDR

An MVP

Getting things to compile: removing Piston

Before I started, the little I knew about WebAssembly was that it doesn’t allow
you to interface with the OS, graphics card or other stuff like that. Using
Emscripten seems to be a way around this problem, but I guess you still need
to adapt your programs to some extent… I have never used it, though, so take
my words with a grain of salt.

After cloning the Rocket repository I started removing stuff. The first thing to
go was the dependency on Piston. I didn’t even try to compile Rocket to wasm
before this step, as it is obvious that Piston requires OS support.

At this point, we were left with:

No game loop

No rendering

No player input

Rebuilding the game: laying down the basic structure

So here we are without even a main function. This means that the game loop should
be implemented in Javascript and call into our Rust functions. Therefore
we need a set of basic functions that are enough to drive the execution of the
game, draw something to the screen and process user input.

Since rendering and processing player input are more involved than just updating
the game state, I chose the latter as a first function to implement. I was able
to reuse the code for the game logic without any change, so the function ended
up looking as follows:

Surprisingly, the update function on the original game is exactly the same,
with the exception of the use of DATA. By the way, we use DATA to
store state instead of passing it between Javascript and Rust every time we call
a function. The definition is quite simple:

Since DATA is accessible from anywhere in the program, Rust forces us to use
a Mutex to ensure thread safety. Technically, this isn’t necessary in the case
of Javascript, since there will only be one thread. Still, the type system knows
nothing about that… Hence the mutex.

Getting things to compile, take two

With Piston out of the way, I set out to get the rest of the code to compile and
to run it in the browser as a simulation without any visual output. This is the
moment where difficulties started to pop out.

The first problem I encountered was caused by the dependency on rand. Generating
random numbers doesn’t necessarily require OS support, but you need to generate a
seed some way or another. For this reason, rand relies on an OsRng struct that
is platform-dependent. Guess what… WebAssembly didn’t had such a struct, so the
crate could not be compiled.

Fortunately, the problem was easily solved by adding such a struct.
After patching the crate, the code finally compiled… but it didn’t run in the
browser.

By the way, you are probably wondering about the seeding problem. If there is no way
to communicate with the outside world from your WebAssembly programs, how can you
get a seed? Below I will describe how you can call Javascript functions from Rust,
which could be a solution to the problem. However, I decided to use a constant seed,
which is clearly not optimal, but is good enough for a playable demo.

Getting things to run: link errors

I mentioned in the paragraph above that the resulting program didn’t run on the browser.
Concretely, after following the instructions on hellorust.com, I got the
following error:

TypeError: import object field 'env' is not an Object

After looking around for a while, this turned out to be a linking problem. In other words,
the generated Rust code contained calls to functions that didn’t exist. Therefore, the
browser expected me to pass an import object containing said functions. It seems that
some f64 functions I used in the physics part of the game have no analogous on
WebAssembly, so I had to pass them explicitly from Javascript through the following object:

letimports={env:{Math_atan:Math.atan,sin:Math.sin,cos:Math.cos}};

After this, the code compiled and could be loaded on the browser, though without
any kind of visual feedback. Rust running on the browser! Finally.

Making the game actually playable

Rendering

At this point I discovered that you could call Javascript functions from within the
Rust program. This follows the same principle as using C functions from a library.
On the Rust side, you need to declare the function as extern. On the Javascript
side, you need to add the function to the imports, so it can be linked.

This means we can define drawing functions on the Javascript side and call them from
Rust. Even though WebAssembly itself cannot interact with the outside world, it can
still call Javascript functions you explicitly pass through the imports object.
This will be our escape hatch to render the game to a canvas

Rendering things to the screen was as easy as adding a bunch of functions to my program:

Of course, these functions had to be implemented on the Javascript side. You can find
them on the source code of the demo. You won’t find any surprises there, as the only
thing they do is drawing to a canvas.

With these extern functions in place, I could implement the rest of the drawing code
in Rust as shown below:

Again, if you compare this code to the original version,
you will see that they are strikingly similar.

Processing user input

With simulation and rendering in place, enabling user input was almost trivial.
First of all, I added a bunch of functions to toggle user actions on and off.
Note that I am using a Rust type as a parameter of each function. This is
technically incorrect, but I am not sure about which type I should use instead.
If you do, please open a PR so it can be fixed.

In this case, the code did differ considerably from the original version,
since the latter relies on the piston_window::Key struct, which no longer exists. In the wasm
version, I moved the key matching logic to Javascript, since I didn’t want to pass strings between
Javascript and Rust. The resulting code is straightforward:

Conclusion

Even though the wasm32-unknown-unknown target is quite new, it clearly has a
lot of potential. I am impressed by the fact that I was able to port Rocket
with almost no modifications to the game logic code. In the end, I ended up
spending most of the time dealing with rendering and figuring out how to correctly
set up the integration between Javascript and Rust.