マイクラ統合版ScriptingAPIでコマンドの逐次実行用関数

マイクラ統合版のScriptingAPIを使って古代のがれきの一番多い高さをカウントしようとして、executeCommandのオーバーヘッドがでかすぎてやめたので、その副産物を紹介するっス

コマンドを直列で複数動かし、その結果に応じて追加でコマンドを実行したいときなどに、普通にやるとcallback地獄になるし、promiseも使えないだろうし（という勝手な思い込み、await/asyncとか実は使えたりするのだろうか）ということで、引数でコマンドを配列で渡すと順番に実行してくれる関数を作った。

スコアボードの値を読み込んで処理をするとかの時に、綺麗に書けるのではないだろうか。

惜しむらくはexecuteCommandの実行が遅いため、512ほどのコマンドを実行するのに25秒ほどかかること。
ちょうど一秒に20コマンドぐらいなので、1tickに1コマンドということかもしれない。
いくつものコマンドをまとめられる仕組みを知っていればもっと速くできるかもしれないが、JavaScriptはできてもマイクラのコマンドは不得手なので、この辺りで今回は断念することにする。

```javascript:server.js
const aServerSystem = server.registerSystem(0, 0);

aServerSystem.emit = function(identifier, properties) {
    const data = this.createEventData(identifier);
    data.data = Object.assign({}, data.data, properties);

    return this.broadcastEvent(identifier, data);
}

aServerSystem.initialize = function () {
    // エラー・警告・情報をすべてログに表示する
    this.emit("minecraft:script_logger_config", {
        log_errors : true,
        log_warnings : true,
        log_infomation : true,
    });

    // アイテムが落ちたら
    this.listenForEvent("minecraft:entity_dropped_item", (eventData) => {
        this.entity_dropped_item(eventData);
    });
}

// アイテムが落ちたら
aServerSystem.entity_dropped_item = function (eventData) {
    let playerEntity = eventData.data.entity;
    // プレイヤーだったら（ニワトリが卵産んだりするので）
    if (playerEntity["__identifier__"] == "minecraft:player") {
        // プレイヤーの位置を取得
        let player_pos = this.getComponent(playerEntity, "minecraft:position");
        // プレイヤーの位置の空気ブロックの数を測定する
        this.countAir(player_pos.data.x, player_pos.data.z);
    }
}

// 指定の場所の高さ１～２５６の空気ブロック数を測定する
aServerSystem.countAir = function (x, z) {
        this.executeCommands([
        "/title @a title 調査を始めます",

        // 関数を入れると、今までの結果を受け取りつつreturnした文字列のコマンドを実行できる
        (results) => {
            let numbers = Array.from(Array(256).keys());
            let commands = numbers.map((number) => {
                // 返すコマンドは文字列の配列でもいい
                return [
                    `/testforblock ${x} ${number} ${z} air`,
                    `/title @a subtitle ${number}/256`,
                ];
            });
            return commands;
        },
        // 最後にすべてを受け取って追加でコマンドを表示する
        (results) => {
            // airだった場所はmatchesがtrueなのでそれを数える
            let count = results.filter((result) => result.data.matches).length;
            return [
                "/title @a title 調べ終わりました",
                `/title @a subtitle 空気は${count}個ありました`,
            ];
        },
    ], (results) => {
        // 終了処理（今回は何もなし、ここでreturnしてもコマンドは実行できない）
    })
}

// 複数コマンドの逐次実行を行う
aServerSystem.executeCommands = function (commands, callback, results, result) {
    if (!results) {
        results = [];
    }
    if (result !== undefined) {
        results.push(result);
    }
    if (!commands.length) {
        if (callback && typeof callback == "function") {
            callback(results);
        }
        return;
    }
    let nextCommands = commands.splice(0, 1);
    let nextCommand = nextCommands.shift();

    if (!nextCommand) {
        this.executeCommands([], callback, results);
    }
    // 関数なら実行して結果をコマンドとして使う
    else if (typeof nextCommand == "function") {
        let newCommand = nextCommand(results);
        commands.splice(0, 0, newCommand);
        this.executeCommands(commands, callback, results);
    }
    // 配列なら展開する
    else if (typeof nextCommand == "object" && nextCommand.length !== undefined) {
        commands = nextCommand.concat(commands);
        this.executeCommands(commands, callback, results);
    }
    // 文字列ならコマンドとして送信する
    else if (typeof nextCommand == "string") {
        const nextCallback = this.executeCommands.bind(this, commands, callback, results);
        this.executeCommand(nextCommand, nextCallback);
    }
}

```