RubyKaigi 2024 キーノートレポート

Samuel Williamsさん「Leveraging Falcon and Rails for Real-Time Interactivity」 ~RubyKaigi 2024 2日目キーノート

RubyKaigi 2024レポートの第二弾です。今回は2日目の、Samuel Williamsさんによる「Leveraging Falcon and Rails for Real-Time Interactivity」という基調講演です。本発表ではRubyの非同期IOや並行処理を前提としたリアルタイム処理、そしてタイトルにあるようにRailsとの連携が主な話題となります。

前提知識

Samuelさんは過去にRubyKaigi 2019RubyKaigi 2023に登壇しました。

2019年のときは、TCP Serverにおける、よくあるfork()によるマルチプロセスベースの実装やThread.newによるマルチスレッドベースの実装に対する、IO.selectFiber.newによるオーバーヘッドの非常に小さい実装の優位性を説いていました。彼の自作のasync gemとasync-io gemにより、これらが容易に実現できます。

Fiberはマルチスレッドやマルチスレッドとは違い、ノンプリエンプティブ、つまりRubyコード側で明示的にcontext switchingのタイミングを指定しなければならないかわりに効率的です。継続に似ていますが、事前に取得しておいた位置ならどこでも自由にジャンプできるかわりに非効率な継続とは違って、親子関係の外側にのみジャンプすることで効率的なFiber、という性質の違いがあります。よくある用途はイテレータを作るときに、この場合は大抵の場合Fiberを直接使うのではなくそれをwrapしたEnumeratorを使うことが多いです。この時の発表では、nonblocking IOの待機中の処理を一覧走査して完了したものから順次後続処理にジャンプする、といった応用でasync-ioが作られていることが読み取れます。

余談ながらSamuelさんはライブラリだけでなく、ruby本体のFiberやIOまわりにかなりたくさんのコントリビューションをしています。

2023年のときは、その発展形として、HTTP Serverの実装をasync gem系で行ったという発表でした。そこでは、HTTP/2の複数のリクエストをまとめて、それぞれのストリーミング処理をThreadもforkも用いずにfiberなどで実現している点がとりわけ大事な要素でした。自作のasync-httpの紹介と、Rack互換のWebサーバーの実装である自作のFalconの紹介とその活用方法で発表が締められていました。

今年のRubyKaigi 2024での基調講演の発表は、これらの蓄積の上に成り立っています。

ネットワークゲームのインタラクティブ性の歴史

発表の最初は、歴史の話から。WWW以前の古のBBSの時代、実はすでにBBS上で遊ぶためのゲームが作られていました。そのうちの1つであるTrade Wars(1984)というCUIのゲームは、すでにリアルタイムでマルチプレイヤーのinteractionを実現していたそうです。

Samuel Williamsさん

ノートノート:余談ながら、登壇者のSamuelさんはニュージーランド在住です。ニュージーランド訛りの英語で聞き取りの難易度が少し高いかなと想定していましたが、そんなことはなくかなり聞き取りやすい英語でした。

その後1990年代にウェブブラウザができ、これの普及によりオンラインでの人の交流がニッチなものではなく、より広いものになっていきました。必然的に、ウェブ上で遊べるゲームも作られます。そのうちの1つであるEarth: 2025は、1996年にリリースされたブラウザゲームです。

ウェブブラウザでゲームをプレイできるのはとても嬉しいことですが、しかし、インタラクティブな操作性はよいものではありませんでした。リアルタイム処理を行うためには当時の仕様と技術が足りなかったのです。

2005年には、DHHによる初期のRailsが誕生しています。RailsはRackを前提としていて、そのRackはRequest-response modelを基としています。これは一般的なウェブページをブラウジングしていくような操作をするためのものであるため、必然的にゲームが求めている性質であるインタラクティブ性はないわけです。

当初、Rubyはたくさんの並行(concurrent)な処理をするのに適した設計ではなかったことが、ActionCableの設計に間接的に影響を与えています。このActionCableはRailsでリアルタイム処理を行うためのものですが、どの程度の規模まで対応できるかは疑問が残ります。

2012年にMozillaによって作られたBrowserQuestというブラウザ上で動くマルチプレイオンラインゲームは、HTML5 + WebSocketsでリアルタイムでインタラクティブな操作性をついに実現しました。これはJavaScriptとNode.jsで作られています。当時としてはこれが理想的な選択でした。

Async gemとFiber

