anti scroll

ブラウザと小説の新しい関係を模索する

作品の長さに関係なく、高速に作品が表示されるようになりました

非同期処理への対応

少し前にブログで「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());
}

fgはともにクロージャーを保持する参照カウント付きのポインタなのですが、gは最初にクロージャーを走らせるためだけに使います。

grunの終了後にクロージャーの参照カウントを減らしますが、もう一方のポインタfクロージャーの中で再帰的に参照され続けるため、何もしないとこのクロージャーに対する参照カウントは永遠にゼロにならず、解放されなくなってしまいます。

なのでreqest_animation_frameの処理を抜けるタイミングで、中身をf.take()で取り出し、スコープを抜けたところで解放されるようにしているわけですね。

基本は理解できるのですが、nehanの場合、上のサンプルにおけるクロージャーの自由変数が、もうちょっと複雑なデータになっていまして、これだけだと動かない部分があり、ハマっていたのです…

しかしまあ、色々と頑張ったら、なんとか動くようになったので、無事に縦書き文庫のビューアーが(以前と同じく)非同期にページを表示できるようになりました。

ちなみに上のrequest_animation_frameはもちろんjsの関数ではなく、rust側のweb_sysパッケージが提供している関数でして、たぶんですがrust側のデータをjs側のデータにいちいち「お直し」する処理が含まれると思われるため、あんまりたくさん呼ぶのはよくなさそうです。

なので、縦書き文庫の場合は、数十ページぐらい組版し、まとめて送信することで、jsとrust間の通信回数を減らすようにしています。

導入の成果

非同期処理に切り替えた結果、全ページを組版するのにかかる時間は、一度にすべて組版してから送信する方式よりは(当たり前ですが)少しだけ遅くなりました。

しかし、せいぜい5% ~ 10%ぐらいの遅延なので、許容範囲だと思います。

なにより作品を開いてから「しばらくお待ち下さい」の表示がほとんどなくなり、一瞬で作品が表示されるメリットのほうが遥かに大きいです。

読者の離脱率も、大きく改善されるものと思われます。