isobe_yakiのブログ

ニコ生ゲーム開発者向けの記事を書きます

WebAssembly(c++)でニコ生ゲームを作ろう その2

その1での予告通り今回はマルチプレイゲームでwasmを実行する方法についてまとめる。

サーバー向けビルド

以前の記事でも書いた通りマルチプレイモードのゲームではサーバー上でもゲームが1つ動いている。 このサーバーは恐らくnodeサーバー(?)なので、サーバー用wasmバイナリというものを別途作成して実行する必要がある。

普通にjavascriptでマルチゲーム作っている時も、DOMやキャンバス、window.addEventListener()などブラウザでしか使えないAPIを使う場合は、サーバー側でこれらが実行されないように処理を分けると思うがそれと同じことである。ただし、wasmの場合はemscriptenのビルド結果のjsファイルにWebGLやキャンバス処理などがエクスポートされるので、サーバー/クライアント兼用のスクリプトにするのが難しい。それでファイルごと分けざるを得ないということだ。

サーバーはブラウザAPIを含んだスクリプトは読めない

サーバー用ビルドをするにはemccのビルドオプションでターゲット環境をshellにする。ただし、ここはかなり怪しい。本来nodeでいいはずだと思うのだがそれだとニコ生で動かなかったのでshellにしてみたらたまたま動いただけである。実はnodeでもうまくいく方法はあるのかも。

// 前回はweb向けにビルドしていたがサーバー向けはshell
emcc -sNO_EXIT_RUNTIME=1 -sENVIRONMENT=shell server.cpp -o server.js

wasm読み込み

サーバー側だと読み込み方法も変わってくる。

前回はwasmをtextアセットに偽装してfetchで読み込むようにしていたが、以前の記事で書いた通り、nodeサーバーからCDNサーバーへはアクセス権が無いのでfetchで読むことができない。そこで、(やや手抜きな気もするが)wasmバイナリをbase64エンコードしてjsに埋め込むことにする。デコードする処理も一緒に埋め込むことで、実質バイナリデータをソースに埋め込んでいることになり、CDNサーバーへの通信なしでwasmの読み込みができる。もちろん、もっといい方法が思いついたらこの方法でなくてもよい。

以下に自分の場合の実現方法を書いておくが、あくまで参考である。各自どのような開発環境を構築するかによってそれぞれ異なるだろう。

  1. 自作のbase64化プログラムを作成
    • 厳密なbase64エンコードではなくややオリジナル
    • テキスト化できれば何でもよい
    • 注意点としてakashic export時のminifyで長すぎる文字列リテラルはエラーとなってしまうので、8KBごとに分割すると良い
      const encoded = '8KBの文字' + '8KBの文字' + ...;のようにすればエラーは出なくなった
    • 実装例(クリックで展開) gist.github.com
  2. makefileのビルドルールにエンコードコマンドを追加
    これはemscriptenで生成した.wasmと.jsの.wasmをbase64エンコードしてもう一つの.jsと結合しscriptフォルダにコピーするビルドルール
    ../script/server_wasm.js: $(BIN_DIR)/server_wasm.wasm $(BIN_DIR)/server_wasm.js
        @call $(WASM2JS) $(BIN_DIR)/server_wasm.wasm tmp.js
        copy /y /b tmp.js+$(BIN_DIR_FOR_WIN)\server_wasm.js ..\script\server_wasm.js
    2行目の`$(WASM2JS)`が1.で書いた自作のbase64化プログラムのパス
  3. base64をUint8Array型にデコードしてModule.wasmBinaryにセットする
    自分の場合は1.のbase64化プログラムでデコード処理も一緒に出力するようにしている*1
    デコードした結果はvar Module = { wasmBinary: decode_bin };というようにModule.wasmBinaryにセットしておくことで、emscriptenがfetchしないで直接このバイナリをロードしてくれるようになる

以上の結果生成されるスクリプト

これ(クリックで展開)

gist.github.com

である*2

デコーダーとwasmバイナリとModuleへの設定が含まれている。このコードをもう一つの生成物であるjsファイルの先頭に結合して読み込むだけでAkashicサーバー上でもwasmを使えるようになる。

ちなみに、バイナリデータをbase64化することでそのサイズは約1.33倍にはなるが、サーバーで実行するwasmというのは描画処理などを含まないため大体数十KB程度に収まるので、その増分はほぼ誤差の範囲である。

まとめ

というわけで今回の記事によって

  • サーバーで実行できるwasmをビルドする方法が分かった
  • Akashicサーバーでwasmを読み込む方法が分かった

後者に関しては各開発者の環境次第で実装方法が変わってくると思うので、どういう結果が得られればよいのかだけ押さえてもらえればいいと思う。以上。

*1:サーバーが恐らくnodeなのでBuffer.from('base64')などが使えると決め打って利用しても良いかも?今回はそこに依存したくないので自前でデコードした。

*2:ニコニコ迷宮の実際のスクリプト