Open5

Emscriptenファイルシステムのパッチ

okuokuokuoku

prev: https://zenn.dev/okuoku/scraps/cb8a6b831f5501
next: https://zenn.dev/okuoku/scraps/d8597a849e4005

Emscriptenのファイルシステムを乗っ取る

Emscriptenにはかなりリッチなファイルシステムエミュレーション層があり、Unityも直接これを使用している。

https://emscripten.org/docs/porting/files/file_systems_overview.html

Unity WebGLの場合は、ゲームの起動時にMEMFSにファイルを展開し以降のアクセスはそちらから行うことになる。要するにゲームのサイズ == メモリ消費量となりちょっと不味い。

幸い、Node.JS上では同期ファイルアクセスが使いたい放題なので非同期アクセスを気にする必要は無く、Unity側のファイルアクセスは全て物理ファイルアクセスにルーティングできる。 ただし、今回の目的では Node.JS自体のファイルアクセスは基本的に使えない 。WebGL実装でSDLが自前のイベントループを使用している都合で、Node.JS自体のファイルアクセスを使う場合は逆に非同期アクセスに対応する方法が無くなってしまう。

okuokuokuoku

ファイルシステム操作の実装手法

とりあえず MEMFSNODEFS のソースコードを見てみる:

(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
    };

mmapmsync は 必要ならエミュレーション対応することになる。 NODEFS では普通のI/Oで代替、 MEMFS はもうちょっと真面目な実装になっている。

okuokuokuoku

Unity WebGLのファイルを展開

UnityがMEMFSに投入するファイルを一旦展開した形でリポジトリにコミットしておくことにした。

https://github.com/okuoku/cwgl-proto/commit/1f3b081141cfe028966f8697621067ce2e981b58

... そもそもGitリポジトリにちゃんとファイルが置けるのか考察してなかったな。。とりあえず起動したから良いけど。

無理矢理 FS をエクスポート

Unity WebGLのビルドでは FS がJavaScript側にエクスポートされないようなので、とりあえず行頭を書き換えることで無理矢理実装した。

もうちょっとマシな方法が要るな。。

okuokuokuoku

とりあえずゲームが起動する程度に実装

とりあえずまだ同期I/Oしか使ってないので、Node.JSの fs を利用してUnity WebGLが起動する程度の実装を用意した。

うーむ。。もうちょっと真剣に考えたい。というか内部APIには流石にドキュメントが無いので、bufferの取り回しとかseekの扱いとか不明点が多い。

これをSDL側のファイルI/Oに置き換えてやる必要がある。