├── tests
└── app.rs
├── .eslintignore
├── .gitignore
├── .husky
└── pre-commit
├── .prettierrc
├── .npmignore
├── publish.js
├── core
├── timer.rs
├── utils.rs
└── lib.rs
├── example
├── webpack.config.js
├── index.js
└── index.html
├── tsconfig.json
├── src
├── wasm.ts
└── index.ts
├── LICENSE
├── .eslintrc
├── Cargo.toml
├── rollup.config.js
├── README.md
├── package.json
└── Cargo.lock
/tests/app.rs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | pkg
4 | target
5 | example/build
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | target
4 | pkg
5 | wasm-pack.log
6 | example/build
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": false,
4 | "tabWidth": 2
5 | }
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | Cargo.*
2 | src/**.*
3 | **/*.rs
4 | tsconfig.json
5 | yarn.lock
6 | pkg
7 | target
8 | node_modules
9 | core
--------------------------------------------------------------------------------
/publish.js:
--------------------------------------------------------------------------------
1 | const ghpages = require("gh-pages");
2 |
3 | ghpages.publish("example/build", function (err) {
4 | if (err) throw err;
5 | console.log("✨ Published to Github!");
6 | });
7 |
--------------------------------------------------------------------------------
/core/timer.rs:
--------------------------------------------------------------------------------
1 | extern crate web_sys;
2 | use web_sys::console;
3 |
4 | pub struct Timer<'a> {
5 | name: &'a str,
6 | }
7 |
8 | impl<'a> Timer<'a> {
9 | pub fn new(name: &'a str) -> Timer<'a> {
10 | console::time_with_label(name);
11 | Timer { name }
12 | }
13 | }
14 |
15 | impl<'a> Drop for Timer<'a> {
16 | fn drop(&mut self) {
17 | console::time_end_with_label(self.name);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | const CopyWebpackPlugin = require("copy-webpack-plugin");
4 |
5 | module.exports = {
6 | entry: "./index.js",
7 | output: {
8 | path: path.resolve(__dirname, "build"),
9 | filename: "index.js",
10 | },
11 | mode: process.env.NODE_ENV || "development",
12 | plugins: [new CopyWebpackPlugin({ patterns: ["index.html"] })],
13 | stats: "errors-only",
14 | };
15 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import { GameOfLife } from "../dist";
2 |
3 | (async () => {
4 | const game = new GameOfLife();
5 | await game.initialize();
6 |
7 | const button = document.getElementById("start");
8 |
9 | button.addEventListener("click", () => {
10 | if (game.paused) {
11 | button.textContent = "pause";
12 | game.resume();
13 | } else {
14 | button.textContent = "start";
15 | game.pause();
16 | }
17 | });
18 | })();
19 |
--------------------------------------------------------------------------------
/core/utils.rs:
--------------------------------------------------------------------------------
1 | pub fn set_panic_hook() {
2 | // When the `console_error_panic_hook` feature is enabled, we can call the
3 | // `set_panic_hook` function at least once during initialization, and then
4 | // we will get better error messages if our code ever panics.
5 | //
6 | // For more details see
7 | // https://github.com/rustwasm/console_error_panic_hook#readme
8 | #[cfg(feature = "console_error_panic_hook")]
9 | console_error_panic_hook::set_once();
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "allowSyntheticDefaultImports": true,
5 | "declaration": true,
6 | "declarationDir": "dist",
7 | "esModuleInterop": true,
8 | "outDir": "./dist",
9 | "lib": ["es6", "dom", "es2016", "es2017"],
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "rootDir": ".",
14 | "strict": true,
15 | "target": "es2018",
16 | "types": ["node"]
17 | },
18 | "include": ["src/**/*"],
19 | "exclude": [
20 | "**/node_modules",
21 | "**/build",
22 | "**/dist",
23 | "**/*.md",
24 | "**/rollup.config.js"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/src/wasm.ts:
--------------------------------------------------------------------------------
1 | import initWasm from "../pkg";
2 | import wasm from "../pkg/index_bg.wasm";
3 |
4 | function asciiToBinary(str: string) {
5 | if (typeof atob === "function") {
6 | return atob(str);
7 | } else {
8 | return new Buffer(str, "base64").toString("binary");
9 | }
10 | }
11 |
12 | function decode(encoded: string) {
13 | const binaryString = asciiToBinary(encoded);
14 | const bytes = new Uint8Array(binaryString.length);
15 | for (let i = 0; i < binaryString.length; i++) {
16 | bytes[i] = binaryString.charCodeAt(i);
17 | }
18 | return bytes.buffer;
19 | }
20 |
21 | export async function init() {
22 | const program = await initWasm(decode(wasm as any));
23 | return program;
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Shaoru Ian Huang.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "plugins": ["@typescript-eslint", "import", "unused-imports"],
5 | "env": {
6 | "node": true,
7 | "browser": true
8 | },
9 | "extends": [
10 | "eslint:recommended",
11 | "plugin:@typescript-eslint/eslint-recommended",
12 | "plugin:@typescript-eslint/recommended",
13 | "plugin:prettier/recommended"
14 | ],
15 | "rules": {
16 | "import/order": [
17 | "error",
18 | {
19 | "alphabetize": {
20 | "order": "asc",
21 | "caseInsensitive": true
22 | },
23 | "newlines-between": "always",
24 | "groups": ["builtin", "external", "parent", "sibling", "index"],
25 | "pathGroups": [
26 | {
27 | "pattern": "react",
28 | "group": "external",
29 | "position": "before"
30 | }
31 | ],
32 | "pathGroupsExcludedImportTypes": ["builtin"]
33 | }
34 | ],
35 | // "unused-imports/no-unused-imports-ts": "warn",
36 | "@typescript-eslint/no-var-requires": "off",
37 | "@typescript-eslint/ban-ts-comment": "off",
38 | "prefer-const": "error",
39 | "prefer-template": "error"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | TS + RS = ❤️!
7 |
8 |
12 |
13 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | # You must change these to your own details.
2 | [package]
3 | authors = ["Ian Huang "]
4 | categories = ["wasm"]
5 | description = "My awesome Rust, WebAssembly, and TypeScript library template!"
6 | edition = "2018"
7 | license = "MIT"
8 | name = "rust-typescript-template"
9 | readme = "README.md"
10 | repository = "https://github.com/iantheearl/rust-typescript-template"
11 | version = "0.1.0"
12 |
13 | [lib]
14 | crate-type = ["cdylib"]
15 | name = "core"
16 | path = "core/lib.rs"
17 |
18 | [profile.release]
19 | # This makes the compiled code faster and smaller, but it makes compiling slower,
20 | # so it's only enabled in release mode.
21 | lto = true
22 |
23 | [features]
24 | # If you uncomment this line, it will enable `wee_alloc`:
25 | #default = ["wee_alloc"]
26 |
27 | [dependencies]
28 | # The `wasm-bindgen` crate provides the bare minimum functionality needed
29 | # to interact with JavaScript.
30 | wasm-bindgen = "0.2.45"
31 |
32 | # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
33 | # compared to the default allocator's ~10K. However, it is slower than the default
34 | # allocator, so it's not enabled by default.
35 | wee_alloc = {version = "0.4.2", optional = true}
36 |
37 | fixedbitset = "0.4.1"
38 |
39 | # The `web-sys` crate allows you to interact with the various browser APIs,
40 | # like the DOM.
41 | [dependencies.web-sys]
42 | features = ["console"]
43 | version = "0.3.22"
44 |
45 | # These crates are used for running unit tests.
46 | [dev-dependencies]
47 | futures = "0.3.19"
48 | js-sys = "0.3.22"
49 | wasm-bindgen-futures = "0.4.28"
50 | wasm-bindgen-test = "0.3.28"
51 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import path from "path";
3 |
4 | import resolve from "@rollup/plugin-node-resolve";
5 | import { base64 } from "rollup-plugin-base64";
6 | import nodePolyfills from "rollup-plugin-node-polyfills";
7 | import { terser } from "rollup-plugin-terser";
8 | import typescript from "rollup-plugin-typescript2";
9 |
10 | const packageJson = require("./package.json");
11 |
12 | const globals = {
13 | ...packageJson.devDependencies,
14 | };
15 |
16 | export default {
17 | input: "src/index.ts",
18 | output: {
19 | file: packageJson.module,
20 | format: "esm",
21 | },
22 | plugins: [
23 | typescript({
24 | useTsconfigDeclarationDir: true,
25 | }),
26 | nodePolyfills(),
27 | resolve({
28 | browser: true,
29 | preferBuiltins: false,
30 | }),
31 | // used to load in .wasm files as base64 strings, then can be instantiated with
32 | // helper functions within `src/wasm.ts`.
33 | base64({ include: "**/*.wasm" }),
34 | {
35 | // copy over rust-built declaration files to dist for later .d.ts bundling
36 | name: "copy-pkg",
37 | generateBundle() {
38 | fs.mkdirSync(path.resolve(`dist/pkg`), { recursive: true });
39 |
40 | ["index.d.ts", "index_bg.wasm.d.ts"].forEach((file) => {
41 | fs.copyFileSync(
42 | path.resolve(`./pkg/${file}`),
43 | path.resolve(`./dist/pkg/${file}`)
44 | );
45 | });
46 |
47 | fs.copyFileSync(
48 | path.resolve(`./pkg/index_bg.wasm`),
49 | path.resolve(`./dist/index_bg.wasm`)
50 | );
51 | },
52 | },
53 | ...(!process.env.ROLLUP_WATCH ? [terser()] : []),
54 | ],
55 | external: [...Object.keys(globals)],
56 | watch: { clearScreen: false },
57 | };
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Create Rust + TypeScript libraries with ease! PR'S WELCOMED!
4 |
5 | ## :sparkles: Inspiration
6 |
7 | I wanted to create a WebAssembly/Rust library with additional JS features, but I couldn't find any resources on how to integrate the npm-ready library built by `wasm-pack` with TypeScript. All the examples online either seemed to use the output of `wasm-pack` using `npm link`, or didn't have TypeScript support. So, I created this, a template that allows you to embed `wasm-pack` all within one single TypeScript-based library. Enjoy!
8 |
9 | ## :building_construction: Install
10 |
11 | Before development, make sure you have installed:
12 | - [wasm-pack](https://github.com/rustwasm/wasm-pack)
13 | - [cargo-watch](https://github.com/watchexec/cargo-watch)
14 |
15 | ```bash
16 | # clone the repo
17 | git clone --depth 1 --branch master https://github.com/iantheearl/rust-typescript-template.git your-project-name
18 | cd your-project-name
19 |
20 | # install dependencies
21 | yarn
22 |
23 | # start development
24 | yarn run dev
25 |
26 | # run example
27 | yarn run example
28 |
29 | # visit http://localhost:8080
30 | ```
31 |
32 | After installation, start developing here:
33 | - `./pkg`: Rust source files
34 | - `./src`: TypeScript source files
35 |
36 | ## :printer: Publish
37 |
38 | ```bash
39 | # build the library to ./dist, including .wasm
40 | yarn build
41 |
42 | # push to npm
43 | npm publish --access public
44 | ```
45 |
46 | ## :tv: Deployment
47 |
48 | ```bash
49 | # build the example to `./example/build`, and push that folder to the gh-pages branch
50 | yarn deploy
51 | ```
52 |
53 | Configure github to deploy branch gh-pages.
54 |
55 |
56 | ## :open_file_folder: Structure
57 |
58 | - `./core`: Rust code, the core of your library
59 | - `./pkg`: `wasm-pack` built npm-ready library
60 | - `./src`: TypeScript source code, where u can import from `wasm-pack`
61 | - `./example`: A web app that uses your library directly from `./dist`
62 | - `./tests`: To test your library
63 |
64 | ## License
65 |
66 | MIT © [Rust TypeScript Template](https://github.com/iantheearl/rust-typescript-template)
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Ian Huang ",
3 | "name": "rust-typescript-template",
4 | "version": "0.1.8",
5 | "module": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "license": "MIT",
8 | "files": [
9 | "dist"
10 | ],
11 | "scripts": {
12 | "clean": "rimraf dist",
13 | "prepare": "husky install",
14 | "predev": "wasm-pack build --out-name index --target web",
15 | "dev": "npm-run-all --parallel \"dev:rs\" \"dev:ts\"",
16 | "dev:ts": "npm run build -- -w",
17 | "dev:rs": "cargo watch -w core -s \"wasm-pack build --out-name index --target web\"",
18 | "test": "cargo test && wasm-pack test --headless",
19 | "build": "npm run clean && rollup -c",
20 | "lint": "eslint . --ext .ts",
21 | "example": "cd example && webpack-dev-server --open",
22 | "deploy": "cd example && webpack && cd .. && node publish.js"
23 | },
24 | "devDependencies": {
25 | "@babel/core": "^7.16.7",
26 | "@babel/preset-env": "^7.16.7",
27 | "@rollup/plugin-babel": "^5.3.0",
28 | "@rollup/plugin-node-resolve": "^13.1.2",
29 | "@rollup/plugin-typescript": "^8.3.0",
30 | "@typescript-eslint/eslint-plugin": "^5.9.0",
31 | "@typescript-eslint/parser": "^5.9.0",
32 | "copy-webpack-plugin": "^10.2.0",
33 | "eslint": "^8.6.0",
34 | "eslint-config-prettier": "^8.3.0",
35 | "eslint-plugin-import": "^2.25.4",
36 | "eslint-plugin-prettier": "^4.0.0",
37 | "eslint-plugin-unused-imports": "^2.0.0",
38 | "gh-pages": "^3.2.3",
39 | "husky": "^7.0.4",
40 | "lint-staged": "^12.1.5",
41 | "npm-run-all": "^4.1.5",
42 | "prettier": "^2.5.1",
43 | "pretty-quick": "^3.1.3",
44 | "rimraf": "^3.0.2",
45 | "rollup": "^2.63.0",
46 | "rollup-plugin-base64": "^1.0.1",
47 | "rollup-plugin-node-polyfills": "^0.2.1",
48 | "rollup-plugin-terser": "^7.0.2",
49 | "rollup-plugin-typescript2": "^0.31.1",
50 | "tslib": "^2.3.1",
51 | "typescript": "^4.5.4",
52 | "wasm-pack": "^0.12.1",
53 | "webpack": "^5.65.0",
54 | "webpack-cli": "^4.9.1",
55 | "webpack-dev-server": "^4.7.2"
56 | },
57 | "lint-staged": {
58 | "{src,example}/**/*.{js,jsx,ts,tsx}": [
59 | "eslint --fix",
60 | "pretty-quick --staged"
61 | ]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Universe } from "../pkg";
2 |
3 | import { init } from "./wasm";
4 |
5 | const CELL_SIZE = 5; // px
6 | const GRID_COLOR = "#CCCCCC";
7 | const DEAD_COLOR = "#FFFFFF";
8 | const ALIVE_COLOR = "#000000";
9 |
10 | export class GameOfLife {
11 | universe!: Universe;
12 | memory!: WebAssembly.Memory;
13 | animationId = null as any;
14 |
15 | canvas!: HTMLCanvasElement;
16 | ctx!: CanvasRenderingContext2D;
17 |
18 | paused = true;
19 |
20 | public async initialize(): Promise {
21 | const { memory } = await init();
22 | this.memory = memory;
23 |
24 | // Construct the universe, and get its width and height.
25 | this.universe = Universe.new();
26 | this.createCanvas();
27 |
28 | this.drawGrid();
29 | this.drawCells();
30 | }
31 |
32 | public resume = () => {
33 | this.animationId = requestAnimationFrame(this.renderLoop);
34 | this.paused = false;
35 | };
36 |
37 | public pause = () => {
38 | cancelAnimationFrame(this.animationId);
39 | this.animationId = null;
40 | this.paused = true;
41 | };
42 |
43 | private createCanvas = () => {
44 | this.canvas = document.getElementById("game-canvas") as HTMLCanvasElement;
45 | this.canvas.height = (CELL_SIZE + 1) * this.height + 1;
46 | this.canvas.width = (CELL_SIZE + 1) * this.width + 1;
47 |
48 | if (!this.canvas) {
49 | this.canvas = document.createElement("canvas");
50 | this.canvas.id = "game-canvas";
51 | document.body.appendChild(this.canvas);
52 | }
53 |
54 | this.canvas.addEventListener("click", (event) => {
55 | const boundingRect = this.canvas.getBoundingClientRect();
56 |
57 | const scaleX = this.canvas.width / boundingRect.width;
58 | const scaleY = this.canvas.height / boundingRect.height;
59 |
60 | const canvasLeft = (event.clientX - boundingRect.left) * scaleX;
61 | const canvasTop = (event.clientY - boundingRect.top) * scaleY;
62 |
63 | const row = Math.min(
64 | Math.floor(canvasTop / (CELL_SIZE + 1)),
65 | this.height - 1
66 | );
67 | const col = Math.min(
68 | Math.floor(canvasLeft / (CELL_SIZE + 1)),
69 | this.width - 1
70 | );
71 |
72 | this.universe.toggleCell(row, col);
73 |
74 | this.drawCells();
75 | });
76 |
77 | this.ctx = this.canvas.getContext("2d") as CanvasRenderingContext2D;
78 | };
79 |
80 | private drawGrid = () => {
81 | this.ctx.beginPath();
82 | this.ctx.strokeStyle = GRID_COLOR;
83 |
84 | // Vertical lines.
85 | for (let i = 0; i <= this.width; i++) {
86 | this.ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0);
87 | this.ctx.lineTo(
88 | i * (CELL_SIZE + 1) + 1,
89 | (CELL_SIZE + 1) * this.height + 1
90 | );
91 | }
92 |
93 | // Horizontal lines.
94 | for (let j = 0; j <= this.height; j++) {
95 | this.ctx.moveTo(0, j * (CELL_SIZE + 1) + 1);
96 | this.ctx.lineTo(
97 | (CELL_SIZE + 1) * this.width + 1,
98 | j * (CELL_SIZE + 1) + 1
99 | );
100 | }
101 |
102 | this.ctx.stroke();
103 | };
104 |
105 | private drawCells = () => {
106 | const cellsPtr = this.universe.cells();
107 | const cells = new Uint8Array(
108 | this.memory.buffer,
109 | cellsPtr,
110 | (this.width * this.height) / 8
111 | );
112 |
113 | this.ctx.beginPath();
114 |
115 | // Alive cells.
116 | this.ctx.fillStyle = ALIVE_COLOR;
117 | for (let row = 0; row < this.height; row++) {
118 | for (let col = 0; col < this.width; col++) {
119 | const idx = this.getIndex(row, col);
120 | if (!this.bitIsSet(idx, cells)) {
121 | continue;
122 | }
123 |
124 | this.ctx.fillRect(
125 | col * (CELL_SIZE + 1) + 1,
126 | row * (CELL_SIZE + 1) + 1,
127 | CELL_SIZE,
128 | CELL_SIZE
129 | );
130 | }
131 | }
132 |
133 | // Dead cells.
134 | this.ctx.fillStyle = DEAD_COLOR;
135 | for (let row = 0; row < this.height; row++) {
136 | for (let col = 0; col < this.width; col++) {
137 | const idx = this.getIndex(row, col);
138 | if (this.bitIsSet(idx, cells)) {
139 | continue;
140 | }
141 |
142 | this.ctx.fillRect(
143 | col * (CELL_SIZE + 1) + 1,
144 | row * (CELL_SIZE + 1) + 1,
145 | CELL_SIZE,
146 | CELL_SIZE
147 | );
148 | }
149 | }
150 |
151 | this.ctx.stroke();
152 | };
153 |
154 | private renderLoop = () => {
155 | this.universe.tick();
156 | this.drawCells();
157 | this.animationId = requestAnimationFrame(this.renderLoop);
158 | };
159 |
160 | private getIndex = (row: number, column: number) => {
161 | return row * this.width + column;
162 | };
163 |
164 | private bitIsSet = (n: number, arr: Uint8Array) => {
165 | const byte = Math.floor(n / 8);
166 | const mask = 1 << n % 8;
167 | return (arr[byte] & mask) === mask;
168 | };
169 |
170 | private get width() {
171 | return this.universe.width();
172 | }
173 |
174 | private get height() {
175 | return this.universe.height();
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/core/lib.rs:
--------------------------------------------------------------------------------
1 | mod utils;
2 |
3 | use fixedbitset::FixedBitSet;
4 | use std::fmt;
5 | use wasm_bindgen::prelude::*;
6 |
7 | // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
8 | // allocator.
9 | #[cfg(feature = "wee_alloc")]
10 | #[global_allocator]
11 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
12 |
13 | #[wasm_bindgen]
14 | pub struct Universe {
15 | width: u32,
16 | height: u32,
17 | cells: FixedBitSet,
18 | back_cells: FixedBitSet,
19 | }
20 |
21 | impl Universe {
22 | fn get_index(&self, row: u32, column: u32) -> usize {
23 | (row * self.width + column) as usize
24 | }
25 |
26 | fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
27 | let mut count = 0;
28 | let cells = &self.cells;
29 |
30 | let north = if row == 0 { self.height - 1 } else { row - 1 };
31 |
32 | let south = if row == self.height - 1 { 0 } else { row + 1 };
33 |
34 | let west = if column == 0 {
35 | self.width - 1
36 | } else {
37 | column - 1
38 | };
39 |
40 | let east = if column == self.width - 1 {
41 | 0
42 | } else {
43 | column + 1
44 | };
45 |
46 | let nw = self.get_index(north, west);
47 | count += cells[nw] as u8;
48 |
49 | let n = self.get_index(north, column);
50 | count += cells[n] as u8;
51 |
52 | let ne = self.get_index(north, east);
53 | count += cells[ne] as u8;
54 |
55 | let w = self.get_index(row, west);
56 | count += cells[w] as u8;
57 |
58 | let e = self.get_index(row, east);
59 | count += cells[e] as u8;
60 |
61 | let sw = self.get_index(south, west);
62 | count += cells[sw] as u8;
63 |
64 | let s = self.get_index(south, column);
65 | count += cells[s] as u8;
66 |
67 | let se = self.get_index(south, east);
68 | count += cells[se] as u8;
69 |
70 | count
71 | }
72 |
73 | /// Get the dead and alive values of the entire universe.
74 | pub fn get_cells(&self) -> *const u32 {
75 | self.cells.as_slice().as_ptr()
76 | }
77 |
78 | /// Set cells to be alive in a universe by passing the row and column
79 | /// of each cell as an array.
80 | pub fn set_cells(&mut self, cells: &[(u32, u32)]) {
81 | for (row, col) in cells.iter().cloned() {
82 | let idx = self.get_index(row, col);
83 | self.cells.set(idx, true);
84 | }
85 | }
86 | }
87 |
88 | #[wasm_bindgen]
89 | impl Universe {
90 | pub fn tick(&mut self) {
91 | for row in 0..self.height {
92 | for col in 0..self.width {
93 | let idx = self.get_index(row, col);
94 | let cell = self.cells[idx];
95 | let live_neighbors = self.live_neighbor_count(row, col);
96 |
97 | let next_cell = match (cell, live_neighbors) {
98 | // Rule 1: Any live cell with fewer than two live neighbours
99 | // dies, as if caused by underpopulation.
100 | (true, x) if x < 2 => false,
101 | // Rule 2: Any live cell with two or three live neighbours
102 | // lives on to the next generation.
103 | (true, 2) | (true, 3) => true,
104 | // Rule 3: Any live cell with more than three live
105 | // neighbours dies, as if by overpopulation.
106 | (true, x) if x > 3 => false,
107 | // Rule 4: Any dead cell with exactly three live neighbours
108 | // becomes a live cell, as if by reproduction.
109 | (false, 3) => true,
110 | // All other cells remain in the same state.
111 | (otherwise, _) => otherwise,
112 | };
113 |
114 | self.back_cells.set(idx, next_cell);
115 | }
116 | }
117 |
118 | std::mem::swap(&mut self.cells, &mut self.back_cells);
119 | }
120 |
121 | pub fn new() -> Universe {
122 | utils::set_panic_hook();
123 | let width = 64;
124 | let height = 64;
125 |
126 | let size = (width * height) as usize;
127 | let mut cells = FixedBitSet::with_capacity(size);
128 | for i in 0..size {
129 | cells.set(i, i % 2 == 0 || i % 7 == 0);
130 | }
131 | let back_cells = cells.clone();
132 |
133 | Universe {
134 | width,
135 | height,
136 | cells,
137 | back_cells,
138 | }
139 | }
140 |
141 | pub fn render(&self) -> String {
142 | self.to_string()
143 | }
144 |
145 | pub fn width(&self) -> u32 {
146 | self.width
147 | }
148 |
149 | pub fn height(&self) -> u32 {
150 | self.height
151 | }
152 |
153 | pub fn cells(&self) -> *const u32 {
154 | self.cells.as_slice().as_ptr()
155 | }
156 |
157 | /// Set the width of the universe.
158 | ///
159 | /// Resets all cells to the dead state.
160 | #[wasm_bindgen(js_name = setWidth)]
161 | pub fn set_width(&mut self, width: u32) {
162 | self.width = width;
163 | self.cells.clear();
164 | }
165 |
166 | /// Set the height of the universe.
167 | ///
168 | /// Resets all cells to the dead state.
169 | #[wasm_bindgen(js_name = setHeight)]
170 | pub fn set_height(&mut self, height: u32) {
171 | self.height = height;
172 | self.cells.clear();
173 | }
174 |
175 | #[wasm_bindgen(js_name = toggleCell)]
176 | pub fn toggle_cell(&mut self, row: u32, column: u32) {
177 | let idx = self.get_index(row, column);
178 | self.cells.set(idx, !self.cells[idx]); // toggle the value of the cell
179 | }
180 | }
181 |
182 | impl fmt::Display for Universe {
183 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184 | let cells = &self.cells;
185 | for row in 0..self.height {
186 | for col in 0..self.width {
187 | let i = self.get_index(row, col);
188 | let symbol = if cells[i] { '◻' } else { '◼' };
189 | write!(f, "{}", symbol)?;
190 | }
191 | write!(f, "\n")?;
192 | }
193 |
194 | Ok(())
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "bumpalo"
7 | version = "3.8.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
10 |
11 | [[package]]
12 | name = "cfg-if"
13 | version = "0.1.10"
14 | source = "registry+https://github.com/rust-lang/crates.io-index"
15 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
16 |
17 | [[package]]
18 | name = "cfg-if"
19 | version = "1.0.0"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
22 |
23 | [[package]]
24 | name = "console_error_panic_hook"
25 | version = "0.1.7"
26 | source = "registry+https://github.com/rust-lang/crates.io-index"
27 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
28 | dependencies = [
29 | "cfg-if 1.0.0",
30 | "wasm-bindgen",
31 | ]
32 |
33 | [[package]]
34 | name = "fixedbitset"
35 | version = "0.4.1"
36 | source = "registry+https://github.com/rust-lang/crates.io-index"
37 | checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e"
38 |
39 | [[package]]
40 | name = "futures"
41 | version = "0.3.19"
42 | source = "registry+https://github.com/rust-lang/crates.io-index"
43 | checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4"
44 | dependencies = [
45 | "futures-channel",
46 | "futures-core",
47 | "futures-executor",
48 | "futures-io",
49 | "futures-sink",
50 | "futures-task",
51 | "futures-util",
52 | ]
53 |
54 | [[package]]
55 | name = "futures-channel"
56 | version = "0.3.19"
57 | source = "registry+https://github.com/rust-lang/crates.io-index"
58 | checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b"
59 | dependencies = [
60 | "futures-core",
61 | "futures-sink",
62 | ]
63 |
64 | [[package]]
65 | name = "futures-core"
66 | version = "0.3.19"
67 | source = "registry+https://github.com/rust-lang/crates.io-index"
68 | checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
69 |
70 | [[package]]
71 | name = "futures-executor"
72 | version = "0.3.19"
73 | source = "registry+https://github.com/rust-lang/crates.io-index"
74 | checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
75 | dependencies = [
76 | "futures-core",
77 | "futures-task",
78 | "futures-util",
79 | ]
80 |
81 | [[package]]
82 | name = "futures-io"
83 | version = "0.3.19"
84 | source = "registry+https://github.com/rust-lang/crates.io-index"
85 | checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2"
86 |
87 | [[package]]
88 | name = "futures-macro"
89 | version = "0.3.19"
90 | source = "registry+https://github.com/rust-lang/crates.io-index"
91 | checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c"
92 | dependencies = [
93 | "proc-macro2",
94 | "quote",
95 | "syn",
96 | ]
97 |
98 | [[package]]
99 | name = "futures-sink"
100 | version = "0.3.19"
101 | source = "registry+https://github.com/rust-lang/crates.io-index"
102 | checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508"
103 |
104 | [[package]]
105 | name = "futures-task"
106 | version = "0.3.19"
107 | source = "registry+https://github.com/rust-lang/crates.io-index"
108 | checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
109 |
110 | [[package]]
111 | name = "futures-util"
112 | version = "0.3.19"
113 | source = "registry+https://github.com/rust-lang/crates.io-index"
114 | checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
115 | dependencies = [
116 | "futures-channel",
117 | "futures-core",
118 | "futures-io",
119 | "futures-macro",
120 | "futures-sink",
121 | "futures-task",
122 | "memchr",
123 | "pin-project-lite",
124 | "pin-utils",
125 | "slab",
126 | ]
127 |
128 | [[package]]
129 | name = "js-sys"
130 | version = "0.3.55"
131 | source = "registry+https://github.com/rust-lang/crates.io-index"
132 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
133 | dependencies = [
134 | "wasm-bindgen",
135 | ]
136 |
137 | [[package]]
138 | name = "lazy_static"
139 | version = "1.4.0"
140 | source = "registry+https://github.com/rust-lang/crates.io-index"
141 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
142 |
143 | [[package]]
144 | name = "libc"
145 | version = "0.2.112"
146 | source = "registry+https://github.com/rust-lang/crates.io-index"
147 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
148 |
149 | [[package]]
150 | name = "log"
151 | version = "0.4.14"
152 | source = "registry+https://github.com/rust-lang/crates.io-index"
153 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
154 | dependencies = [
155 | "cfg-if 1.0.0",
156 | ]
157 |
158 | [[package]]
159 | name = "memchr"
160 | version = "2.4.1"
161 | source = "registry+https://github.com/rust-lang/crates.io-index"
162 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
163 |
164 | [[package]]
165 | name = "memory_units"
166 | version = "0.4.0"
167 | source = "registry+https://github.com/rust-lang/crates.io-index"
168 | checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
169 |
170 | [[package]]
171 | name = "pin-project-lite"
172 | version = "0.2.8"
173 | source = "registry+https://github.com/rust-lang/crates.io-index"
174 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
175 |
176 | [[package]]
177 | name = "pin-utils"
178 | version = "0.1.0"
179 | source = "registry+https://github.com/rust-lang/crates.io-index"
180 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
181 |
182 | [[package]]
183 | name = "proc-macro2"
184 | version = "1.0.36"
185 | source = "registry+https://github.com/rust-lang/crates.io-index"
186 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
187 | dependencies = [
188 | "unicode-xid",
189 | ]
190 |
191 | [[package]]
192 | name = "quote"
193 | version = "1.0.14"
194 | source = "registry+https://github.com/rust-lang/crates.io-index"
195 | checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
196 | dependencies = [
197 | "proc-macro2",
198 | ]
199 |
200 | [[package]]
201 | name = "rust-typescript-template"
202 | version = "0.1.0"
203 | dependencies = [
204 | "fixedbitset",
205 | "futures",
206 | "js-sys",
207 | "wasm-bindgen",
208 | "wasm-bindgen-futures",
209 | "wasm-bindgen-test",
210 | "web-sys",
211 | "wee_alloc",
212 | ]
213 |
214 | [[package]]
215 | name = "scoped-tls"
216 | version = "1.0.0"
217 | source = "registry+https://github.com/rust-lang/crates.io-index"
218 | checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
219 |
220 | [[package]]
221 | name = "slab"
222 | version = "0.4.5"
223 | source = "registry+https://github.com/rust-lang/crates.io-index"
224 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
225 |
226 | [[package]]
227 | name = "syn"
228 | version = "1.0.84"
229 | source = "registry+https://github.com/rust-lang/crates.io-index"
230 | checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b"
231 | dependencies = [
232 | "proc-macro2",
233 | "quote",
234 | "unicode-xid",
235 | ]
236 |
237 | [[package]]
238 | name = "unicode-xid"
239 | version = "0.2.2"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
242 |
243 | [[package]]
244 | name = "wasm-bindgen"
245 | version = "0.2.78"
246 | source = "registry+https://github.com/rust-lang/crates.io-index"
247 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
248 | dependencies = [
249 | "cfg-if 1.0.0",
250 | "wasm-bindgen-macro",
251 | ]
252 |
253 | [[package]]
254 | name = "wasm-bindgen-backend"
255 | version = "0.2.78"
256 | source = "registry+https://github.com/rust-lang/crates.io-index"
257 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
258 | dependencies = [
259 | "bumpalo",
260 | "lazy_static",
261 | "log",
262 | "proc-macro2",
263 | "quote",
264 | "syn",
265 | "wasm-bindgen-shared",
266 | ]
267 |
268 | [[package]]
269 | name = "wasm-bindgen-futures"
270 | version = "0.4.28"
271 | source = "registry+https://github.com/rust-lang/crates.io-index"
272 | checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
273 | dependencies = [
274 | "cfg-if 1.0.0",
275 | "js-sys",
276 | "wasm-bindgen",
277 | "web-sys",
278 | ]
279 |
280 | [[package]]
281 | name = "wasm-bindgen-macro"
282 | version = "0.2.78"
283 | source = "registry+https://github.com/rust-lang/crates.io-index"
284 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
285 | dependencies = [
286 | "quote",
287 | "wasm-bindgen-macro-support",
288 | ]
289 |
290 | [[package]]
291 | name = "wasm-bindgen-macro-support"
292 | version = "0.2.78"
293 | source = "registry+https://github.com/rust-lang/crates.io-index"
294 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
295 | dependencies = [
296 | "proc-macro2",
297 | "quote",
298 | "syn",
299 | "wasm-bindgen-backend",
300 | "wasm-bindgen-shared",
301 | ]
302 |
303 | [[package]]
304 | name = "wasm-bindgen-shared"
305 | version = "0.2.78"
306 | source = "registry+https://github.com/rust-lang/crates.io-index"
307 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
308 |
309 | [[package]]
310 | name = "wasm-bindgen-test"
311 | version = "0.3.28"
312 | source = "registry+https://github.com/rust-lang/crates.io-index"
313 | checksum = "96f1aa7971fdf61ef0f353602102dbea75a56e225ed036c1e3740564b91e6b7e"
314 | dependencies = [
315 | "console_error_panic_hook",
316 | "js-sys",
317 | "scoped-tls",
318 | "wasm-bindgen",
319 | "wasm-bindgen-futures",
320 | "wasm-bindgen-test-macro",
321 | ]
322 |
323 | [[package]]
324 | name = "wasm-bindgen-test-macro"
325 | version = "0.3.28"
326 | source = "registry+https://github.com/rust-lang/crates.io-index"
327 | checksum = "6006f79628dfeb96a86d4db51fbf1344cd7fd8408f06fc9aa3c84913a4789688"
328 | dependencies = [
329 | "proc-macro2",
330 | "quote",
331 | ]
332 |
333 | [[package]]
334 | name = "web-sys"
335 | version = "0.3.55"
336 | source = "registry+https://github.com/rust-lang/crates.io-index"
337 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
338 | dependencies = [
339 | "js-sys",
340 | "wasm-bindgen",
341 | ]
342 |
343 | [[package]]
344 | name = "wee_alloc"
345 | version = "0.4.5"
346 | source = "registry+https://github.com/rust-lang/crates.io-index"
347 | checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
348 | dependencies = [
349 | "cfg-if 0.1.10",
350 | "libc",
351 | "memory_units",
352 | "winapi",
353 | ]
354 |
355 | [[package]]
356 | name = "winapi"
357 | version = "0.3.9"
358 | source = "registry+https://github.com/rust-lang/crates.io-index"
359 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
360 | dependencies = [
361 | "winapi-i686-pc-windows-gnu",
362 | "winapi-x86_64-pc-windows-gnu",
363 | ]
364 |
365 | [[package]]
366 | name = "winapi-i686-pc-windows-gnu"
367 | version = "0.4.0"
368 | source = "registry+https://github.com/rust-lang/crates.io-index"
369 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
370 |
371 | [[package]]
372 | name = "winapi-x86_64-pc-windows-gnu"
373 | version = "0.4.0"
374 | source = "registry+https://github.com/rust-lang/crates.io-index"
375 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
376 |
--------------------------------------------------------------------------------