RubyKaigi 2024レポートの第二弾です。今回は2日目の、Samuel Williamsさんによる
前提知識
Samuelさんは過去にRubyKaigi 2019とRubyKaigi 2023に登壇しました。
2019年のときは、TCP Serverにおける、よくあるfork()
によるマルチプロセスベースの実装やThread.
によるマルチスレッドベースの実装に対する、IO.
とFiber.
によるオーバーヘッドの非常に小さい実装の優位性を説いていました。彼の自作の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/
今年のRubyKaigi 2024での基調講演の発表は、これらの蓄積の上に成り立っています。
ネットワークゲームのインタラクティブ性の歴史
発表の最初は、歴史の話から。WWW以前の古のBBSの時代、実はすでにBBS上で遊ぶためのゲームが作られていました。そのうちの1つであるTrade Wars
ノート:ノート:余談ながら、登壇者のSamuelさんはニュージーランド在住です。ニュージーランド訛りの英語で聞き取りの難易度が少し高いかなと想定していましたが、そんなことはなくかなり聞き取りやすい英語でした。
その後1990年代にウェブブラウザができ、これの普及によりオンラインでの人の交流がニッチなものではなく、より広いものになっていきました。必然的に、ウェブ上で遊べるゲームも作られます。そのうちの1つであるEarth: 2025は、1996年にリリースされたブラウザゲームです。
ウェブブラウザでゲームをプレイできるのはとても嬉しいことですが、しかし、インタラクティブな操作性はよいものではありませんでした。リアルタイム処理を行うためには当時の仕様と技術が足りなかったのです。
2005年には、DHHによる初期のRailsが誕生しています。RailsはRackを前提としていて、そのRackはRequest-response modelを基としています。これは一般的なウェブページをブラウジングしていくような操作をするためのものであるため、必然的にゲームが求めている性質であるインタラクティブ性はないわけです。
当初、Rubyはたくさんの並行
2012年にMozillaによって作られたBrowserQuestというブラウザ上で動くマルチプレイオンラインゲームは、HTML5 + WebSocketsでリアルタイムでインタラクティブな操作性をついに実現しました。これはJavaScriptとNode.
Async gemとFiber
2017年、本発表者であるSamuelさんがAsyncというgemを開発しました
例えば以下のようなシンプルな同期的な処理のコードは、表示処理をしている間、ユーザからの新規のリクエストを受け付けることができません。
while request = read_request
write_response process(request)
end
async gemでAsync()
ブロックで囲うだけで、非同期処理が可能になります。
while request = read_request
Async do
write_response process(request)
end
end
内部的には各処理をFiber内で行うように変更しています。発表でははっきりと言及していませんでしたが、このprocess()
内などの処理がIO待ちをするときに、そのFiberを自動で切り替えて、次のリクエストの処理に移し、どこかのタイミングでもとのIO処理をしていたFiberに戻る、といった挙動をします
なお、AsyncとRactorは対立する概念ではありません。そもそもAsync gemはFiber based concurrencyという実行モデルを基にしていますが、その実行モデルは単なる様々な要素のうちの一つだと主張しています。
IO::
そしてFiber::
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を消します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
- 例:重力加速度は-9.
- Boxのクラスで衝突判定
handle()
でイベント処理- 実は世界が動いているのではなく、パイプが-2m/
sで移動している - 最終成果物:https://
github. com/ socketry/ flappy-bird
ゲームの完成後は、会場にいる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でやれてメンテナビリティが大幅に高まりそうだなと感じました。