isobe_yakiのブログ

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

akashic export改善

ニコ生ゲーをアップロードする際必ずakashic exportコマンドを実行するが、開発後半など異様に時間がかかるようになったことはないだろうか。

怖い部屋3Dを作っているときこまめにエクスポートと動作確認を繰り返していたら、あまりにエクスポートが遅すぎて投稿が間に合わなくなりそうなことがあったので原因を調べたことがある。その際のメモと対処法を載せておこうと思う。

⚠️追記

公式で対応されたので本記事の内容は最新のakashic-cliであれば不要になりました。

対応コミット

調査

まずエクスポートコマンドのデバッグ方法を確認する。ただし、他の方法がわからないので自分の開発環境(Windows)での手順で書かせてもらう。

1. VSCodeでプロジェクトを開く

2. package.jsonに以下の記述をする

"scripts": { "export": "akashic export html --minify --atsumaru --output ./game.zip -f", ... }

3. ExplorerタブのNPM SCRIPTSのpackage.json>exportをデバッグ実行する

これでエクスポートコマンドをステップ実行できるようになるので処理を追いかける。

原因

調べた結果一番重かったのがゲームフォルダのコピーだった。ゲームフォルダの全ファイルをtempフォルダにコピーして、コピー先で色々パッケージング処理を行っていたのだ。恐らく時間のかかるエクスポート中も安全にゲームの編集ができるように、いったん作業スペースを確保したかったのだろう。 しかし、そんな挙動と知らずに怖い部屋3Dではゲームフォルダにc++の中間ファイルも出力するようにしていたのでとんでもない数ととんでもないサイズのファイルが全てコピーされてしまっていた。それ以外にもgimpファイルやblenderファイルなども置いていたし、node_modulesフォルダはどうしてもエクスポートに関係ない大量のファイルが含まれてしまう。

つまり最終データと関係ないファイルも区別なくコピーしてしまっていたのが問題だった。

改善

そこで、コピー処理をしているjsスクリプトにひと手間加えることにした。 akashicコマンドが存在するパスからの相対パス

\node_modules\@akashic\akashic-cli\node_modules\@akashic\akashic-cli-export\lib\html\exportHTML.js

がコピーを行っているスクリプトだ。gitだとここ

このファイルの一番下にcreateRenamedGameという関数があるはず。これを以下のように改変した。

function createRenamedGame(sourcePath, hashLength, logger) {
    var destDirPath = path.resolve(fs.mkdtempSync(path.join(os.tmpdir(), "akashic-export-html-")));

    ////////////////////////////////////////////////////////////////////////////////////
    // コピーすべきファイルのみを条件指定して高速化
    var includeList;
    const rulePath = path.join(sourcePath, 'rules.json');
    if (fs.existsSync(rulePath)) {
        const ruleText = fs.readFileSync(rulePath);
        const rules = JSON.parse(new TextDecoder("utf-8", {ignoreBOM: false}).decode(ruleText));
        includeList = rules.includeList.map(rule => new RegExp(rule));
    }
    fsx.copySync(sourcePath, destDirPath, includeList ? file => {
        if(fs.statSync(path.resolve(file)).isDirectory(file))return true;
        const relPath = path.relative(sourcePath, file);
        return includeList.some(rule => {
            return relPath.match(rule);
        });
    } : undefined);
    ////////////////////////////////////////////////////////////////////////////////////

    return Promise.resolve()
        .then(function () { return cmn.ConfigurationFile.read(path.join(destDirPath, "game.json"), logger); })
        .then(function (gamejson) {
        cmn.Renamer.renameAssetFilenames(gamejson, destDirPath, hashLength);
        return cmn.ConfigurationFile.write(gamejson, path.resolve(path.join(destDirPath, "game.json")), logger);
    }).then(function () { return destDirPath; });
}

コメント行で囲まれた部分が追加処理だ。簡単に解説すると、エクスポートしようとしているゲームフォルダ直下にrules.jsonというファイルが存在する場合それを読み込み、正規表現のフィルタパターンリストを作成し、パターンに適合するパスのみコピーを行う感じだ。rules.jsonの例は以下となる。includeList正規表現文字列を追加してコピーするファイルパスのパターンを追加していく。

{
    "includeList": [
        "image.*",
        "script.*",
        "game\\.json",
        "node_modules\\\\@akashic\\-extension.*"
    ]
}

imageフォルだとscriptフォルダとgame.jsonとnode_modules/@akashic-extensionフォルダ以下をコピー対象としている。パス区切り文字(\or/)やエスケープ文字に注意。

ほんとは.gitignoreとかと同じフォーマットで記述できるようにしたかったがめんどかったので妥協。誰か書き直せる人いたらお願い🙏