rust + WebAssembleyでライフゲーム

rustとWebAssembleyで何か作ろうと思ったんですが、何も思い浮かばず、[チュートリアル](https://rustwasm.github.io/docs/book/game-of-life/introduction.html)をやってみました。

チュートリアルをやるとこんな感じの[ライフゲーム](https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0)が作れます。
![wasm.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/f906249b-25e0-daae-afcf-206443e8b604.gif)
デモはこちらにあります。
https://wasm-rust-game-of-life.netlify.app/

コードはこちらです。
https://github.com/pokotyan/wasm-game-of-life

# WebAssembley
ブラウザ上で動かせるバイナリです。C、C++、Rust、Go、Kotlin、AssemblyScript（TypeScriptのサブセット）などからコンパイルして生成することができます。
いろんな言語からwasmを作れますが、GCを備えた言語だとコンパイル後のwasmファイルが大きくなってしまいます。RustのようなGCがない言語だとwasmのサイズが小さくなるため、ページ読み込み速度も改善するようです。[参考](https://medium.com/blockchain-engineer-blog/rust-webassembly-76dc05934eff)

wasmの現状の主な用途は重い計算処理などをオフロードさせるために使われてるみたいなので、GCがないかつ、メモリ安全なRustは相性いいんじゃないかなと思いました。

# ツールチェイン
rustでWebAssembleyをやる際のツールチェインは[こちらの記事](https://qiita.com/legokichi/items/5d6344314ab6d6633554)がよく纏まっていました。大きく分けてemscripten系統とwasm-bindgen系統に大別されるみたいです。(wasm-bindgenの方が新しい)

冒頭のチュートリアルでは[wasm-bindgen](https://github.com/rustwasm/wasm-bindgen)系統である[wasm-pack](https://github.com/rustwasm/wasm-pack)を利用しています。
wasm-bindgenはRust(wasm)とjs間がやりとりできるようなインターフェースを作ってくれるツールですが、wasm-packはそれをさらにwrapして、webpackで読み込めるようにしたりとか、npmに簡単にpublishできるようなコマンドを用意してくれたりしてます。

# ボイラープレート
チュートリアルでは以下のボイラープレートを用いて、さらに環境構築を簡単にしています。

## rust(wasm)側のテンプレート

**wasm-pack-template**
https://github.com/rustwasm/wasm-pack-template

wasm-packを活用したボイラープレートです。このテンプレートをcargo-generateコマンドを用いて環境構築します。
rustのパッケージマネージャであるcargoはいろいろなサブコマンドが用意されており（自分で作ることも可能）、[cargo-generate](https://github.com/ashleygwilliams/cargo-generate)を使うと、指定したgitリポジトリのテンプレートを元にRustのプロジェクトを新規作成できます。

なので、これを一発打つだけで、Rust側の環境構築は終わりです。
（もちろん事前にrustやcargoのインストールは必要ですが、そこもチュートリアル上でちゃんと書かれてます）

```
$ cargo generate --git https://github.com/rustwasm/wasm-pack-template
```

## フロント側のテンプレート

**create-wasm-app**
https://github.com/rustwasm/create-wasm-app

このテンプレートを `npm init wasm-app` で利用すると [hello-wasm-pack](https://www.npmjs.com/package/hello-wasm-pack) というwasmのnpmパッケージを利用する最低限のプロジェクトが生成されます。

```js
import * as wasm from "hello-wasm-pack";

wasm.greet();
```

チュートリアルでは、ここのimportを実際に自分でコーディングし、ビルドしたrustのwasmに置き換えながら進めていきます。


## 他にもあるテンプレート
上記の wasm-pack-template と create-wasm-app のテンプレートは以下のような利用を想定したテンプレートです。

- wasm-pack-template
  Rustで作ったwasmをnpmにパブリッシュし、npmパッケージにする。

- create-wasm-app
  npm上にあるwasmをインストールし利用する。

なので、どちらもnpmを介して利用されることを期待したテンプレートです。
そうではなく、モノレポ的に使えるオールインワンなテンプレートも用意されてます。

**rust-webpack-template**
https://github.com/rustwasm/rust-webpack-template

**rust-parcel-template**
https://github.com/rustwasm/rust-parcel-template

この二つの違いは名前の通り、バンドラーにwebpackを使うかparcelを使うかくらいの違いだとは思いますが、これを使えば一瞬でRust + WebAssembley + jsの環境構築ができるので、wasmをちょっと試したい場合にはすごい便利そうです。

# [ライフゲーム](https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0)
ライフゲームでは各セルが生死の状態を持ちます。
そしてそれぞれのセルが以下の4つの条件によって生死を繰り返します。

- 生きている隣人が2つ以下の生細胞は、過疎化が原因であるかのように、死ぬ。
- 2つまたは3つの生きた隣人を持つすべての生細胞は、次の世代に生き続ける。
- 3つ以上の生きている隣人を持つ生きている細胞は、過疎が原因であるかのように、死ぬ。
- 生きている隣人がちょうど3人いる死んだ細胞は、生殖によって生じたかのように、生きている細胞になる。

この世代交代をrequestAnimationFrameの度に実行すると、冒頭に貼ったgifのようになります。
このライフゲームで現れるセルのパターンはいろいろあり、調べると色々出てきます。僕はこのチュートリアルでライフゲームというものを知りましたが、ライフゲームはチューリング完全らしく、[論理ゲートを作ってる人もいました](https://www.youtube.com/watch?v=ZO0yrIUr8Ag&feature=youtu.be)。

# jsからwasmのメモリを参照する

チュートリアルでは、初めは簡単な実装でライフゲームを動かし、それをリファクタしていく形で進んでいきます。

最初の実装はrustのwasm側で「セルの生死の状態によって ◼ もしくは ◻ を出力する」というコードを書きます。そしてjs側からはその関数を呼び出すだけです。js側からwasm側に全ての処理を任せてるイメージです。

```rust:lib.rs
#[wasm_bindgen]
impl Universe {
    // ...

    pub fn render(&self) -> String {
        self.to_string()
    }
}

impl fmt::Display for Universe {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for line in self.cells.as_slice().chunks(self.width as usize) {
            for &cell in line {
                // cellの状態によって表示を変える
                let symbol = if cell == Cell::Dead { '◻' } else { '◼' };
                write!(f, "{}", symbol)?;
            }
            write!(f, "\n")?;
        }

        Ok(())
    }
}
```

```js:index.js
const pre = document.getElementById("game-of-life-text");

// js側からはrenderを叩くだけ。
pre.textContent = universe.render();
```


それを次の実装ではjs側からwasmのメモリを参照して各セルの生死の状態を確認し、js側で描画するといった形にリファクタします。 

具体的にはこんな感じでwasmのメモリを参照しています。

```rust:lib.rs
#[wasm_bindgen]
impl Universe {
    // cellsには各セルの生死状態(0 or 1)が入った配列。それのポインタを返す
    pub fn cells(&self) -> *const Cell {
        self.cells.as_ptr()
    }
```

```js:index.js
import { Universe, Cell } from "wasm-game-of-life";
import { memory } from "wasm-game-of-life/wasm_game_of_life_bg";

  // wasmのメモリ内からcellsのメモリ内容を取得する。
  // cellsPtrにはcellsの先頭アドレスが入っている。cellsの先頭からwidth * height分の範囲のメモリ内容を返す。
  const cellsPtr = universe.cells();
  const cells = new Uint8Array(memory.buffer, cellsPtr, width * height);
```

そして、wasmのメモリから取り出した各セルの情報を元に画面へ描画します。

```js:index.js
  ctx.beginPath();

  for (let row = 0; row < height; row++) {
    for (let col = 0; col < width; col++) {
      const idx = getIndex(row, col);

      // cellの状態によって表示を変える
      ctx.fillStyle = cells[idx] === Cell.Dead ? DEAD_COLOR : ALIVE_COLOR;

      ctx.fillRect(
        col * (CELL_SIZE + 1) + 1,
        row * (CELL_SIZE + 1) + 1,
        CELL_SIZE,
        CELL_SIZE
      );
    }
  }

  ctx.stroke();
```

今まで、jsでArrayBufferを扱うコードを書くことはそんななかったため、あまりわかってないですが、ここら辺のメモリの扱いがWebAssembleyをやってく上でのキモだなあという感じがしています。

# 最後に
~~僕はもういいかと思ってやってないですが、~~ 以下のような内容もチュートリアルにあります。

- 生死の判定に8bitも使っているのを1bitで判定するようにリファクタ
- テストの追加
- フレームのたびにデバッガーを挟む
- 一時停止ボタン
- フレームごとにかかった処理時間の計測
- ビルドのオプションを見直して、wasmのサイズ削減
- npmへpublish

そんなに時間もかからず終わるのでWebAssembleyが気になっている人は[このチュートリアル](https://rustwasm.github.io/docs/book/game-of-life/introduction.html)をやってみてはどうでしょうか！