非同期処理への対応
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%ぐらいの遅延なので、許容範囲です。
作品を開いてから「しばらくお待ち下さい」の表示がなくなり、一瞬で作品が表示されるので、読者の離脱率も大きく改善されるものと思われます。