WebAssemblyでSleep: Rustが征く(8)
wasm-timer::Delayが使えそう
関連記事:
Rustが征くシリーズ過去記事
- WSL2で作るWindows開発環境: Rustが征く(1)
- wasmerでWebAssemblyの門を叩く: Rustが征く(2)
- JavaScriptからWebAssemblyの関数を呼び出す: Rustが征く(3)
- TypeScriptとRollupでWebassemblyを稼働させる: Rustが征く(4)
- SvelteでWebAssemblyでTypeScriptでRollupで: Rustが征く(5)
- WASMのサイズでかすぎね?: Rustが征く(6)
- Webassemblyマルチスレッド用crateを調べる: Rustが征く(7)
- WebAssemblyでSleep: Rustが征く(8) ← イマココ
Wasmで使えるかどうかってどうやって見分ければいいのかしら。
------------------- ↓ 前書はここから ↓-------------------
非同期処理のサンプルを作ろうと、
WebAssembly上でランダム時間でsleepをかけて、
時間差動作を表現しよう思った。
コードにするとこんな感じ?
use std::time::Duration;
use std::{thread, time};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn sleep_millis(numbers: u16) {
let millis: u64 = u64::from(numbers);
let ten_millis: Duration = time::Duration::from_millis(millis);
thread::sleep(ten_millis);
}
実行してみると、
1a8d8036:0x15b9 Uncaught (in promise) RuntimeError: unreachable
at <anonymous>:wasm-function[5]:0x15b9
at <anonymous>:wasm-function[13]:0x1876
at <anonymous>:wasm-function[11]:0x1823
at <anonymous>:wasm-function[12]:0x1851
at sleep_millis (<anonymous>:wasm-function[7]:0x1710)
at Object.sleep_millis (index.js:8)
at App.svelte:7
at Generator.next (<anonymous>)
at App.svelte:1
at new Promise (<anonymous>)
sleep行を消すとエラーは出ない。
(゚_゚) マジカYO・・・
調べてみるとRustで実行する分には問題ないが、
WebAssembly上で実行すると、
単純にsleepを使うのは無理っぽい。
中にはJavaScriptをロードするなんてアクロバティックなことをしている人
もいた。
サンプルなら良いけど現実利用はありえない。
なんか良いものないかしらと調べてみると
割と使えそうなものがあった。
それが
wasm-timer::Delay
ヾ(・ω<)ノ" 三三三● ⅱⅲ コロコロ♪
------------------- ↓ 本題はここから ↓-------------------
プロジェクト作成
rollup.js+WebAssemblyの組み合わせのテンプレートからプロジェクトを作成。
テンプレートは使用パッケージのバージョンが古くなっているので、
最新のものにしておく。
cargo install cargo-edit wasm-pack
npx degit wasm-tool/rollup-plugin-rust/example wasm-sleep
cd wasm-sleep
npx npm-check-updates -u
npm i
cargo upgrade
cargo check
追加のパッケージをインストール
- js-sys
- JavaScript側のパラメータにアクセスする
- 非同期処理に必要
- wasm-bindgen-futures
- RustのFutureとJavaScriptのPromiseを橋渡しする
- 非同期動作時に必要
- wasm-timer
- タイマー関連のパッケージ
- parking_lot
- タイマー使用時に発生する余計なコードをカット
cargo add js-sys wasm-bindgen-futures wasm-timer
cargo add parking_lot --features wasm-bindgen
タイマー関数作成
msecで入力、Promiseを返す流れで実装
use std::time::Duration;
use wasm_bindgen::prelude::*;
use wasm_timer::Delay;
#[wasm_bindgen]
pub async fn sleep_millis(numbers: u16) -> js_sys::Promise {
let millis: u64 = u64::from(numbers);
Delay::new(Duration::from_millis(millis)).await.unwrap();
let promise = js_sys::Promise::resolve(&numbers.into());
return promise;
}
フロント側の実装
sleep動作を確認するフロント側を整備
consoleに実行時間を表示することで、
sleep_millisで5秒間止まっているかを確認する。
import wasm from "../Cargo.toml";
(async () => {
const modules = await wasm()
console.time("sleep_millis")
await modules.sleep_millis(5000)
console.timeEnd("sleep_millis")
})()
export default wasm
テンプレート側の調整
rollup.jsの設定を調整
最後にビルドツールの設定を調整する。
フロント用のコードを用意したので、
Cargo.toml
呼び出しを変更する
import rust from "@wasm-tool/rollup-plugin-rust";
export default {
input: {
- example: "Cargo.toml",
+ example: "./src/main.mjs",
},
output: {
dir: "dist/js",
- format: "iife",
sourcemap: true,
},
plugins: [
rust({
serverPath: "js/",
}),
],
};
HTMLの調整
HTMLをES6対応させるために、
scriptタグをmodule呼び出しする
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
- <script src="js/example.js"></script>
+ <script src="js/example.js" type="module"></script>
</body>
</html>
ビルド
npm run build
npx http-server dist
実行
ブラウザでアクセスして5秒後にコンソールに以下のものが出る
|・∀・| ピタッ!
await外せば非同期タイマーに。
JSのTimeoutと同じ感じか。
------------------- ↓ 後書はここから ↓-------------------
import * as __wbg_star0 from 'env'ってなんだ?
謎のエラーがでた
Rollup側にはワーニングが
調べてみると、
生成されたJavaScriptファイルの先頭にヘンテコな文字列が。
どうやらwasm-packが書き出したらしい。
import * as __wbg_star0 from 'env';
🤔 うーん
さらに調べてみたが、
以下のパッケージを追加すれば良いらしい。
[dependencies]
・・・
parking_lot = { version = "0.11.1", features = ["wasm-bindgen"]}
なんかしらんが動いたっぽい。
参考:
Discussion