wasmに入門してみる【8】

今回で最後になります。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 wasmに入門してみる【6】 wasmに入門してみる【7】 wasmのサイズを小さくする いくつかの設定を追記することで、wasmのサイズを小さくすることができます。 Shrinking .wasm Size - Rust and WebAssembly Rustのリリースビルドみたいなものですね。 まず必要なパッケージをインストールします。 cargo install wasm-opt Cargo.tomlに設定を追記します。 [profile.release] lto = true opt-level = "z" 再ビルドする前に、一度どのくらいの大きさなのか見ておきましょう。 % wc -c pkg/wasm_game_of_life_bg.wasm 25136 pkg/wasm_game_of_life_bg.wasm 大体25KBくらいでした。 では先程インストールしたwasm-optで最適化していきます。 wasm-opt -Oz -o output.wasm pkg/wasm_game_of_life_bg.wasm 最適化できました。 % wc -c output.wasm 24955 output.wasm あんまり変わっていないですね。。 wasm-packでもビルドしてみて、wasmファイルに変化があるか確認してみます。 %wasm-pack build [INFO]: 🎯 Checking for the Wasm target... [INFO]: 🌀 Compiling to Wasm... Compiling wasm-game-of-life v0.1.0 (/home/username/Project/personal/wasm-game-of-life) warning: unused macro definition: `log` --> src/lib.rs:25:14 | 25 | macro_rules! log { | ^^^ | = note: `#[warn(unused_macros)]` on by default warning: `wasm-game-of-life` (lib) generated 1 warning Finished release [optimized] target(s) in 0.41s [INFO]: ⬇️ Installing wasm-bindgen... [INFO]: found wasm-opt at "/home/username/.cargo/bin/wasm-opt" [INFO]: Optimizing wasm binaries with `wasm-opt`... [INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended [INFO]: ✨ Done in 0.86s [INFO]: 📦 Your wasm pkg is ready to publish at /home/username/Project/personal/wasm-game-of-life/pkg. ビルドできたので確認します。 ...

8月 18, 2023 · にあえん

wasmに入門してみる【7】

前回からの続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 wasmに入門してみる【6】 今回はタイムプロファイリングの章です。 Time Profiling - Rust and WebAssembly タイムプロファイリングする ライフゲームのパフォーマンス向上の指針として、FPS(Frames Per Second)タイマーの導入を行います。 window.performance.nowという関数を利用して、 www/index.jsにfpsオブジェクトを実装します。 const fps = new class { constructor() { this.fps = document.getElementById("fps"); this.frames = []; this.lastFrameTimeStamp = performance.now(); } render() { // 最後のフレームレンダリングからの差分時間を1秒あたりのフレーム数に変換する。 const now = performance.now(); const delta = now - this.lastFrameTimeStamp; this.lastFrameTimeStamp = now; const fps = 1 / delta * 1000; // 最新の100個のタイミングだけを保存する。 this.frames.push(fps); if (this.frames.length > 100) { this.frames.shift(); } // 最新の100個のタイムの最大値、最小値、平均値を求めます。 let min = Infinity; let max = -Infinity; let sum = 0; for (let i = 0; i < this.frames.length; i++) { sum += this.frames[i]; min = Math.min(this.frames[i], min); max = Math.max(this.frames[i], max); } let mean = sum / this.frames.length; // fpsに描画する this.fps.textContent = ` Frames per Second: latest = ${Math.round(fps)} avg of last 100 = ${Math.round(mean)} min of last 100 = ${Math.round(min)} max of last 100 = ${Math.round(max)} `.trim(); } }; //...中略 const renderLoop = () => { fps.render(); // 追記 universe.tick(); drawGrid(); drawCells(); animationId = requestAnimationFrame(renderLoop); }; www/index.htmlにも要素を追加します。 ...

8月 17, 2023 · にあえん

wasmに入門してみる【6】

前回からの続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmに入門してみる【5】 ゲームの一時停止と再生機能 まずはゲームの一時停止と再生ができるようにコードを修正します。 実装はJS内のみで完結できます。 www/index.htmlに新しくボタンを追加します。 <body> <noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript> <!-- ボタンを追加 --> <button id="play-pause"></button> <canvas id="game-of-life-canvas"></canvas> <script src="./bootstrap.js"></script> </body> 次にこのボタンに対するハンドラの実装を行います。 // requestAnimationFrameが発行する返り値を格納する変数 // https://developer.mozilla.org/ja/docs/Web/API/window/requestAnimationFrame let animationId = null; // 再生/停止を行うボタンを取得 const playPauseButton = document.getElementById("play-pause"); // 再生時のハンドラを実装 const play = () => { playPauseButton.textContent = "⏸"; renderLoop(); }; // 停止時のハンドラを実装 const pause = () => { playPauseButton.textContent = "▶"; cancelAnimationFrame(animationId); animationId = null; }; // 停止しているか判定する関数 const isPaused = () => { return animationId === null; }; // 再生/停止ボタンにイベントリスナーを追加 playPauseButton.addEventListener("click", (event) => { if (isPaused()) { play(); } else { pause(); } }); const renderLoop = () => { universe.tick(); drawGrid(); drawCells(); animationId = requestAnimationFrame(renderLoop); // requestAnimationFrameの返り値を設定 }; drawGrid(); drawCells(); play(); // requestAnimationFrame(renderLoop);をplay関数に置き換え これで再生/停止ボタンが使えるようになります! ...

8月 12, 2023 · にあえん

wasmに入門してみる【5】

シリーズ化しているwasm入門です。 (wasmのチュートリアルをやったという実績残しと備忘録を兼ねてやっています。真新しいことはないので悪しからず。。) wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 テストの実装 これまでで実装してきたライフゲームのテストを実装します。 Testing Life - Rust and WebAssembly まずはUniverseの幅と高さのセッターを実装します。 #[wasm_bindgen] impl Universe { // ... /// Universeの幅を設定します。 /// /// すべてのセルの状態を死んだ状態にします。 pub fn set_width(&mut self, width: u32) { self.width = width; self.cells = (0..width * self.height).map(|_i| Cell::Dead).collect(); } /// Universeの高さを設定します。 /// /// すべてのセルの状態を死んだ状態にします。 pub fn set_height(&mut self, height: u32) { self.height = height; self.cells = (0..self.width * height).map(|_i| Cell::Dead).collect(); } // ... } 次に、セルのゲッターとセッターを実装します。 ここで、セルのゲッターを#[wasm_bindgen]マクロのあるUniverseに実装するとエラーになります。 #[wasm_bindgen] impl Universe { // ... /// Get the dead and alive values of the entire universe. pub fn get_cells(&self) -> &[Cell] { &self.cells } // ... } この状態でビルドします。 ...

8月 11, 2023 · にあえん

wasmに入門してみる【4】

前回の続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【3】 キャンバスタグを使って描画する 前回はpreタグを使って文字列で描画していました。 今回はキャンバスタグで描画するようにします。 前回preタグで宣言していた部分をそのままcanvasにします。 <canvas id="game-of-life-canvas"></canvas> Universeに高さ、幅、セルを返却するゲッターを実装します。 これはJSに公開されるように、#[wasm_bindgen]マクロを使用する必要があります。 #[wasm_bindgen] impl Universe { // ... pub fn width(&self) -> u32 { self.width } pub fn height(&self) -> u32 { self.height } pub fn cells(&self) -> *const Cell { self.cells.as_ptr() } // ... } 再ビルドもしておきましょう。 wasm-pack build 次にJSを修正します。 以前までの実装を削除し、新たに以下の実装を追加します。 セルのサイズや、盤上の色を定義します。 import { Universe, Cell } from "wasm-game-of-life"; const CELL_SIZE = 5; // px const GRID_COLOR = "#CCCCCC"; const DEAD_COLOR = "#FFFFFF"; const ALIVE_COLOR = "#000000"; 次に、マスを成形するためのグリッドを描画する関数を実装します。 // グリッド(マスを形成するための線)を描画します 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(); }; 次の実装はかなり興味深いです。 ...

8月 10, 2023 · にあえん

wasmに入門してみる【3】

前回の続きです。 wasmに入門してみる【1】 wasmに入門してみる【2】 wasmに入門してみる【4】 ライフゲームの実装(Rust側) うっすらイメージがついてきたところで実装に入ります。 まずは、src/lib.rsの内容を以下の実装で上書きします。 use wasm_bindgen::prelude::*; // セルの状態を表す列挙型を定義 #[wasm_bindgen] #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Cell { Dead = 0, Alive = 1, } #[repr(u8)]を定義すると、Enumのプロパティが1バイトとして表現できます。 また、生存を1として表現することで、周囲のセルの生存状況を単純な加算で取得することができます。 続けてライフゲームの盤上である宇宙を表現します。 // ライフゲームの盤上(宇宙)の表現を行う構造体 #[wasm_bindgen] pub struct Universe { width: u32, // 横幅 height: u32, // 縦幅 cells: Vec<Cell>, // 盤上の生存状況 } 特定の行と列にアクセスするために、前回説明した式を実装します。 index(row, column, universe) = row * width(universe) + column これは宇宙の実装として用意します。 impl Universe { fn get_index(&self, row: u32, column: u32) -> usize { (row * self.width + column) as usize } } また、セルの次の状態を計算するため、生存しているセルをカウントする関数もUniverseに実装してあげます。 ...

8月 9, 2023 · にあえん

wasmに入門してみる【2】

前回からの続きです。 wasmに入門してみる【1】 wasmに入門してみる【3】 wasmに入門してみる【4】 作成したCargoパッケージをnpmパッケージから使う さて、前回までの状態だとhello-wasm-packを使っているので、せっかくRustで作ったwasmを使えていません。 使用できるようにするため、まずは前回wasm-pack buildで作成したpkgディレクトリに移動し、npm linkというコマンドを実行します。 このコマンドを使うと、npmパッケージをパブリッシュせずに相互に参照することが可能になるそうです。へー。 {{ url_title “https://qiita.com/k_kind/items/b3d04bd5a47ee26b6e2d" }} さっそく実行してみます。 % npm link added 1 package, and audited 3 packages in 713ms found 0 vulnerabilities 実行すると、npm install --globalで指定した際のディレクトリにシンボリックリンクが作成されます。 % ls -l /home/username/.anyenv/envs/nodenv/versions/17.9.0/lib/node_modules lrwxrwxrwx 1 username username 59 8月 8 22:56 wasm-game-of-life -> ../../../../../../../Project/personal/wasm-game-of-life/pkg nodenvを使っているのでこのようなディレクトリになっています。 nodenvはバージョンごとにグローバルなパッケージがインストールされるディレクトリが変わるので、もしnodenvで開発する場合はnodeのバージョンはpkgとwwwの両方のディレクトリで合わせる必要があります。 .node-versionの配置を忘れないようにしないといけませんね! では続けて、wwwディレクトリに移動して、先程作成したリンクへの依存を追加していきます。 npm link wasm-game-of-life このコマンドを実行すると、node_modulesの中にwasm-game-of-lifeへのシンボリックリンクが作成され、先程作成したシンボリックリンクと紐付きます。 つまり、pkgへのシンボリックリンクを持つことになります。 # wwwディレクトリで実行 % ls -l node_modules | grep wasm-game-of-life lrwxrwxrwx 1 username username 9 8月 8 23:06 wasm-game-of-life -> ../../pkg 最後に、www/index.jsのインポートをwasm-game-of-lifeモジュールを参照するようにしてあげれば完成です! ...

8月 8, 2023 · にあえん

wasmに入門してみる【1】

社内でwasmを触っているプロジェクトの人と話して、羨ましかったので入門してみる。 wasmに入門してみる【2】 wasmに入門してみる【3】 wasmに入門してみる【4】 wasmとは ブラウザ上で高速に動作するバイナリファイルのことです。 WebAssemblyとは?何ができるのか?できないことやユースケースをわかりやすく解説 |パーソルクロステクノロジー 主にC++やRustで実装されています。 2023-08-07のwasmの対応状況はこんな感じです。 safariやモバイル版operaを除いて、ほぼほぼ対応していますね。 WebAssembly | Can I use... Support tables for HTML5, CSS3, etc Cargoプロジェクトを作成する とにかく実装したかったので、Rustで実装してみます。 公式にチュートリアルが用意してあったので、これをなぞる形でやっていこうと思います。 チュートリアル - Rust and WebAssembly チュートリアルを実施するに際して、以下の3つのツールが必要なのでインストールしておきます。 私の場合はcargo-generateとwasm-packがなかったのでインストールしておきました。 wasm-pack cargo-generate npm cargo-generateを使用して、以下のgithubリポジトリをベースにパッケージを作成します。 cargo generate --git https://github.com/rustwasm/wasm-pack-template 名前は以下のようにしておきます。 wasm-game-of-life これでwasmのチュートリアルパッケージが生成されました。 Cargo.tomlの中身は以下のようになっています。 [package] name = "wasm-game-of-life" version = "0.1.0" edition = "2018" [lib] crate-type = ["cdylib", "rlib"] [features] default = ["console_error_panic_hook"] [dependencies] wasm-bindgen = "0.2.84" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. console_error_panic_hook = { version = "0.1.7", optional = true } [dev-dependencies] wasm-bindgen-test = "0.3.34" [profile.release] # Tell `rustc` to optimize for small code size. opt-level = "s" 見てみると、crate-typeというセクションがありますね。 ...

8月 7, 2023 · にあえん