自作3Dライブラリ次回作の新しい武器を探してたらElixirに出会いました


はじめに

@emadurandalと申します。泣く子も黙るQiitaポエマーです。
もともとコンピュータグラフィックス畑の出身で、ここ数年は自作WebGLライブラリをOSSで開発する活動をしております。

今までやってきたこと

最初に作り始めたのはGLBoostというWebGLライブラリになります。
第一作目ですが、ありがたくも複数の会社さんに商用採用していただく実績に恵まれ、貴重な体験となりました。
しかしECMAScript2015であるため型チェックが効かず、またテストコードもろくにないためデグレが多発。開発効率に大きな課題がありました。

今開発しているのは、2世代目のRhodoniteというTypeScriptベースのWebGLライブラリになります。
こちらはJestとPuppeteerによるレンダリング画像の比較ベースで、ビジュアルテストをGithub Actionで回しています。
GLBoostと比べれば、OSSプロジェクトとしては大きく改善したように思います。こちらもすでにいくつかの商用案件で使われています。

Rhodoniteの詳細や開発の経緯については、今年のWebグラフィックスアドベントカレンダー24日目の以下の記事にまとめています。

自作WebGLライブラリ: GLBoostの次は「Rhodonite(ロードナイト)」

技術的な課題

3Dライブラリの開発競争はまさに世界規模で行われており、世にはThree.jsやBabylonJSなどの優れたライブラリが多くあります。
マンパワーが限られる自分たちとしては、追いつくためにさらに次世代の設計が必要と考えていました。

そうした中で私はふと、一つのQiita記事を思い出したのです。

Elixirとの出会い

それは2018年のWebGL Advent Calendarで、zacky さんこと、北九州市立大学 山崎先生が書かれた記事でした。

WebGL / WebGPU + Hastega / Elixir / Phoenix で分散/エッジ・コンピューティング

2018年、最初に読んだときは内容の高度さとグラフィクス寄りでないことから、実はすぐそっ閉じしてしまっていました(ゴメンナサイ!)

記事を再度舐めるように読んでみた所、山崎先生はElixerの関数型言語としての記述性を活かし、それをGPGPUのSIMD演算にスマートに変換するプロダクトを開発されていました。
この当時の記事ではHastegaという名前ですが、今はPelemeyという名前になっています。

記事の内容はご覧の通り、WASM(WebAssembly)やWebGPU1の話が2018年にもかかわらず既に入っており、
Rhodoniteの次はネイティブ言語&WASM&WebGPU&並列処理指向でやろうと思っていた自分の方向性にマッチするものでした。

また同時に、こうしたアグレッシブなことをやろうとしているElixir勢というのは、一体どんな人達なんだろう、という興味も湧いてきたのです。

こうしてElixirという言語自体にも興味が芽生え、気がついたら私は日本のElixirコミュニティの皆さんと交流をさせていただくようになっていました。

Elixirという言語

Elixirを「自分とはあまり関係なさそう」と思っていた方は結構いらっしゃるのではないでしょうか。
最近のRustやSwiftなども、部分的に関数型の機能が導入されています。
そのままスルーでいいんじゃないか、と私もそれまで思っていました。

しかし前述の出来事は自分にとって、再考のきっかけとなりました。
Elixirを始めとした各言語を一度、比較検討してみたのです。TypeScriptなどの非関数型も含めて、様々考えました。

調査を続けて思ったのは、関数型言語としての部分よりも、その言語ならではの特徴や周辺環境の充実ぶりの方が評価上の決め手となりえる、ということです。
一例ですが、あのRust言語が評価されているのも、優れたパッケージ・ビルドのCargoなどテストスイートなど、標準の周辺環境が極めて充実している点があるでしょう。

私がElixirに惹かれたのは、以下の点でした。

  • 並列処理がアクターモデルであり、またスレッド(軽量プロセス)生成が高速であること。
  • GCはあるものの、プロセスごとに独立で、GC動作も極めて軽いこと。
  • コミュニティが活発で更に成長しそうであること。
  • 関数型言語の中では、開発環境周りのエコシステムも比較的リッチであること
  • マルチコアのスケール性が非常に高いこと(頭打ちにならない)

OCamlなども検討したのですが、マルチコア並列性のサポートやエコシステム周りが比較的弱そうで見送ることになりました(それ以外の部分はかなり高評価でしたが)。

ゲーム用途なども見据えた3Dライブラリの開発でGCがある点は少し気になったのですが、自分が考える規模であれば、実質問題にならない程度です。
Web3Dライブラリでは未だにシングルスレッドが処理の主流なので、並列処理に一気に対応したい自分としてはElixirの並列モデルはとても気に入りました。

しかし、あとになって気づいた問題がありました。

Elixirって動的言語じゃん…

そう、ErlangのVM上で動作する言語なのです。調査を進めると、どうも最初は静的言語にしたかったが、ホットスワッピングなどのサポートなどの関係で動的にせざるを得なかった、みたいな事がどこかに書いてありました(詳しい方、教えて下さい)。

