非同期処理への対応
少し前にブログで「wasmではjsと非同期のやり取りをするのが難しい」と書いたのですが、この技術的な課題をなんとか解決できたので、組版の完了したページを(全ページの計算の完了を待たずに)表示できるようになりました。
これによって、すべての作品が0.1 ~ 0.2秒ぐらいで表示されるようになり、大幅に使用感が改善されたと思います。
技術的な部分
実はjsと非同期にやり取りする部分のコードのサンプルは、wasm_bindgenのサンプルに入っています。
https://github.com/rustwasm/wasm-bindgen/tree/main/examples/request-animation-frame
肝要なところを抜き出すと以下のようになります。
pub fn run() { let f = Rc::new(RefCell::new(None)); let g = f.clone(); let mut i = 0; *g.borrow_mut() = Some(Closure::new(|| { if i >= 300 { let _ = f.take(); return; } i += 1; request_animation_frame(f.borrow().as_ref().unwrap()); })); request_animation_frame(g.borrow().as_ref().unwrap()); }
f
とg
はともにクロージャーを保持する参照カウント付きのポインタなのですが、g
は最初にクロージャーを走らせるためだけに使います。
g
はrun
の終了後にクロージャーの参照カウントを減らしますが、もう一方のポインタf
はクロージャーの中で再帰的に参照され続けるため、何もしないとこのクロージャーに対する参照カウントは永遠にゼロにならず、解放されなくなってしまいます。
なのでreqest_animation_frame
の処理を抜けるタイミングで、中身をf.take()
で取り出し、スコープを抜けたところで解放されるようにしているわけですね。
基本は理解できるのですが、nehanの場合、上のサンプルにおけるクロージャーの自由変数が、もうちょっと複雑なデータになっていまして、これだけだと動かない部分があり、ハマっていたのです…
しかしまあ、色々と頑張ったら、なんとか動くようになったので、無事に縦書き文庫のビューアーが(以前と同じく)非同期にページを表示できるようになりました。
ちなみに上のrequest_animation_frame
はもちろんjsの関数ではなく、rust側のweb_sysパッケージが提供している関数でして、たぶんですがrust側のデータをjs側のデータにいちいち「お直し」する処理が含まれると思われるため、あんまりたくさん呼ぶのはよくなさそうです。
なので、縦書き文庫の場合は、数十ページぐらい組版し、まとめて送信することで、jsとrust間の通信回数を減らすようにしています。
導入の成果
非同期処理に切り替えた結果、全ページを組版するのにかかる時間は、一度にすべて組版してから送信する方式よりは(当たり前ですが)少しだけ遅くなりました。
しかし、せいぜい5% ~ 10%ぐらいの遅延なので、許容範囲だと思います。
なにより作品を開いてから「しばらくお待ち下さい」の表示がほとんどなくなり、一瞬で作品が表示されるメリットのほうが遥かに大きいです。
読者の離脱率も、大きく改善されるものと思われます。