🌊

WebAssemblyでSleep: Rustが征く(8)

2021/10/04に公開

wasm-timer::Delayが使えそう

関連記事:

Rustが征くシリーズ過去記事

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を返す流れで実装

./src/lib.rs
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秒間止まっているかを確認する。

./src/main.mjs
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 呼び出しを変更する

rollup.config.js
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呼び出しする

./dist/index.html
<!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が書き出したらしい。

dist/js/bundle.js
import * as __wbg_star0 from 'env';

🤔 うーん

さらに調べてみたが、
以下のパッケージを追加すれば良いらしい。

Cargo.toml
[dependencies]
・・・
parking_lot = { version = "0.11.1", features = ["wasm-bindgen"]}

なんかしらんが動いたっぽい。

参考:
https://github.com/rustwasm/wasm-bindgen/issues/2215#issuecomment-796244209

Discussion