diff --git a/game-of-life/asconfig.json b/game-of-life/asconfig.json index b5391b7..c5128c8 100644 --- a/game-of-life/asconfig.json +++ b/game-of-life/asconfig.json @@ -1,26 +1,23 @@ { + "options": { + "runtime": "stub", + "use": "Math=JSMath", + "importMemory": true, + "sourceMap": true, + "measure": true + }, "targets": { - "options": { - "runtime": "stub", - "sourceMap": true, - "measure": true - }, "debug": { - "outFile": "build/untouched.wasm", - "textFile": "build/untouched.wat", - "use": "Math=JSMath", - "importMemory": true, + "outFile": "build/debug.wasm", + "textFile": "build/debug.wat", "debug": true }, "release": { - "outFile": "build/optimized.wasm", - "textFile": "build/optimized.wat", - "use": "Math=JSMath", - "importMemory": true, + "outFile": "build/release.wasm", + "textFile": "build/release.wat", "optimizeLevel": 3, - "converge": false, "shrinkLevel": 0, - "noAssert": false + "noAssert": true } } } diff --git a/game-of-life/assembly/index.ts b/game-of-life/assembly/index.ts index ca6c93b..28f3245 100644 --- a/game-of-life/assembly/index.ts +++ b/game-of-life/assembly/index.ts @@ -6,18 +6,21 @@ import { BGR_ALIVE, BGR_DEAD, BIT_ROT } from "./config"; var width: i32, height: i32, offset: i32; /** Gets an input pixel in the range [0, s]. */ +// @ts-ignore: decorator @inline function get(x: u32, y: u32): u32 { return load((y * width + x) << 2); } /** Sets an output pixel in the range [s, 2*s]. */ +// @ts-ignore: decorator @inline function set(x: u32, y: u32, v: u32): void { store((offset + y * width + x) << 2, v); } /** Sets an output pixel in the range [s, 2*s] while fading it out. */ +// @ts-ignore: decorator @inline function rot(x: u32, y: u32, v: u32): void { var alpha = max((v >> 24) - BIT_ROT, 0); diff --git a/game-of-life/build/.gitignore b/game-of-life/build/.gitignore index 5eef771..4443273 100644 --- a/game-of-life/build/.gitignore +++ b/game-of-life/build/.gitignore @@ -1,3 +1,3 @@ * !.gitignore -!optimized.wasm +!release.wasm diff --git a/game-of-life/build/optimized.wasm b/game-of-life/build/release.wasm similarity index 90% rename from game-of-life/build/optimized.wasm rename to game-of-life/build/release.wasm index 5aa82bd..9461853 100644 Binary files a/game-of-life/build/optimized.wasm and b/game-of-life/build/release.wasm differ diff --git a/game-of-life/index.html b/game-of-life/index.html index 21631bb..17279dd 100644 --- a/game-of-life/index.html +++ b/game-of-life/index.html @@ -31,19 +31,19 @@

const BIT_ROT = 10; // Set up the canvas with a 2D rendering context -var cnv = document.getElementsByTagName("canvas")[0]; -var ctx = cnv.getContext("2d"); -var bcr = cnv.getBoundingClientRect(); +var canvas = document.getElementsByTagName("canvas")[0]; +var ctx = canvas.getContext("2d"); +var bcr = canvas.getBoundingClientRect(); // Compute the size of the universe (here: 2px per cell) -var width = bcr.width >>> 1; -var height = bcr.height >>> 1; -var size = width * height; -var byteSize = (size + size) << 2; // input & output (here: 4b per cell) +var width = bcr.width >>> 1; +var height = bcr.height >>> 1; +var size = width * height; +var byteSize = (2 * size) << 2; // input & output (here: 4b per cell) -cnv.width = width; -cnv.height = height; -cnv.style = ` +canvas.width = width; +canvas.height = height; +canvas.style = ` image-rendering: optimizeSpeed; image-rendering: -moz-crisp-edges; image-rendering: -webkit-optimize-contrast; @@ -56,21 +56,23 @@

