Skip to content

Commit

Permalink
lab wasm du jugsummercamp 2020
Browse files Browse the repository at this point in the history
  • Loading branch information
tercoli committed Sep 13, 2020
0 parents commit 3112aee
Show file tree
Hide file tree
Showing 19 changed files with 695 additions and 0 deletions.
1 change: 1 addition & 0 deletions GameOfLife/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty AssemblyScript Project
77 changes: 77 additions & 0 deletions GameOfLife/assembly/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// The Game of Life, also known simply as Life, is a
// cellular automaton devised by the British
// mathematician John Horton Conway in 1970.
//
// https://en.wikipedia.org/wiki/Conway's_Game_of_Life

let width: i32;
let height: i32;
let size: i32;

/** Initializes width and height. Called once from JS. */
export function init(inputWidth: i32, inputHeight: i32): void {
width = inputWidth;
height = inputHeight;
size = width * height;
}

/** Performs one step. Called about 30 times a second from JS. */
export function step(): void {
// The universe of the Game of Life is an infinite two-dimensional
// orthogonal grid of square "cells", each of which is in one
// of two possible states, alive or dead.

for (let row = 0; row < height; ++row) {
// Create the torus ilusion. Top and bottom are connected.
let rowMinus1 = row == 0 ? height - 1 : row - 1;
let rowPlus1 = row == height - 1 ? 0 : row + 1;

for (let column = 0; column < width; ++column) {
// Create the torus ilusion. Left and right are connected.
let columnMinus1 = column == 0 ? width - 1 : column - 1;
let columnPlus1 = column == width - 1 ? 0 : column + 1;

// Every cell interacts with its eight neighbours,
// which are the cells that are horizontally,
// vertically, or diagonally adjacent:
let aliveNeighbors =
load<u8>(rowMinus1 * width + columnMinus1) +
load<u8>(rowMinus1 * width + column) +
load<u8>(rowMinus1 * width + columnPlus1) +
load<u8>(row * width + columnMinus1) +
load<u8>(row * width + columnPlus1) +
load<u8>(rowPlus1 * width + columnMinus1) +
load<u8>(rowPlus1 * width + column) +
load<u8>(rowPlus1 * width + columnPlus1);

let alive = load<u8>(row * width + column);
if (alive) {
switch (aliveNeighbors) {
// A live cell with fewer than 2 live neighbors dies, as if caused by underpopulation.
// A live cell with more than 3 live neighbors dies, as if by overpopulation.
default: {
store<u8>(size + row * width + column, 0);
break;
}
// A live cell with 2 or 3 live neighbors lives on to the next generation.
case 2:
case 3:
}
} else {
switch (aliveNeighbors) {
// A dead cell with exactly 3 live neighbors becomes a live cell, as if by reproduction.
case 3: {
store<u8>(size + row * width + column, 1);
break;
}
default:
}
}
}
}
}

// Performing a step uses bytes [0, size - 1] as the input
// and writes the output to [size, 2 * size - 1].

// Note that the code above wastes a lot of space by using one byte per cell.
6 changes: 6 additions & 0 deletions GameOfLife/assembly/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "../node_modules/assemblyscript/std/assembly.json",
"include": [
"./**/*.ts"
]
}
50 changes: 50 additions & 0 deletions GameOfLife/gulpfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const gulp = require("gulp");
/*
Runtime variants:
"--runtime", "full" (default)
A proper memory manager and reference-counting based garbage collector, with runtime interfaces
being exported to the host for being able to create managed objects externally.
"--runtime", "half"
The same as full but without any exports, i.e. where creating objects externally is not required.
This allows the optimizer to eliminate parts of the runtime that are not needed.
"--runtime", "stub"
A minimalist arena memory manager without any means of freeing up memory again, but the same external
interface as full. Useful for very short-lived programs or programs with hardly any memory footprint,
while keeping the option to switch to full without any further changes. No garbage collection.
"--runtime", "none"
The same as stub but without any exports, for the same reasons as explained in half. Essentially
evaporates entirely after optimizations.
For more information see: https://docs.assemblyscript.org/details/runtime
*/
gulp.task("build", callback => {
const asc = require("assemblyscript/bin/asc");
asc.main([
"main.ts",
"--baseDir", "assembly",
"--binaryFile", "../out/main.wasm",
"--sourceMap",
"--measure",
"--runtime", "half",
"--optimize",
"--importMemory"
], callback);
});

gulp.task("default", ["build"]);