山崎先生らが進めていらっしゃるZEAMなどの、野心的な別のVM開発の試みもあるようで、それらにも期待しているのですが、とはいえゲームエンジンの言語選択でGCやVMによる制限はかなり気になる所です(あれ、さっきこれくらいのGCなら問題ないとか言ってなかったか?)。

実際、Elixirはその開発効率や耐障害性の高さから、オンラインゲームのサーバープログラム開発にはよく使われるそうです。しかし、ゲームエンジン本体の開発に使うという例は確かに聞いたことがありません。そもそもElixirで書いたライブラリってWebAssemblyにできるのか……?(汗)

まぁもともと、私も本当にヘビーな計算部分や、エンジンのコア部分は他のRustなどのネイティブ言語で書くことを想定していました。
つまり、もしElixirをエンジンの一部として使うなら、VMの上で動作するElixirから各ネイティブ言語で作ったコンポーネントをロードするようなアプローチが考えられそうです。

そこで、今回はElixirド初心者ながら、Elixirからネイティブコンポーネントをロードする技術選択肢について調べました。
と言っても熟練の方がすでに書かれた記事などを漁っただけなので、そこはご勘弁ください...

NIF (Native Implemented Function)

共有ライブラリを Erlang VM に読み込んで(なのでErlang VMが主体)コード実行する方法です。
APIがシンプルなのが利点ですが、共有ライブラリ側の関数をそのまま呼び出すので、同期的な呼び出し関係になります。

利点

  • 呼び出し効率が高い
  • 共有ライブラリ側でクラッシュが起きると、Erlang VMまで波及して落ちることがある。
  • 共有ライブラリ側を呼び出したら、Erlang VM側に1m秒以内に制御が戻らないといけないという制約がある

Port Linked-In Driver

NIFと同様に共有ライブラリをErlang VMにロードして実行する形式のようなのです。NIFとの違いは非同期メインであること。同期的なNIFよりもコーディングはやや煩雑になりますが、NIFの1m秒制約からも開放されますし、非同期であることはスループットの向上や並列性の実現に繋がるのではないかと思います。

Port Driver

外部プログラムをErlang VMとは別プロセスとして起動し、標準入出力を通じてデータ連携する方法です。遅そう。そして連携のためのコーディングも慣れが必要そうですね。
ゲームのコア動作としてはとても使えませんが、逆にプロセスが完全に別という利点は活かせる用途がありそうです。

ゲームエンジンをコード的に内包する形でGUIエディタやプロファイラーツールを作ってしまうと、エンジンランタイムのクラッシュ時にエディタやプロファイラ部分まで一緒に落ちてしまいます。
この問題はゲーム開発の現場では、作業効率上かなり問題になることがあります。しかしこうしたプロセス分離のアプローチなら、どちらがクラッシュしても作業がやり直しになることを防げそうですね。

NIF, Port Linked-In Driver, Port Driver3つとも、どれもきちんと棲み分けがなされていそうで良いですね。

自分のライブラリ開発にどう使えるか

Elixirを調べるなかで、やはり思ったのはVMベースの動的言語であるところがどうしてもネックだということです。
正直、エンジンのコア部分としては採用しにくいところがあります。
おそらく、大半の部分はRustで作るでしょう。

しかし、Elixirのコードとしての高生産性や高性能な部分を使わないのはたいへんに惜しい気がします。

おそらく、利用するとすれば開発支援のためのツール開発やサーバー開発かもしれません。
ElixirにはPhoenix(Webフレームワーク)やNerves(IoTフレームワーク)などの優れた支援環境が豊富にあります。

ゲーム開発と言ってももはやゲームのコアプログラムだけでは成り立たない世界ですので、今後も色々とElixirの使い所を考えていきたいと思います。

そしてゲームエンジン部分にどうにかして使えないか、という部分もまだ諦めたわけではありません。

商用エンジンでも、アプリケーションコードやセットアップコードの生成処理がC#などの非ネイティブ言語で書かれていたりすることはよくありますからね。

もちろん「コア部分にもこうすればいけるんじゃない?」というご意見があればぜひ拝見したいですし、そこはぜひ山崎先生や @piacerexさんといった凄腕Elixir勢の大先輩方から教えを請いたいと思っております。

まとめ

まだElixirを触って日が浅いため、コードベースの気の利いた記事はまだ書けません。自己紹介という意味もあり、自分のライブラリ開発という背景を下地に記事を書いてみました。
前置きが長くなってしまうのはQiitaポエマーの最たる悪癖でして、初めてのElixir記事からして、大変申し訳ございません(汗)

いずれにしても、Elixirとfukuoka.exを始めとした国内Elixir勢の方々との出会いは、私にとってとても新鮮で、意義深く感じています。
こういうワクワクした気持ちになったのは、久しぶりのような気がしますね。今後とも皆様、よろしくお願いいたします。

参考にさせていただいた記事

ネイティブコードとの連携については、大半の部分をこちらの記事から追っていきました。 @tatsuya6502様、ありがとうございます。
ElixirからRustの関数をつかう → はやい


  1. 当時は今でいうWebMetalという古い方の規格だと思いますが、WebGPUは2020年末の今ですら仕様が固まっていません。仕上がるのは2022年第1位四半期を目指しているらしいです。