ctx.imageSmoothingEnabled = false; // Compute the size of and instantiate the module's memory -var memory = new WebAssembly.Memory({ initial: ((byteSize + 0xffff) & ~0xffff) >>> 16 }); +var memory = new WebAssembly.Memory({ + initial: ((byteSize + 0xffff) & ~0xffff) >>> 16 +}); // Fetch and instantiate the module -fetch("build/optimized.wasm") +fetch("build/release.wasm") .then(response => response.arrayBuffer()) .then(buffer => WebAssembly.instantiate(buffer, { env: { memory, - abort: function() {}, + abort() {}, "Math.random": Math.random }, config: { - BGR_ALIVE : rgb2bgr(RGB_ALIVE) | 1, // little endian, LSB must be set - BGR_DEAD : rgb2bgr(RGB_DEAD) & ~1, // little endian, LSB must not be set - BIT_ROT + BIT_ROT, + BGR_ALIVE: rgb2bgr(RGB_ALIVE) | 1, // little endian, LSB must be set + BGR_DEAD: rgb2bgr(RGB_DEAD) & ~1, // little endian, LSB must not be set }, })) .then(module => { @@ -84,45 +86,52 @@

// Update about 30 times a second (function update() { setTimeout(update, 1000 / 30); - mem.copyWithin(0, size, size + size); // copy output to input - exports.step(); // perform the next step + mem.copyWithin(0, size, 2 * size); // copy output to input + exports.step(); // perform the next step })(); // Keep rendering the output at [size, 2*size] var imageData = ctx.createImageData(width, height); var argb = new Uint32Array(imageData.data.buffer); + (function render() { requestAnimationFrame(render); - argb.set(mem.subarray(size, size + size)); // copy output to image buffer - ctx.putImageData(imageData, 0, 0); // apply image buffer + argb.set(mem.subarray(size, 2 * size)); // copy output to image buffer + ctx.putImageData(imageData, 0, 0); // apply image buffer })(); // When clicked or dragged, fill the current row and column with random live cells - var down = false; - [ [cnv, "mousedown"], - [cnv, "touchstart"] - ].forEach(eh => eh[0].addEventListener(eh[1], e => down = true)); - [ [document, "mouseup"], - [document, "touchend"] - ].forEach(eh => eh[0].addEventListener(eh[1], e => down = false)); - [ [cnv, "mousemove"], - [cnv, "touchmove"], - [cnv, "mousedown"] - ].forEach(eh => eh[0].addEventListener(eh[1], e => { - if (!down) return; - var loc; - if (e.touches) { - if (e.touches.length > 1) return; - loc = e.touches[0]; - } else { - loc = e; - } - var bcr = cnv.getBoundingClientRect(); - exports.fill((loc.clientX - bcr.left) >>> 1, (loc.clientY - bcr.top) >>> 1, 0.5); - })); + let down = false; + for (const ty of ["mousedown", "touchstart"]) { + canvas.addEventListener(ty, e => down = true); + } + for (const ty of ["mouseup", "touchend"]) { + document.addEventListener(ty, e => down = false); + } + for (const ty of ["mousemove", "touchmove", "mousedown"]) { + canvas.addEventListener(ty, e => { + if (!down) return; + const touches = e.touches; + let loc; + if (touches) { + if (touches.length > 1) return; + loc = touches[0]; + } else { + loc = e; + } + const rect = canvas.getBoundingClientRect(); + exports.fill( + (loc.clientX - rect.left) >>> 1, + (loc.clientY - rect.top) >>> 1, + 0.5 + ); + }); + } // :-( - if (navigator.userAgent.indexOf(" Edge/") >= 0) document.getElementById("edge").style.display = "block"; + if (navigator.userAgent.includes(" Edge/")) { + document.getElementById("edge").style.display = "block"; + } }).catch(err => { alert("Failed to load WASM: " + err.message + " (ad blocker, maybe?)"); console.log(err.stack); diff --git a/game-of-life/package.json b/game-of-life/package.json index d124931..0b7d97c 100644 --- a/game-of-life/package.json +++ b/game-of-life/package.json @@ -4,9 +4,9 @@ "license": "Apache-2.0", "private": true, "scripts": { - "asbuild:untouched": "asc assembly/index.ts --target debug", - "asbuild:optimized": "asc assembly/index.ts --target release", - "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized", + "asbuild:debug": "asc assembly/index.ts --target debug", + "asbuild:release": "asc assembly/index.ts --target release", + "asbuild": "npm run asbuild:debug && npm run asbuild:release", "start": "npx serve" }, "devDependencies": {