WebAssembly (frequently abbreviated as Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of higher-level languages such as C/C++/Rust, enabling deployment on the web for client and server applications.

Code compiled to run in WebAssembly creates a module. The WebAssembly module can be loaded by a JavaScript engine which in turn, calls into the Wasm module to perform computation.

One of the main features of WebAssembly is high performance computation. Implementing CPU intensive operations in a statically typed language that can be compiled into WebAssembly enables higher performance web applications for some application use cases. Similarly, existing code can be ported across to run in a web browser with memory safety and high performance.

What you will build

In this codelab you will build a web application that shows an hourglass built with SVG graphics. Your app will:

  • Create some DOM nodes to represent grains of sand.
  • Interface to a physics engine compiled from C into WebAssembly.
  • Set up the physics engine from geometry created in Javascript.
  • Dynamically update the positions of the grains of sand by calling into the Wasm physics engine.

What you'll learn

What you'll need

This codelab is focused on WebAssembly. Non-relevant concepts and code blocks are glossed over and are provided for you to simply copy and paste.

Download the Code

Click the following link to download all the code for this codelab:

Download source code

Unpack the downloaded zip file. This will unpack a root folder (chipmunk-hourglass), which contains one folder for each step of this codelab, along with all of the resources you will need.

The step-NN folders contain the desired end state of each step of this codelab. They are there for reference. We'll be doing all our coding work in a directory called work. There is a directory called solution which contains the finished project.

Install and verify web server

While you're free to use your own web server, this codelab is designed to work well with the Chrome Web Server. If you don't have that app installed yet, you can install it from the Chrome Web Store.

Install Web Server for Chrome

After installing the Web Server for Chrome app, click on the Apps shortcut on the bookmarks bar:

In the ensuing window, click on the Web Server icon:

You'll see this dialog next, which allows you to configure your local web server:

Click the choose folder button, and select the work folder. This will enable you to serve your work in progress via the URL highlighted in the web server dialog (in the Web Server URL(s) section).

Now visit your work site in your web browser at this URL http://localhost:8887/hourglass.svg and you should see a page that looks like this:

This is showing the skeleton graphic that'll be the host for our dynamic content. If you're curious to look at the source content, open up the file hourglass.svg from your work folder. Looking inside, you'll see that it's simply a bunch of elements in the SVG namespace, which of course means it'll scale nicely on any window size.

Define our containing shape

In this codelab, we'll explore manipulating DOM objects in the browser from calculations made in a Wasm module. The example used is an hourglass, where the grains of sand in the hourglass are circles as SVG DOM nodes.

Our source graphic has a bunch of DOM elements but no outline of the interior of the hourglass. So the first thing to do is define a shape that will contain our grains of sand for the physical simulation.

Since SVG has the notion of 'user space units', we should be able to define a shape that will scale alongside the source graphic at any scale.

Open up the hourglass.svg file in your favourite text editor.

At the end of the file, you'll see two closing tags that look like this:

 </g>
</svg>

Replace those lines with our containing shape using the following code:

</g>
  <polyline stroke-width="1" fill="none" stroke="red" points="
    105,89
    276,89
    284,100
    288,127
    285,151
    273,176
    249,204
    198,247
    198,258
    250,308
    271,334
    284,358
    287,379
    285,403
    96,403
    98,375
    107,345
    128,315
    186,257
    186,247
    127,193
    114,180
    105,165
    98,141
    97,111
    104,89
    "/>
</svg>

Test it out

Now that you've added the container shape, reloading hourglass.svg should display the original graphic with a red outline overlayed that will be the container for our physical objects (grains of sand).

You should see the image below:

We'll use the coordinates of the red polygon to act as the container for our grains of sand to fall through.

Notice that the coordinates of the polygon we just added are in the user space coordinate system of the SVG graphic, which means they are scale-independent so we can use them to send to the physics engine and expect the returned values to be scale independent (work no matter what size we rescale the application window to).

The positions of the grains of sand are managed by a physics engine, in this case Chipmunk2D which is compiled into Wasm.

When the timer is eventually started, the positions of the grains of sand are controlled by the physics engine calculations inside the Wasm module.

Building Chipmunk2D

First thing we need to do is build the physics engine library.

Note, the library itself is open source and lives at https://github.com/slembcke/Chipmunk2D.

The source repository contains build files for a number of different platforms, but doesn't have a standard ./configure and make step. Conveniently it does come with a Cmake file which we can look at as a basis to build our own Makefile that we can then use to compile it into a Wasm module.

The CMakeLists.txt files at the top level (Chipmunk2D) and src directories show that we just need to include all the C files from the src directory in our compile.

We'll be using a compiler toolchain SDK that's called emscripten. Full documentation for the toolchain and it's features can be found here: http://kripken.github.io/emscripten-site/

If you're at Google I/O 2018, then you can just open a terminal and run:

source ~/emsdk/emsdk_env.sh

That command will set up the environment variables in your terminal so that you can use the emscripten toolchain.

If you're not at I/O 2018, then you should install the emscripten SDK by following these instructions.

The emscripten C/C++ compiler is called emcc.

We've pre-constructed a Makefile based on the Chipmunk2D build files that you will find in the work directory.

It contains this:

CHIP_DIR=Chipmunk2D
CHIP_SRC=$(CHIP_DIR)/src
CHIP_INC=$(CHIP_DIR)/include

CHIP_SRCS=\
    $(CHIP_SRC)/chipmunk.c $(CHIP_SRC)/cpArbiter.c $(CHIP_SRC)/cpArray.c \
    $(CHIP_SRC)/cpBBTree.c $(CHIP_SRC)/cpBody.c $(CHIP_SRC)/cpCollision.c \
    $(CHIP_SRC)/cpConstraint.c $(CHIP_SRC)/cpDampedRotarySpring.c \
    $(CHIP_SRC)/cpDampedSpring.c $(CHIP_SRC)/cpGearJoint.c \
    $(CHIP_SRC)/cpGrooveJoint.c $(CHIP_SRC)/cpHashSet.c \
    $(CHIP_SRC)/cpHastySpace.c $(CHIP_SRC)/cpMarch.c $(CHIP_SRC)/cpPinJoint.c \
    $(CHIP_SRC)/cpPivotJoint.c $(CHIP_SRC)/cpPolyShape.c \
    $(CHIP_SRC)/cpPolyline.c $(CHIP_SRC)/cpRatchetJoint.c \
    $(CHIP_SRC)/cpRobust.c $(CHIP_SRC)/cpRotaryLimitJoint.c \
    $(CHIP_SRC)/cpShape.c $(CHIP_SRC)/cpSimpleMotor.c \
    $(CHIP_SRC)/cpSlideJoint.c $(CHIP_SRC)/cpSpace.c \
    $(CHIP_SRC)/cpSpaceComponent.c $(CHIP_SRC)/cpSpaceDebug.c \
    $(CHIP_SRC)/cpSpaceHash.c $(CHIP_SRC)/cpSpaceQuery.c \
    $(CHIP_SRC)/cpSpaceStep.c $(CHIP_SRC)/cpSpatialIndex.c \
    $(CHIP_SRC)/cpSweep1D.c

SRCS=$(CHIP_SRCS) bridge.c

TARGET=chipmunk.js
TARGETS=$(TARGET) chipmunk.wasm

all: chipmunk.js

chipmunk.js: $(SRCS)
                # Compile commands go here...

Note the line:

SRCS=$(CHIP_SRCS) bridge.c

That contains reference to all the Chipmunk2D source files plus another file called bridge.c. The file bridge.c will be our glue code that interfaces Javascript to the physics library.

Open up the file bridge.c in your text editor. You'll edit this to build the interface between the Javascript calls into our Wasm module and the Chipmunk2D physics engine.

Add the following code to bridge.c:

/*
 *  Bridge from JS into Chipmunk2D in Wasm
 */
#include <emscripten.h>
#include <chipmunk/chipmunk.h>
#include <stdlib.h>
#include <memory.h>

/**
 *  A struct that manages our instantiation of
 *  the Chipmunk2D physics engine
 */
typedef struct {
    cpSpace  *cmi_Space;      /**< Holds instance of Chipmunk2D */
    float    cmi_Location[3]; /**< Stores x, y, rotation        */
} CM_Instance;

/*
 *  This is our interface from the Wasm world out into
 *  the JS world when using 'C'
 */

/**
 *  Constructor for the Chipmunk2D engine
 */
CM_Instance *
CM_Instance_new() {
    CM_Instance     *cm;

    cm = malloc(sizeof *cm);
    if (cm) {
        memset(cm, 0, sizeof *cm);
    }
    // cpVect is a 2D vector and cpv() is a shortcut
    // for initializing them.
    cpVect gravity = cpv(0, 50);
  
    // Create an empty space.
    cm->cmi_Space = cpSpaceNew();
    cpSpaceSetGravity(cm->cmi_Space, gravity);

    return cm;
}

/**
 *  Destructor for when we're all done (frees everything).
 */
void
CM_Instance_destroy(CM_Instance *cmi) {
    if (cmi != NULL) {
        cpSpaceFree(cmi->cmi_Space);

        free(cmi);
    }
}

Save the file bridge.c once you've added the lines above.

Now we should be able to compile our physics engine and glue code by making sure we're in the work directory and typing the following command in a shell:

make

After running make you should find 2 new files in the work directory - chipmunk.js and chipmunk.wasm.

The file chipmunk.wasm is the Chipmunk2D physics library compiled into a Wasm module.

The file chipmunk.js is emscripten generated glue code that can take care of loading the Wasm module and managing the interface to it.

Next, we need to invoke the loading of the Wasm module from our web application.

In the file hourglass.svg, add the following lines as the first child of the root SVG element (just before the <defs> element:

    <script xlink:href="chipmunk.js"></script>
    <script><![CDATA[
        var wasm_loaded = false;
        var cm_instance;

        Module.onRuntimeInitialized = function() {
            wasm_loaded = true;
            cm_instance = Module._CM_Instance_new();
        }

    ]]></script>

The Module.onRuntimeInitialized property can be set to a function that gets called after the Wasm module is loaded, in this case it will create an instance of the Chipmunk2D physics library.

Try it out

Reload the file hourglass.svg and you should see no change.

Try opening up the debugger console then reload and you should see some debug output telling you the Wasm module has been loaded, and a message from Chipmunk2D telling you to recompile with debugging disabled by adding the -DNDEBUG flag.

Now that we have our physics engine loaded, we need 2 things:

  1. An interface to create boundaries for our grains of sand to bounce off; and
  2. Create the boundary of the physical space from the Javascript side.

For the boundary creation interface, open up the bridge.c file and add this function:

/**
 *  Add a static wall to our space.
 *  This is for containing the balls as they're dropping
 *  through the space.
 *
 *  @return
 *      Pointer to the cpBody created, 0 on error.
 */
void
CM_Add_wall
(
    CM_Instance     *cmi,/**< Our Chimpunk2D instance             */
    int             id,  /**< An id to map to our DOM node in JS  */
    int             x1,
    int             y1,
    int             x2,
    int             y2
) {
    cpVect          p1, p2;
    cpShape        *segment;

    p1.x = x1;
    p1.y = y1;
    p2.x = x2;
    p2.y = y2;
    segment = cpSegmentShapeNew(cpSpaceGetStaticBody(cmi->cmi_Space),
                                p1, p2, 1.5);
    cpShapeSetFriction(segment, 1);
    cpSpaceAddShape(cmi->cmi_Space, segment);
}

This function lets us add fixed surfaces inside our physical model, but now we need to call those from Javascript to create the containing shape.

Recall that we added the red outlined polygon defining our container earlier. What we'll do it take those coordinates and pass them through to the physics engine using our new API function.

Edit the hourglass.svg file and add this code inside the <script> element:

        const interior = [
                            105,89,
                            276,89,
                            284,100,
                            288,127,
                            285,151,
                            273,176,
                            249,204,
                            198,247,
                            198,258,
                            250,308,
                            271,334,
                            284,358,
                            287,379,
                            285,403,
                            96,403,
                            98,375,
                            107,345,
                            128,315,
                            186,257,
                            186,247,
                            127,193,
                            114,180,
                            105,165,
                            98,141,
                            97,111,
                            105,89
                          ];

        function build_world() {
          // Create the walls of the interior of the hourglass shape
          for (var i = 0; i < interior.length - 2; i += 2) {
            Module._CM_Add_wall(cm_instance, 0, interior[i],
                                interior[i + 1], interior[i + 2],
                                interior[i + 3]);
          }
        }

Call the geometry generator when the Wasm module loads

We need to make sure that we call into the Wasm module after it's finished loading.

To do so, replace the onRuntimeInitialized function with this code:

        Module.onRuntimeInitialized = function() {
            wasm_loaded = true;
            cm_instance = Module._CM_Instance_new();
            build_world();
        }

This ensures that the build_world() function will only get called after the Wasm module is loaded.

Create some sand

Now that we have a container, we need to generate some grains of sand to fall through the hourglass.

We'll be building those with DOM nodes - in this case using the <circle> element from SVG.

To create the sand, first lets edit bridge.c and add a function to create circular objects in the physics engine by adding this code:

/**
 *  Add a circular dynamic object to our space.
 *  This is for adding grains of sand
 *
 *  @return
 *      Pointer to the cpBody created, 0 on error.
 */
cpBody *
CM_Add_circle
(
    CM_Instance     *cmi,  /**< Our Chimpunk2D instance              */
    int             id,    /**< Id to map back to our DOM node in JS */
    float           x,     /**< Starting 'x' position of the circle  */
    float           y,     /**< Starting 'y' position of the circle  */
    float           radius /**< Radius of the circle                 */
) {
    static cpFloat  mass = 1;
    cpFloat         moment = cpMomentForCircle(mass, 0, radius,
                                               cpvzero);
    cpBody         *ballBody = cpSpaceAddBody(cmi->cmi_Space,
                                              cpBodyNew(mass, moment));
    cpShape        *ballShape;
  
    // Set the position of the circle
    cpBodySetPosition(ballBody, cpv(x, y));
    // Add the collision shape to the circle
    ballShape = cpSpaceAddShape(cmi->cmi_Space,
                                cpCircleShapeNew(ballBody, radius,
                                                 cpvzero));
    cpShapeSetElasticity(ballShape, 0.0);
    cpShapeSetFriction(ballShape, 0.0);

    return ballBody;
}

To create a container for our grains of sand, edit hourglass.svg and add these lines at the end of the file just before the closing </svg> tag:

  <g id="bottle" style="fill: url(#ballGrad)">
  </g>

That <g> element will hold all of our grains of sand once we generate them using Javascript.

Now edit the hourglass.svg file and add a function to create some DOM nodes to represent the sand by adding this code inside our <script> element:

        const cols = 41;
        const rows = 18;
        const startx = 109;
        const starty = 92;
        const grains = cols * rows;
        const SVGNS = "http://www.w3.org/2000/svg";
        function generate_sand() {
          var ball, ballNode;
          var bottle = document.getElementById("bottle");
          var ident;
          x = startx;
          y = starty;
          for (var j = 0; j < rows; j++) {
            for (var i = 0; i < cols; i++) {
              ident = (j * cols) + i;
              ball = Module._CM_Add_circle(cm_instance, (i * cols) + j,
                                           x, y, 1.8);
              ballNode = document.createElementNS(SVGNS, "circle");
              ballNode.setAttribute("id", "ball" + ident);
              ballNode.setAttribute("cx", x);
              ballNode.setAttribute("cy", y);
              ballNode.setAttribute("r", "2");
              ballNode.setAttribute("physref", ball); // Store bodyref
              bottle.appendChild(ballNode);
              x += 4;
            }
            x = startx;
            y += 4;
          }
        }

Finally we need to actually call the generate_sand() function to dynamically create the <circle> nodes that represent our sand.

To do so, replace the build_world() function in hourglass.svg with the following code:

 function build_world() {
          // Create the walls of the interior of the hourglass shape
          for (var i = 0; i < interior.length - 2; i += 2) {
            Module._CM_Add_wall(cm_instance, 0, interior[i],
                                interior[i + 1], interior[i + 2],
                                interior[i + 3]);
          }
          generate_sand();
    }

Try it out

Recompile the Wasm module so that the sand creation function is built in, using the command line:

make

Now reload the web page and you should see a whole lot of circles representing sand at the top of the hourglass. It should look similar to this:

Now that we've generated a bunch of DOM nodes from script, and connected our physics engine it's time to drive the physics engine to create a dynamic animated experience.

For this we'll use requestAnimationFrame to trigger recalculation of the positions of all our grains of sand and update the display.

To do so, we need to add an interface function to drive the physics engine by editing bridge.c and adding this function:

/**
 *  Step the space.
 *  This runs one tick of the simulation, and updates all our 
 *  object positions.
 */
void
CM_Step(CM_Instance *cmi) {
    static cpFloat timeStep = 1.0 / 60.0;

    cpSpaceStep(cmi->cmi_Space, timeStep);
}

We also need to find out where the grains of sand have moved to, so add this function as well:

/**
 *  Get the location of an object in our space.
 *
 *  @return
 *      An array of 3 floats - x, y position and rotation
 */
float *
CM_Get_location(CM_Instance *cmi, cpBody *what) {
    cpVect  pos = cpBodyGetPosition(what);
    cpFloat rot = cpBodyGetAngularVelocity(what);
    cmi->cmi_Location[0] = pos.x;
    cmi->cmi_Location[1] = pos.y;
    cmi->cmi_Location[2] = rot;

    return cmi->cmi_Location;
}

Finally, let's activate the physics engine from JavaScript to run a tick of the physics engine each time we get a requestAnimationFrame callback and update the positions of the DOM nodes that represent our grains of sand by adding this code to hourglass.svg:

        function handle_tick() {
          // Run a tick of the simulation, update sand positions
          Module._CM_Step(cm_instance);
          for (var i = 0; i < grains; i++) {
            var ident = "ball" + i;
            var ballNode = document.getElementById(ident);
            var ball = ballNode.getAttribute("physref");
            var location = Module._CM_Get_location(cm_instance, ball);
            // Create a TypedArray view from the Wasm pointer value
            var pos = new Float32Array(Module.HEAPU8.buffer, location, 3);
            var val = ballNode.getAttribute("cx");
            ballNode.setAttribute("cx", pos[0]);
            ballNode.setAttribute("cy", pos[1]);
          }
        }

        function setup() {
            function update() {
                // Do stuff with Chipmunk2D
                if (wasm_loaded) {
                  handle_tick();
                }
                requestAnimationFrame(update);
            }
            requestAnimationFrame(update);
        }
        window.onload = setup;

Try it out

Recompile the Wasm module from the command line with:

make

Now reload hourglass.svg and you should see grains of sand falling through the hourglass.

Congratulations!

You've built a web application that dynamically modifies DOM node content from a compiled Wasm module.

We still have a red polygon outline that isn't needed for the graphic. Also, the generated grains of sand start as a grid which doesn't look very realistic. Let's fix it.

When we first created the containing polygon points, we added this code at the end of hourglass.svg:

  <polyline stroke-width="1" fill="none" stroke="red" points="
    105,89
    276,89
    284,100
    288,127
    285,151
    273,176
    249,204
    198,247
    198,258
    250,308
    271,334
    284,358
    287,379
    285,403
    96,403
    98,375
    107,345
    128,315
    186,257
    186,247
    127,193
    114,180
    105,165
    98,141
    97,111
    104,89
    "/>

Delete it, since we no longer need to show the container.

To make the grains of sand look more realistic we could do a couple of things:

  1. We could work out a nice arrangement of positions and code them into our initialization code; or
  2. We could take advantage of our physics engine to reposition everything for us instead.

Of course we'll pick (2) since all good engineers let the machine do the hard work!

To reposition the sand at start up time we'll place the grains as before but invert gravity and run the physical simulation for a few cycles to force the grains to levitate upwards into a natural cluster before starting the simulation.

To do so, first we need to edit bridge.c to add a function to change the gravity in our physics simulation by adding this code:

/**
 *  Set the gravity vector
 */
void
CM_Set_gravity(CM_Instance *cmi, float x, float y) {
    cpVect gravity = cpv(x, y);
    cpSpaceSetGravity(cmi->cmi_Space, gravity);
}

Now to force the grains of sand to levitate into a nice holding position before we run the simulation, edit hourglass.svg and change the build_world() function to this:

        function build_world() {
          // Create the walls of the interior of the hourglass shape
          for (var i = 0; i < interior.length - 2; i += 2) {
            Module._CM_Add_wall(cm_instance, 0, interior[i],
                                interior[i + 1], interior[i + 2],
                                interior[i + 3]);
          }
          generate_sand();
          // Invert gravity and run for a few ticks to clump the sand
          // at the top of the hourglass before starting
          Module._CM_Set_gravity(cm_instance, 2, -50);
          for (var i = 0; i < 200; i++) {
            Module._CM_Step(cm_instance);
          }
          Module._CM_Set_gravity(cm_instance, 0, 50);
        }

Try it out

Recompile the Wasm module from the command line using:

make

Then reload hourglass.svg. You will see a small delay on reload which is the physics engine clumping the grains of sand at the top of the hourglass before they all start falling nicely.

Let's shrink this thing

Earlier we saw that the generated Javascript and Wasm file were larger than we'd like - and the console warning from Chipmunk2D gave us a hint of what we should do.

We can greatly improve the size of the generated output by listening to what the library tells us, but more importantly using a great feature in the Emscripten toolchain which is an optimization flag -Os. That flag analyzes the function calls across Javascript and Wasm before the final link step and eliminates code that isn't called to minimize the resultant size.

To try out these improvements, load 'Makefile' in your text editor and replace the line:

                -I$(CHIP_INC) \

With this:

                -I$(CHIP_INC) -Os -DNDEBUG \

Delete the existing output from the command line with:

make clean

Then recompile from the command line and compare the size reduction in the generated Javascript and Wasm files.

make
ls -l

Make it more useful

By now you're probably hoping to be able to time your cooking or something equally important, so surely it should be possible to flip the timer to restart it?

Of course it is - courtesy of the gravity function we built and a bit of animation. So let's try.

Edit hourglass.svg and add these elements as direct children of the root <svg> element:

    <animateTransform id="flip" attributeName="transform" attributeType="XML" type="rotate" from="0" to="180" begin="undefined" dur="1s" fill="freeze"/>
    <animateTransform id="unflip" attributeName="transform" attributeType="XML" type="rotate" from="180" to="0" begin="undefined" dur="1s" fill="freeze"/>

Then inside the <script> element, add a click handler by adding these lines:

        var flipped = 0;
        function handle_click(evt) {
          if (flipped) {
            document.getElementById('unflip').beginElement();
            Module._CM_Set_gravity(cm_instance, 0, 50);
          }
          else {
            document.getElementById('flip').beginElement();
            Module._CM_Set_gravity(cm_instance, 0, -50);
          }
          flipped = !flipped;
        }
        document.getElementById('hourglass').addEventListener('click',
                                handle_click);

Try it out

Reload hourglass.svg - each time you click the hourglass should flip over, changing gravity in the process and restarting the simulation.

Congratulations again! - you've built a dynamically modifiable web application connected to a physics engine written in C.

Read More

If you'd like to explore more about WebAssembly, here are a few resources to help: