Open17

glooを理解する

泡沫京水泡沫京水

glooがやりやすくしてくれることの一覧

ざっくりいうと

web-sysjs-sysで書きにくいようなものを簡単にしてくれるいいライブラリ!

対応してくれるもの

https://docs.rs/gloo/latest/gloo/

  • console(開発者ツールのコンソールへの表示)
  • dialog(多分確認表示のときに出すもの)
  • events(イベントリスナーを使うときに簡単にするやつ)
    • これが一番助かりそう...!
  • blobs(ファイルやblobを使う時に使うらしい)
  • history(ユニバーサルセッション履歴?と位置情報を扱うためのもの)
    • ユニバーサルセッション履歴ってのがわからん、後で調べる
  • net(HTTPRequestを扱うもの、fetchとかWebSocketを扱う)
  • render(requestAnimationFrameのラッパー)
    • WebGL関連、応用としてWebGPU,WebXRに関連する
  • storage(Web Storageに関連する)
    • そんなに使うか...?
  • timers(SetTimeoutとかsetInterval)
  • utils
    • なんだろうこれ
  • worker(WebWorker)
泡沫京水泡沫京水

ちょっとWorkerを使ってみたい

普通にやってみるとどうなるの?

Cargo.toml
[package]
name = "example"
version = "0.1.0"
edition = "2021"

+ [lib]
+ crate-type = ["cdylib"]

[dependencies]
+ wasm-bindgen = "0.2.92"
console_error_panic_hook = { version = "0.1.6", optional = true }

+ [dependencies.web-sys]
+ version = "0.3.4"
+ features = [
+     'console',
+     'Document',
+     'HtmlElement',
+     'HtmlInputElement',
+     'MessageEvent',
+     'Window',
+     'Worker',
+ ]
泡沫京水泡沫京水

まず、web-sysにおけるworkerの扱いから理解していきたい

https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Worker.html
まず、WorkerのClassがある。

色々なメソッドがあるのがわかるが、いったん必要なのは
以下の5つほどだと考えられるが

  • new
  • onmessage
  • set_onmessage
  • post_message
  • terminate

個人的にはこっち選択した方が書きやすい気もする

  • new
  • onmessage -> add_event_listener_with_event_listener
  • post_message
  • terminate

なぜadd_event_listener_with_event_listenerなのか?

書いててわかりやすかった

泡沫京水泡沫京水

既存のWebWorkerコードを置換していく

まず、JavaScriptで書かれたコード

index.js
const MyWorker = new Worker('worker.js');
const OutputElement = document.getElementById('output');

const buttonEl = document.createElement('button');
buttonEl.textContent = 'worker!';

buttonEl.addEventListener('click', (event) => {
  event.preventDefault();
  MyWorker.postMessage('test');

  setTimeout(() => {
    MyWorker.terminate();
    OutputElement.textContent = 'Worker Terminate';
  }, 2500);
});

MyWorker.onmessage = (event) => {
  // console.log(event.data);
  OutputElement.textContent = event.data;
};

document.getElementById('main').appendChild(buttonEl);
worker.js
self.addEventListener('message', (event) => {
  postMessage(event.data + ' in Worker!');
});
泡沫京水泡沫京水

Fetch Do Code Reading

use gloo_net::http::RequestModeがCorsの制御をする補助をしてくれているように見える
use gloo_net::http::RequestがHTTPメソッドを使ったデータフェッチの補助をするものであるように見える
なんかHonoのappのHTTPメソッドを選ぶやつをみている感覚になった
でもRequestインスタンスを作成している方が近いのだろうか、どちらにせよ書きやすくなっている

泡沫京水泡沫京水

Example Reading

  • clock
    • spawn_localを非同期で実行してる
    • その中で、glooのIntervalStreamで一秒ごとの実行による変更を行っている
  • file-hash(必要だと思った部分だけ見てる)
    • gloo_workerにまとめられている
      • HandlerId,Worker,WorkerScopeが重要なところを占めているのではないか?
  • markdown
    • Workerの使い方がわかるっぽい。これを読むのが早そう
    • use gloo::worker::oneshot::oneshot;というのがある。一回きりのWorker作成か?
    • また、Spawnableというのが出てきた。
      • Worker::spawner().spawn(worker_file_url)という関数でWorker生成している?
      • use gloo::worker::Registrable;というのも出てきた
        • Woker登録?Worker::registrar().register();があるから登録しているんだろうけど、どこへ?
  • prime
    • READMEすらない。なんなんだ。
    • use gloo::worker::reactor::{reactor, ReactorScope};はなんなんだ。
      • Reactorってことはどういうことなん?
泡沫京水泡沫京水

まともなサンプルないの??