2017年、本発表者であるSamuelさんがAsyncというgemを開発しました(読み方はAにアクセントを置くエイ・シンク⁠⁠。

例えば以下のようなシンプルな同期的な処理のコードは、表示処理をしている間、ユーザからの新規のリクエストを受け付けることができません。

while request = read_request
  write_response process(request)
end

async gemでAsync()ブロックで囲うだけで、非同期処理が可能になります。

while request = read_request
  Async do
    write_response process(request)
  end
end
Asyncの概念を説明するためのゲーム的な動きで表現するデモ

内部的には各処理をFiber内で行うように変更しています。発表でははっきりと言及していませんでしたが、このprocess()内などの処理がIO待ちをするときに、そのFiberを自動で切り替えて、次のリクエストの処理に移し、どこかのタイミングでもとのIO処理をしていたFiberに戻る、といった挙動をします(ThreadでもGVL(Global VM Lock)されているときは似たような挙動をしますが、そのタイミングは不定です。また、Thread自体のオーバーヘッドがあります。とくにメモリ使用量に関して⁠⁠。

なお、AsyncとRactorは対立する概念ではありません。そもそもAsync gemはFiber based concurrencyという実行モデルを基にしていますが、その実行モデルは単なる様々な要素のうちの一つだと主張しています。

Asyncの構成要素。上部にあるAsyncのロゴに対して「Asyncのロゴのジョークに気づけるかな?」といった発言もあった(同じ発音であるa sink)

IO::EventによってOSごとの非同期IOイベントに対応し、抽象化されたイベントループのインタフェースが利用可能です(著者はここで昔まれによく使っていたEventMachineを思い出しました⁠⁠。

そしてFiber::Scheduler(まさにその実行モデル)やFiber#storage(requestごとのstateを保持する先)など、様々なFiber系とIO系のツールがあり、それらの上に高度に抽象化されたAsync gemがあります。Timeoutなどはasyncの中ではnon blockingになります。

Falcon

Falconはasyncで作られたHTTP Serverで、HTTP 1とHTTP 2、WebSocketをサポートしています。

歴史の話のところで、RackはRequest-response modelを基としていると話をしていましたが、今のRack 3の規格はこれだけでなく双方向のストリーミング通信もサポートしています。FalconはこのRack 3の規格に対応しています。

ここから、実際にFalconを用いてリアルタイムなブラウザゲームを作るデモが行われました。対象はFlappy Birdで、その構成はFalcon + Rails + Live (自作フレームワーク) です。

ノートノート:Flappy Birdはシングルプレイのゲームなので、以後の説明はあくまでシングルプレイとして説明しています。しかし、Webフロントエンド技術で作っているわけではなくRailsでバックエンド側の処理で作っているため、本質的にマルチプレイの開発環境が整った状態であることも念頭においてください。

rails new のあと、まず最初にpumaを消します(会場笑⁠⁠。Falconを使うわけですからね」といった話からゲーム作成のデモがはじまりました(何気にこのデモがRubyKaigi 2024で初のrails newな気がしました⁠⁠。

このあとデモが続くのですが、ぜひ後日配信予定のRubyKaigi公式のYouTubeチャンネルでご覧ください。rails newからはじめて、実際にブラウザで操作可能なFlappy Birdができるまで作り上げています。

このデモでは、Samuelさんが何をしたかというより、何をしなかったかに着目すると特異性が浮かび上がります。つまり、

  • HTML Canvas
  • ゲーム用JavaScriptライブラリ
    • あるいはreactなど
  • Rubyのデスクトップアプリのゲーム用ライブラリ

が一切登場していません! OpalやWasmなどでもなく、普通のRuby on Railsです。

開発デモからキーとなる要素を抜き出すと、次の事柄が挙げられます。

  • Live gemとLive.jsがWebSocketで双方向通信する
  • ゲームのオブジェクトの配置はCSSで行う
  • 物理エンジンはLive::Viewの子クラスで定義
    • 例:重力加速度は-9.8ms/s/s, クリック時の上に持ち上がるときの速度は6m/s
  • Boxのクラスで衝突判定
  • handle()でイベント処理
  • 実は世界が動いているのではなく、パイプが-2m/sで移動している
  • 最終成果物:https://github.com/socketry/flappy-bird

ゲームの完成後は、会場にいるMatz(まつもとさん)を壇上に呼び出して、実際にプレイしてもらっていました。

壇上に召喚されたMatzがゲームをプレイしている様子

Matzが2回に加えてSamuelさんもプレイしましたが、とてもヌルヌルかつ安定に動作していました。

まとめ

Samuelさんは今回の発表で、

  • シンプルがいい、Rubyはわかりやすい
  • ActionCableのAdapter化で互換性
  • Rails 7.2と100%互換
  • Rails 8(未リリース)ならActionCableもばっちり
    • 100%互換までもう少し

といった具合の、リアルタイムなインタラクティブな処理をRailsで行うための新しい選択肢を提案しました。

FalconとLiveに限らず、これまでのasync系gemやそれに伴うRuby本体のFiberの進化の集大成といった具合で、著者は感極まってしまいました。

著者は以前Rails + React + ゲーム用のライブラリいくつかを組み合わせて趣味でちょっとしたゲームを作ったりしたことがありますが、これからはわざわざそんなことせず全部バックエンドのRubyでやれてメンテナビリティが大幅に高まりそうだなと感じました。

おすすめ記事

記事・ニュース一覧