rustifyで簡単WebAssemblyしてみる

WebAssembly(以下WASM)には興味があって遊んでみたいと思っていたら、rustifyという便利な魔法が先日GitHubに現れたので使ってみました。

続編のほうが詳しいです => rustifyでWebAssembly - ライフゲームを作る

ElectronでWASMを使ってランダムウォークをしてみます。

embed.ly

準備

Node.js環境をnvmで、Rust環境をrustupで準備します。

% nvm install --lts
% rustup update

rustifyを使うための準備です。

% rustup target add wasm32-unknown-unknown --toolchain nightly
% cargo install --git https://github.com/alexcrichton/wasm-gc

Rustのブロジェクトを作ります。

% cargo init random_walk

Node.jsのプロジェクトも作ります。

% cd random_walk
% mkdir view
% npm init

Browserifyとrustify、Electronのインストールします。

% npm install browserify rustify electron

サンプル動作

まずElectronの準備をします。

https://github.com/electron/electron/blob/master/docs/tutorial/quick-start.md

ここを参考にindex.htmlとmain.jsを作ります。

index.htmlはcanvasタグとscriptタグでbuild.jsを読み込むように変更します。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Random Walk</title>
  </head>
  <body>
    <canvas id="canvas"></canvas>
    <script src="build.js"></script>.
  </body>
</html>

次にrustifyするrender.jsを作ります。

https://github.com/browserify/rustify

サンプルコードのExternalで作ります。

var rust = require('rustify')

var wasm = rust('add-one.rs')

WebAssembly.instantiate(wasm, {})
  .then(function (res) {
    var addOne = res.instance.exports.add_one
    console.log(addOne(41))
    console.log(addOne(68))
  }).catch(function (e) {
    console.error('Creating WASM module failed', e)
  })

viewフォルダにadd-one.rsを作ります。

#[no_mangle]
pub fn add_one(x: i32) -> i32 {
    x + 1
}

browserifyでWASM入りのbuild.jsに変換します。

% cd view
% ./node_modules/.bin/browserify -t rustify render.js > build.js
% ./node_modules/.bin/electron .

DevToolsに42と69が表示されていれば成功。

ランダムウォークを作る

必要なのは乱数だけです。

Rustの乱数はrandクレートですが、extern crateするとrustifyでエラーが出てしまったので実装します。

手軽なのはXorShiftなので検索してスッと実装します。

use std::u32;

pub struct XorShift {
    x: u32,
    y: u32,
    z: u32,
    w: u32,
}

impl XorShift {
    pub fn new() -> XorShift {
        return XorShift {
            x: 123456789,
            y: 362436069,
            z: 521288629,
            w: 88675123,
        };
    }

    pub fn gen(&mut self) -> u32 {
        let _x = self.x;
        let t = _x ^ (_x << 11);
        self.x = self.y;
        self.y = self.z;
        self.z = self.w;
        let _w = self.w;
        self.w = (_w ^ (_w >> 19)) ^ (t ^ (t >> 8));
        self.w
    }

    pub fn gen_norm(&mut self) -> f64 {
        self.gen() as f64 / u32::MAX as f64
    }
}

面倒なのでseedは固定です。0~1に正規化された乱数のほうが判定しやすいのでgen_norm関数を用意しました。

wasmからstructを読み込む方法がわからなかったのでstatic mutのXorShiftを用意してそれから乱数を取り出す関数を作りました。先程のファイルの末尾に以下を追加します。

static mut xorshift: XorShift = XorShift {
    x: 123456789,
    y: 362436069,
    z: 521288629,
    w: 88675123,
};

#[no_mangle]
pub fn rand() -> f64 {
    unsafe { xorshift.gen_norm() }
}

あとはこの乱数を使ってCanvas上にぽちぽちプロットしていけば完成です。

let rust = require('rustify')

let wasm = rust('../src/xorshift.rs')

WebAssembly.instantiate(wasm, {})
  .then(function (res) {
    let rand = res.instance.exports.rand
    let canvas = document.getElementById("canvas")
    canvas.width = window.innerWidth
    canvas.height = window.innerHeight
    let ctx = canvas.getContext("2d")
    // start from center
    let x = ~~canvas.width / 2
    let y = ~~canvas.height / 2
    // unit of step
    const us = 2;
    function render() {
      const r = rand()
      if (r < 0.25) x += us
      else if (r < 0.5) x -= us
      else if (r < 0.75) y += us
      else y -= us
      ctx.fillRect(x, y, us, us)
    }
    setInterval(render, 5)
  }).catch(function (e) {
    console.error('Creating WASM module failed', e)
  })

これをrandom_walk.jsというファイル名で保存して以下のコマンドを実行します。

% cd view
% ./node_modules/.bin/browserify -t rustify random_walk.js > build.js
% ./node_modules/.bin/electron .

random walk

ぽちぽち動いていれば完成。

rustify、らくらくwasm体験できるのでもう少し遊んでみたさが高まりました。

Show comments

Adsense

Share

  • このエントリーをはてなブックマークに追加

About

どこにでもいる平凡なプログラムを書く人間のログ。

Recently

Tags

-->