Emscriptenファイルシステムのパッチ
prev: https://zenn.dev/okuoku/scraps/cb8a6b831f5501
next: https://zenn.dev/okuoku/scraps/d8597a849e4005
Emscriptenのファイルシステムを乗っ取る
Emscriptenにはかなりリッチなファイルシステムエミュレーション層があり、Unityも直接これを使用している。
Unity WebGLの場合は、ゲームの起動時にMEMFSにファイルを展開し以降のアクセスはそちらから行うことになる。要するにゲームのサイズ == メモリ消費量となりちょっと不味い。
幸い、Node.JS上では同期ファイルアクセスが使いたい放題なので非同期アクセスを気にする必要は無く、Unity側のファイルアクセスは全て物理ファイルアクセスにルーティングできる。 ただし、今回の目的では Node.JS自体のファイルアクセスは基本的に使えない 。WebGL実装でSDLが自前のイベントループを使用している都合で、Node.JS自体のファイルアクセスを使う場合は逆に非同期アクセスに対応する方法が無くなってしまう。
ファイルシステム操作の実装手法
とりあえず MEMFS
と NODEFS
のソースコードを見てみる:
-
MEMFS
: https://github.com/emscripten-core/emscripten/blob/4b71fc7a4d4c4f19f4c08cba83b5905bf5610f02/src/library_memfs.js -
NODEFS
: https://github.com/emscripten-core/emscripten/blob/4b71fc7a4d4c4f19f4c08cba83b5905bf5610f02/src/library_nodefs.js
(Unityが使っているEmscriptenは2年くらい前のものだが、ファイルシステムのAPI自体は変わっていないようだ。)
Emscriptenでは、これらのファイルシステムの下位に、通常のOSで言うところのVFS(Virtual File System)に相当する node
インターフェースがある。個々のファイルシステムは、この下位層の node
にそれぞれのファイルシステムの具体的な操作である ops
をくっつけることで、実際のファイルシステム操作を実装できる。
ファイルシステムは操作を実装しないこともできる。例えば、 MEMFS
には open
操作が存在しない。 MEMFS
で扱われるファイル内容は node
側にJavaScriptオブジェクトとしてくっつけられるため、 open
に副作用が無い。対して、 NODEFS
では、Node.JS側の fd
オブジェクトを取得する必要があるので open
を実装している。
if (FS.isFile(stream.node.mode)) {
stream.nfd = fs.openSync(path, NODEFS.flagsForNode(stream.flags));
}
ゲームのアセットに関して言えば読み取り専用なので、実装が必要なのはこの辺かな。
const dir_nodeops = {
getattr: dir_getattr,
//setattr: dir_setattr,
lookup: dir_lookup,
//mknod: dir_mknod,
//rename: dir_rename,
//unlink: dir_unlink,
//rmdir: dir_rmdir,
readdir: dir_readdir,
//symlink: dir_symlink,
};
const file_nodeops = {
getattr: file_getattr,
//setattr: file_setattr,
};
const dir_streamops = {
llseek: dir_llseek
};
const file_streamops = {
open: file_open,
close: file_close,
llseek: file_llseek,
read: file_read,
write: file_write,
//allocate: file_allocate,
//mmap: file_mmap,
//msync: file_msync
};
mmap
や msync
は 必要ならエミュレーション対応することになる。 NODEFS
では普通のI/Oで代替、 MEMFS
はもうちょっと真面目な実装になっている。
Unity WebGLのファイルを展開
UnityがMEMFSに投入するファイルを一旦展開した形でリポジトリにコミットしておくことにした。
... そもそもGitリポジトリにちゃんとファイルが置けるのか考察してなかったな。。とりあえず起動したから良いけど。
FS
をエクスポート
無理矢理 Unity WebGLのビルドでは FS
がJavaScript側にエクスポートされないようなので、とりあえず行頭を書き換えることで無理矢理実装した。
もうちょっとマシな方法が要るな。。
とりあえずゲームが起動する程度に実装
とりあえずまだ同期I/Oしか使ってないので、Node.JSの fs
を利用してUnity WebGLが起動する程度の実装を用意した。
うーむ。。もうちょっと真剣に考えたい。というか内部APIには流石にドキュメントが無いので、bufferの取り回しとかseekの扱いとか不明点が多い。
これをSDL側のファイルI/Oに置き換えてやる必要がある。
fetch
の polyfillを削除
Emscriptenのバイナリはデフォルトでは自力でWASMバイナリを fetch
しようとする。これは主にストリーミングのサポートのためだが今回の目的には不要なので fetch
ではなく直接ロードしたい。
これは Module["wasmBinary"]
に事前にWASMバイナリを代入しておくことで実現できる。