// This task is not required when running the project locally. Its purpose is to set up the
// AssemblyScript compiler when a new project has been loaded in WebAssembly Studio.
gulp.task("project:load", () => {
const utils = require("@wasm/studio-utils");
utils.eval(utils.project.getFile("setup.js").getData(), {
logLn,
project,
monaco,
fileTypeForExtension,
});
});
Binary file added GameOfLife/out/main.wasm
Binary file not shown.
1 change: 1 addition & 0 deletions GameOfLife/out/main.wasm.map

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions GameOfLife/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@wasm/empty_ts",
"description": "",
"version": "1.0.0",
"scripts": {
"build": "gulp"
},
"devDependencies": {
"assemblyscript": "AssemblyScript/assemblyscript",
"gulp": "^3"
},
"wasmStudio": {
"name": "Empty AssemblyScript Project",
"description": "# Empty AssemblyScript Project\n\n[AssemblyScript](https://github.com/AssemblyScript/assemblyscript) compiles strictly typed TypeScript to WebAssembly using Binaryen.\n\nSee the [AssemblyScript wiki](https://github.com/AssemblyScript/assemblyscript/wiki) for further instructions and documentation.",
"icon": "typescript-lang-file-icon"
}
}
34 changes: 34 additions & 0 deletions GameOfLife/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This file is not required when running the project locally. Its purpose is to set up the
// AssemblyScript compiler when a new project has been loaded in WebAssembly Studio.
require.config({
paths: {
"binaryen": "https://cdn.jsdelivr.net/npm/binaryen@latest/index",
"assemblyscript": "https://cdn.jsdelivr.net/npm/assemblyscript@latest/dist/assemblyscript",
"assemblyscript/bin/asc": "https://cdn.jsdelivr.net/npm/assemblyscript@latest/dist/asc",
}
});
logLn("Loading AssemblyScript compiler ...");
require(["assemblyscript/bin/asc"], asc => {
monaco.languages.typescript.typescriptDefaults.addExtraLib(asc.definitionFiles.assembly);
asc.main = (main => (args, options, fn) => {
if (typeof options === "function") {
fn = options;
options = undefined;
}
return main(args, options || {
stdout: asc.createMemoryStream(),
stderr: asc.createMemoryStream(logLn),
readFile: (filename, baseDir) => {
const file = project.getFile(baseDir + "/" + filename.replace(/^\//, ""));
return file ? file.data : null;
},
writeFile: (filename, contents) => {
const name = filename.startsWith("../") ? filename.substring(3) : filename;
const type = fileTypeForExtension(name.substring(name.lastIndexOf(".") + 1));
project.newFile(name, type, true).setData(contents);
},
listFiles: () => []
}, fn);
})(asc.main);
logLn("AssemblyScript compiler is ready!");
});
24 changes: 24 additions & 0 deletions GameOfLife/src/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<title>JUG SummerCamp 2020 WebAssembly Codelab - Game of Life</title>
<style>
html,
body {
height: 100%;
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
</style>
</head>
<body style="background: #fff">
<canvas id="canvas"></canvas>
<script src="./main.js"></script>
</body>
</html>
79 changes: 79 additions & 0 deletions GameOfLife/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Set up the canvas with a 2D rendering context
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const boundingClientRect = canvas.getBoundingClientRect();
canvas.width = boundingClientRect.width | 0;
canvas.height = boundingClientRect.height | 0;

const width = boundingClientRect.width >>> 1; // == boundingClientRect.width / 2
const height = boundingClientRect.height >>> 1; // == boundingClientRect.height /2
const size = width * height; // memory required to store either input or output
const totalMemoryRequired = size + size; // total memory required to store input and output

// Compute the size of and instantiate the module's memory
const numberPages = ((totalMemoryRequired + 0xffff) & ~0xffff) >>> 16; // aligned up in 64k units
const wasmMemory = new WebAssembly.Memory({ initial: numberPages });







// Executed when the WASM module is instantiated
function initGame(module) {
const exports = module.instance.exports;

// Tell the module about the universe's width and height
exports.init(width, height);

// Fill input at [0, s-1] with random live cells
const memory = new Uint8Array(wasmMemory.buffer);
for (let y = 0; y < height; ++y)
for (let x = 0; x < width; ++x)
memory[y * width + x] = Math.random() > 0.1 ? 0 : 1;

// Update about 30 times a second
const desiredFps = 30;
const frameDuration = 1000 / 30;
function update() {
// To be done
}
// Poorly optimised render function
// Easily bigger bottleneck than the actual module
function render() {
// To be done
}
update();
render();
}

function update() {
setTimeout(update, frameDuration);
exports.step();
// copy output at [size, totalMemoryRequired] to input at [0, size]
memory.copyWithin(0, size, totalMemoryRequired);
}

function render() {
requestAnimationFrame(render);

context.fillStyle = 'rgba(238,238,238,0.67)';
context.fillRect(0, 0, width << 1, height << 1);
context.fillStyle = '#333';

for (var y = 0; y < height; ++y)
for (var x = 0; x < width; ++x)
if (memory[size + y * width + x])
context.fillRect(x << 1, y << 1, 2, 2);
}

// Fetch and instantiate the module
async function loadAndInstantiate() {
let response = await fetch('../out/main.wasm');
let arrayBuffer = await response.arrayBuffer();
let wasmModule = await WebAssembly.instantiate(arrayBuffer,
{ env: { memory: wasmMemory } });
initGame(wasmModule);
}
loadAndInstantiate();
24 changes: 24 additions & 0 deletions HelloWebAssembly/HelloWebAssembly.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JUG SummerCamp 2020 WebAssembly Codelab - Hello WebAssembly</title>
</head>
<body>
<h1>JUG SummerCamp 2020 WebAssembly Codelab</h1>
<h2>Hello WebAssembly</h2>

<p>
We are using a `helloWebAssembly()`
function written in C, compiled into WASM
and loaded and instantiated via JS.
</p>

<p>
Look at the console, it it works you should see
a `Hello WebAssembly` message.
</p>
</body>

<script src='./HelloWebAssembly.js'></script>
</html>
23 changes: 23 additions & 0 deletions HelloWebAssembly/HelloWebAssembly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
let configurationObject = {
}

async function loadWASM() {
let response = await fetch('./HelloWebAssembly.wasm');
let arrayBuffer = await response.arrayBuffer();
let wasmModule = await WebAssembly.instantiate(arrayBuffer, configurationObject);
let helloWebAssembly = await wasmModule.instance.exports.helloWebAssembly;

let str = readWASMbuffer(wasmModule.instance, helloWebAssembly());
console.log(str);
}

function readWASMbuffer(wasmInstance, pointer) {
let buffer = new Int8Array(wasmInstance.exports.memory.buffer);
let str = "";
for (i = pointer; buffer[i]; i++) {
str += String.fromCharCode(buffer[i]);
}
return str;
}

loadWASM();
Binary file added HelloWebAssembly/HelloWebAssembly.wasm
Binary file not shown.
Loading

0 comments on commit 3112aee

Please sign in to comment.