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. ビルドできたので確認します。 ...

2023年8月18日 · にあえん

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にも要素を追加します。 ...

2023年8月17日 · にあえん

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関数に置き換え これで再生/停止ボタンが使えるようになります! ...

2023年8月12日 · にあえん

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 } // ... } この状態でビルドします。 ...

2023年8月11日 · にあえん

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(); }; 次の実装はかなり興味深いです。 ...

2023年8月10日 · にあえん

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に実装してあげます。 ...

2023年8月9日 · にあえん

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モジュールを参照するようにしてあげれば完成です! ...

2023年8月8日 · にあえん

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というセクションがありますね。 ...

2023年8月7日 · にあえん

prisma-client-rust + actix-webで簡単RESTクライアント実装

以前、prisma-clientでGraphQLに入門しました。 せっかくRustを触りだしたので、prismaでRustクライアントを出力できるprisma-client-rustを使用してRESTアプリケーションを構築してみたいと思います。 GitHub - Brendonovich/prisma-client-rust: Type-safe database access for Rust Cargoパッケージの初期化 まずは元になるパッケージを作成します。 % cargo new prisma-for-rest Created binary (application) `prisma-for-rest` package せっかくこの前学んだので、このパッケージに移動して安定版の最新バージョンを参照するrust-toolchain.tomlを作っておきます。 [toolchain] channel = "stable-2023-07-12" 続けてデータベース用のパッケージを作成します。 cargo new database データベース用のパッケージに移動し、prisma-client-rustを依存関係に追加します。 [dependencies] prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.8" } prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.8" } database/src/main.rsに、prisma-client-rust用のCLIを操作するための実装を追記します。 fn main() { prisma_client_rust_cli::run(); } prisma-client-rust-cliの呼び出しを簡素化するため、.cargo/config.tomlというファイルを作成して以下のような設定を追記します。 [alias] prisma = "run --" これにより、cargo prismaでprisma-client-rust-cliの呼び出しが可能になりました。 % cargo prisma --help Finished dev [unoptimized + debuginfo] target(s) in 0.49s Running `target/debug/database --help` ◭ Prisma is a modern DB toolkit to query, migrate and model your database (https://prisma.io) Usage $ prisma [command] Commands init Set up Prisma for your app generate Generate artifacts (e.g. Prisma Client) db Manage your database schema and lifecycle migrate Migrate your database studio Browse your data with Prisma Studio validate Validate your Prisma schema format Format your Prisma schema Flags --preview-feature Run Preview Prisma commands Examples Set up a new Prisma project $ prisma init Generate artifacts (e.g. Prisma Client) $ prisma generate Browse your data $ prisma studio Create migrations from your Prisma schema, apply them to the database, generate artifacts (e.g. Prisma Client) $ prisma migrate dev Pull the schema from an existing database, updating the Prisma schema $ prisma db pull Push the Prisma schema state to the database $ prisma db push Validate your Prisma schema $ prisma validate Format your Prisma schema $ prisma format prismaスキーマの初期化 Prisma CLIが使えるようになったので、CLIからPrismaの初期化を行います。 ...

2023年8月5日 · にあえん

Rustのworkspaceの使い方

またまたRustについてです。 今回はworkspace機能についてよくわからなかったので勉強がてら書いていこうと思います。 Rustのワークスペースとは 大元であるCargoパッケージ内に複数パッケージがある場合に、相互にパッケージを扱うための機能です。 大きくなってきたプロジェクトで使用します。 Cargoのワークスペース - The Rust Programming Language 日本語版 公式のチュートリアルをなぞる形になりますが、やってみます。 まずはワークスペース機能のチュートリアルを行うCargoパッケージを作成します。 cargo new rust-workspace-playground 作成したパッケージに移動し、更にadderというパッケージを作成します。 cargo new adder この時点でcargo buildしても、adderの依存性をrust-workspace-playgroundには記述していないため、targetに出力されるのはrust-workspace-playgroundにあるhello worldプログラムのみです。 # cargo build実行後のtargetディレクトリの中身 target ├── CACHEDIR.TAG └── debug ├── build ├── deps │ ├── librust_workspace_playground-25fa51ac7f690b9a.rmeta │ ├── librust_workspace_playground-5cbc2718815bd853.rmeta │ ├── librust_workspace_playground-9f7880f2efd546b2.rmeta │ ├── rust_workspace_playground-01238b030f757f04 │ ├── rust_workspace_playground-01238b030f757f04.d │ ├── rust_workspace_playground-25fa51ac7f690b9a.d │ ├── rust_workspace_playground-5cbc2718815bd853.d │ └── rust_workspace_playground-9f7880f2efd546b2.d ├── examples ├── incremental │ ├── rust_workspace_playground-1xmwk187vqtbq │ ├── rust_workspace_playground-3oehp425tljca │ ├── rust_workspace_playground-3tb4u1zdv4nwu ├── rust-workspace-playground └── rust-workspace-playground.d ここで、rust-workspace-playgroundのCargo.tomlに以下のworkspaceセクションを追加します。 ...

2023年8月5日 · にあえん