なさそうだった(コメントがあんまないし、どこを何を担当しているのかは大体わかったけど、サンプルみて、「ああ!そういうことか!」ってなるのがない)ので、Docを読むことにした

泡沫京水泡沫京水

gloo_workerのDocを読む

  • 使用方法は2つあるらしい
    • Workerトレイトを使う
    • #[oneshot]か#[reactor]マクロを使う
      • この2つのマクロの違いは
        • ONESHOTは各入力が1つの出力を生成する(入力と出力が一対一?)
        • Reactorは入力から出力を生成する(???)
        • わからんかったので原文を載せて考える

The macros provide a function-like syntax to spawn workers and communicate with them. There are two macros:
#[oneshot] - Worker where each input produces a single output.
#[reactor] - Worker that receives input(s) and may produce output(s).

Modules Reading

  • oneshot 1つのインプットに対して1つのアウトプットが生み出される
  • reactor : 多くのインプットを消費し、多くのアウトプットを生み出すことができる
泡沫京水泡沫京水

詳しく読む前

Structs Reading

  • Bincode
    • メッセージエンコーディングらしい。多分、ワーカー間で送受されるメッセージという値のエンコーディングのための機能なのかな?
  • HandlerId
    • 出力をブリッジに送る識別子らしい。
      • ブリッジってなんだよ
      • 識別子ってなんだ?IDのことか?ワーカーのIDのことなのか?
  • WorkerBridge
    • コンポーネントとワーカーの間の接続管理をしてくれる
  • WorkerDestroyHandle
    • ワーカーを落としたら閉じてくれるハンドル
      • ワーカーを落とすってなんだよ
        • dropがなんの意味を指すかわからない、失敗した時の処理ということ?
  • WorkerRegister
    • ワーカーを登録する
      • JavaScriptだとワーカー作ったら勝手に登録されるけど、この場合はそうでないのか?
  • WorkerScope
    • コンポーネントとグローバルスケジューラとの参照を保持
      • グローバルスケジューラってなんだよ
  • WorkerSpawner
    • ワーカーを生成するためのスポナー
      • マイクラのスポナーを考えるとわかりやすいかも
        • これ(クラスのような動きをするもの)からワーカーを生成しますよ!っていう扱いを想定している?

Traits Reading

  • Codec
    • メッセージをエンコードしたりデコードするフォーマット
      • なんかWeb Transportとかそういうやつで面白いことができそうな気もする
      • 何かしらの画像を文字列としてエンコードして、文字列から画像としてデコードする的な?
  • Registrable
    • 公開ワーカーをWeb Workerとして登録できるようにするもの
      • 公開ワーカーってのはなんなんだろう
  • Spawnable
    • スポナーによってスポーンできるワーカー
      • これを使ってワーカーを定義して、これに付属したスポーン関数で生成するのか?
  • Worker
    • Workerの動作を宣言したもの
      • Workerの動きそのものはここで定義されている?
        • 特定のことだけをする専任のWorkerクラスを作りたい時のインターフェースとして活用する?
泡沫京水泡沫京水

詳しく調べていこう!

Modules

oneshot

サンプルコード

use gloo_worker::oneshot::oneshot;
use gloo_worker::Spawnable;

#[oneshot]
async fn Squared(input: u32) -> u32 {
    input.pow(2)
}

let mut squared_bridge = Squared::spawner().spawn("...");
assert_eq!(squared_bridge.run(2).await, 4)
  • 謎の点はなんなんや
    • 何書くかはせめある程度は示しといてくれ

reactor

サンプル

use gloo_worker::reactor::{reactor, ReactorScope};
use gloo_worker::Spawnable;
use futures::{sink::SinkExt, StreamExt};

#[reactor]
async fn SquaredOnDemand(mut scope: ReactorScope<u64, u64>) {
    while let Some(m) = scope.next().await {
        if scope.send(m.pow(2)).await.is_err() {
            break;
        }
    }
}
let mut bridge = SquaredOnDemand::spawner().spawn("...");

bridge.send_input(2);

assert_eq!(bridge.next().await, Some(4));
assert_eq!(bridge.next().await, None);
  • ここも謎の点々がある
    • いいかげんこれがなんなんかおしえてくれ
泡沫京水泡沫京水

謎の点々の仮説

WebAssemblyのビルド時に出てくるJavaScriptファイルじゃないかと推測

  • サンプルみたらjsコードを読み込んでいた。(example_prime_worker.js)
    • ファイル名が、src/binディレクトリ内のexample_prime_worker.rsのファイル名と一致しているため、こいつをビルドしたものを参照しているとみられる。
泡沫京水泡沫京水

ビルド方法がわからん!!

今まで複数ファイルにまたがるWebAssemblyをビルドしたことがなかった