1
Fork 0

Nice visualisation based on sandpiles

This commit is contained in:
Joshua Moerman 2020-12-17 20:40:36 +01:00
parent a37b87166c
commit a000624a7e
12 changed files with 13377 additions and 4 deletions

View file

@ -12,6 +12,7 @@ default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.63"
js-sys = "0.3"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
@ -32,3 +33,6 @@ wasm-bindgen-test = "0.3.13"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
[package.metadata.wasm-pack.profile.release]
wasm-opt = ["-Oz", "--enable-mutable-globals"]

24
README.md Normal file
View file

@ -0,0 +1,24 @@
rust-wasm
=========
Playground for rust stuff in the browser.
Currently it is a nice visualisation.
## Build
```
wasm-pack build
cd www
npm install
```
## Run
```
cd www
npm run start
```
This command automatically watches the files, so you can build
rust stuff and it will automatically appear.

View file

@ -1,6 +1,7 @@
mod utils;
use wasm_bindgen::prelude::*;
extern crate js_sys;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
@ -8,12 +9,115 @@ use wasm_bindgen::prelude::*;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
fn alert(s: &str);
pub struct Universe {
width: i32,
height: i32,
stress: i32,
cells: Vec<u8>,
base: Vec<u8>,
}
impl Universe {
fn get_index(&self, row: i32, col: i32) -> usize {
(row * self.width + col) as usize
}
fn in_bounds(&self, row: i32, col: i32) -> bool {
0 <= row && row < self.height && 0 <= col && col < self.width
}
fn incr_cell(&mut self, row: i32, col: i32) {
if !self.in_bounds(row, col) { return; }
let idx = self.get_index(row, col);
self.base[idx] += 1;
if self.base[idx] >= 4 {
self.cells[idx] = 100;
self.base[idx] = 0;
for dc in [-1, 1].iter() {
let col2 = col + dc;
self.incr_cell(row, col2);
}
for dr in [-1, 1].iter() {
let row2 = row + dr;
self.incr_cell(row2, col);
}
}
}
fn incr_random_cell(&mut self) {
let row = (js_sys::Math::random() * self.height as f64) as i32;
let col = (js_sys::Math::random() * self.width as f64) as i32;
let idx = self.get_index(row, col);
let count = self.base[idx];
if js_sys::Math::random() < 1.0 / (count + 1) as f64 {
self.incr_cell(row, col);
}
}
}
#[wasm_bindgen]
pub fn greet() {
alert("Hello, rust-wasm!");
impl Universe {
pub fn tick(&mut self) {
self.stress = 0;
for _i in 1..2 {
self.incr_random_cell();
}
for row in 0..self.height {
for col in 0..self.width {
let idx = self.get_index(row, col);
self.stress += self.base[idx] as i32;
if self.cells[idx] > 0 {
self.cells[idx] -= 1;
}
}
}
}
pub fn new() -> Universe {
let width = 180;
let height = 81;
let stress = 0;
let cells = (0..width * height)
.map(|_i| 0)
.collect();
let base = (0..width * height)
.map(|_i| 0)
.collect();
Universe {width, height, stress, cells, base}
}
pub fn init(&mut self) {
for _i in 1..50000 {
self.incr_random_cell();
}
}
pub fn width(&self) -> i32 {
self.width
}
pub fn height(&self) -> i32 {
self.height
}
pub fn stress(&self) -> i32 {
self.stress
}
pub fn cells(&self) -> *const u8 {
self.cells.as_ptr()
}
}

View file

@ -1,3 +1,4 @@
#[allow(dead_code)]
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then

24
www/.bin/create-wasm-app.js Executable file
View file

@ -0,0 +1,24 @@
#!/usr/bin/env node
const { spawn } = require("child_process");
const fs = require("fs");
let folderName = '.';
if (process.argv.length >= 3) {
folderName = process.argv[2];
if (!fs.existsSync(folderName)) {
fs.mkdirSync(folderName);
}
}
const clone = spawn("git", ["clone", "https://github.com/rustwasm/create-wasm-app.git", folderName]);
clone.on("close", code => {
if (code !== 0) {
console.error("cloning the template failed!")
process.exit(code);
} else {
console.log("🦀 Rust + 🕸 Wasm = ❤");
}
});

2
www/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
dist

5
www/bootstrap.js vendored Normal file
View file

@ -0,0 +1,5 @@
// A dependency graph that contains any wasm must all be imported
// asynchronously. This `bootstrap.js` file does the single async import, so
// that no one else needs to worry about it again.
import("./index.js")
.catch(e => console.error("Error importing `index.js`:", e));

27
www/index.html Normal file
View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello wasm-pack!</title>
<style>
body {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style>
</head>
<body>
<canvas id="game-of-life-canvas"></canvas>
<div style="text-align: center;">chaos: <span id="chaos"></span>, max: <span id="max"></span>, mv avg: <span id="avg"></span></div>
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
<script src="./bootstrap.js"></script>
</body>
</html>

94
www/index.js Normal file
View file

@ -0,0 +1,94 @@
import { Universe, Cell } from "rust-wasm";
import { memory } from "rust-wasm/rust_wasm_bg";
const CELL_SIZE = 5; // px
const GRID_COLOR = "#CCCCCC";
const COLOURS = ["#FFF", "#FF8", "#FF0", "#F80", "#F00", "#800", "#000"];
// Construct the universe, and get its width and height.
const universe = Universe.new();
universe.init();
const width = universe.width();
const height = universe.height();
// Give the canvas room for all of our cells and a 1px border
// around each of them.
const infochaos = document.getElementById("chaos");
const infomax = document.getElementById("max");
const infoavg = document.getElementById("avg");
var maxstress = 0;
var avgstress = 31000;
const canvas = document.getElementById("game-of-life-canvas");
canvas.height = (CELL_SIZE + 1) * height + 1;
canvas.width = (CELL_SIZE + 1) * width + 1;
const ctx = canvas.getContext('2d');
const renderLoop = () => {
universe.tick();
//drawGrid();
drawCells();
const st = universe.stress();
if (st > maxstress) maxstress = st;
avgstress = 0.999 * avgstress + 0.001 * st
infochaos.innerHTML = st;
infomax.innerHTML = maxstress;
infoavg.innerHTML = Math.round(avgstress);
// setTimeout(renderLoop, 200);
requestAnimationFrame(renderLoop);
};
// setTimeout(renderLoop, 1);
requestAnimationFrame(renderLoop);
const drawGrid = () => {
ctx.beginPath();
ctx.strokeStyle = GRID_COLOR;
// Vertical lines.
for (let i = 0; i <= width; i++) {
ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0);
ctx.lineTo(i * (CELL_SIZE + 1) + 1, (CELL_SIZE + 1) * height + 1);
}
// Horizontal lines.
for (let j = 0; j <= height; j++) {
ctx.moveTo(0, j * (CELL_SIZE + 1) + 1);
ctx.lineTo((CELL_SIZE + 1) * width + 1, j * (CELL_SIZE + 1) + 1);
}
ctx.stroke();
};
const getIndex = (row, column) => {
return row * width + column;
};
const drawCells = () => {
const cellsPtr = universe.cells();
const cells = new Uint8Array(memory.buffer, cellsPtr, width * height);
ctx.beginPath();
for (let row = 0; row < height; row++) {
for (let col = 0; col < width; col++) {
const idx = getIndex(row, col);
const rat = Math.round(COLOURS.length * cells[idx] / 101);
const colour = COLOURS[rat];
ctx.fillStyle = colour;
ctx.fillRect(
col * (CELL_SIZE + 1) + 1,
row * (CELL_SIZE + 1) + 1,
CELL_SIZE,
CELL_SIZE
);
}
}
ctx.stroke();
};

13051
www/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

23
www/package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "create-wasm-app",
"version": "0.1.0",
"description": "create an app to consume rust-generated wasm packages",
"main": "index.js",
"bin": {
"create-wasm-app": ".bin/create-wasm-app.js"
},
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server"
},
"dependencies": {
"rust-wasm": "file:../pkg"
},
"devDependencies": {
"copy-webpack-plugin": "^5.0.0",
"hello-wasm-pack": "^0.1.0",
"webpack": "^4.29.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5"
}
}

14
www/webpack.config.js Normal file
View file

@ -0,0 +1,14 @@
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require('path');
module.exports = {
entry: "./bootstrap.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bootstrap.js",
},
mode: "development",
plugins: [
new CopyWebpackPlugin(['index.html'])
],
};