text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  var trigger_1 = require("@akashic/trigger");
  var AssetAccessor_1 = require("./AssetAccessor");
  var AssetHolder_1 = require("./AssetHolder");
  var Camera2D_1 = require("./Camera2D");
  var ExceptionFactory_1 = require("./ExceptionFactory");
  var TimerManager_1 = require("./TimerManager");
  /**
   * シーンを表すクラス。
   */
  var Scene = /** @class */ (function () {
      /**
       * 各種パラメータを指定して `Scene` のインスタンスを生成する。
       * @param param 初期化に用いるパラメータのオブジェクト
       */
      function Scene(param) {
          var game = param.game;
          var local = param.local === undefined
              ? "non-local"
              : param.local === false
                  ? "non-local"
                  : param.local === true
                      ? "full-local"
                      : param.local;
          var tickGenerationMode = param.tickGenerationMode !== undefined ? param.tickGenerationMode : "by-clock";
          if (!param.storageKeys) {
              this._storageLoader = undefined;
              this.storageValues = undefined;
          }
          else {
              this._storageLoader = game.storage._createLoader(param.storageKeys, param.storageValuesSerialization);
              this.storageValues = this._storageLoader._valueStore;
          }
          this.name = param.name;
          this.game = game;
          this.local = local;
          this.tickGenerationMode = tickGenerationMode;
          this.onLoad = new trigger_1.Trigger();
          this.loaded = this.onLoad;
          this._onReady = new trigger_1.Trigger();
          this._ready = this._onReady;
          this.assets = {};
          this.asset = new AssetAccessor_1.AssetAccessor(game._assetManager);
          this._loaded = false;
          this._prefetchRequested = false;
          this._loadingState = "initial";
          this.onUpdate = new trigger_1.Trigger();
          this.update = this.onUpdate;
          this._timer = new TimerManager_1.TimerManager(this.onUpdate, this.game.fps);
          this.onAssetLoad = new trigger_1.Trigger();
          this.onAssetLoadFailure = new trigger_1.Trigger();
          this.onAssetLoadComplete = new trigger_1.Trigger();
          this.assetLoaded = this.onAssetLoad;
          this.assetLoadFailed = this.onAssetLoadFailure;
          this.assetLoadCompleted = this.onAssetLoadComplete;
          this.onMessage = new trigger_1.Trigger();
          this.onPointDownCapture = new trigger_1.Trigger();
          this.onPointMoveCapture = new trigger_1.Trigger();
          this.onPointUpCapture = new trigger_1.Trigger();
          this.onOperation = new trigger_1.Trigger();
          this.message = this.onMessage;
          this.pointDownCapture = this.onPointDownCapture;
          this.pointMoveCapture = this.onPointMoveCapture;
          this.pointUpCapture = this.onPointUpCapture;
          this.operation = this.onOperation;
          this.children = [];
          this.state = "standby";
          this.onStateChange = new trigger_1.Trigger();
          this._assetHolders = [];
          this._sceneAssetHolder = new AssetHolder_1.AssetHolder({
              assetManager: this.game._assetManager,
              assetIds: param.assetIds,
              assetPaths: param.assetPaths,
              handlerSet: {
                  owner: this,
                  handleLoad: this._handleSceneAssetLoad,
                  handleLoadFailure: this._handleSceneAssetLoadFailure,
                  handleFinish: this._handleSceneAssetLoadFinish
              },
              userData: null
          });
      }
      /**
       * このシーンが変更されたことをエンジンに通知する。
       *
       * このメソッドは、このシーンに紐づいている `E` の `modified()` を呼び出すことで暗黙に呼び出される。
       * 通常、ゲーム開発者がこのメソッドを呼び出す必要はない。
       * @param isBubbling この関数をこのシーンの子の `modified()` から呼び出す場合、真を渡さなくてはならない。省略された場合、偽。
       */
      Scene.prototype.modified = function (_isBubbling) {
          this.game.modified();
      };
      /**
       * このシーンを破棄する。
       *
       * 破棄処理の開始時に、このシーンの `onStateChange` が引数 `BeforeDestroyed` でfireされる。
       * 破棄処理の終了時に、このシーンの `onStateChange` が引数 `Destroyed` でfireされる。
       * このシーンに紐づいている全ての `E` と全てのTimerは破棄される。
       * `Scene#setInterval()`, `Scene#setTimeout()` に渡された関数は呼び出されなくなる。
       *
       * このメソッドは `Scene#end` や `Game#popScene` などによって要求されたシーンの遷移時に暗黙に呼び出される。
       * 通常、ゲーム開発者がこのメソッドを呼び出す必要はない。
       */
      Scene.prototype.destroy = function () {
          this.state = "before-destroyed";
          this.onStateChange.fire(this.state);
          // TODO: (GAMEDEV-483) Sceneスタックがそれなりの量になると重くなるのでScene#dbが必要かもしれない
          var gameDb = this.game.db;
          for (var p in gameDb) {
              if (gameDb.hasOwnProperty(p) && gameDb[p].scene === this)
                  gameDb[p].destroy();
          }
          var gameDb = this.game._localDb;
          for (var p in gameDb) {
              if (gameDb.hasOwnProperty(p) && gameDb[p].scene === this)
                  gameDb[p].destroy();
          }
          this._timer.destroy();
          this.onUpdate.destroy();
          this.onMessage.destroy();
          this.onPointDownCapture.destroy();
          this.onPointMoveCapture.destroy();
          this.onPointUpCapture.destroy();
          this.onOperation.destroy();
          this.onLoad.destroy();
          this.onAssetLoad.destroy();
          this.onAssetLoadFailure.destroy();
          this.onAssetLoadComplete.destroy();
          this.assets = {};
          // アセットを参照しているEより先に解放しないよう最後に解放する
          for (var i = 0; i < this._assetHolders.length; ++i)
              this._assetHolders[i].destroy();
          this._sceneAssetHolder.destroy();
          this._storageLoader = undefined;
          this.game = undefined;
          this.state = "destroyed";
          this.onStateChange.fire(this.state);
          this.onStateChange.destroy();
      };
      /**
       * 破棄済みであるかを返す。
       */
      Scene.prototype.destroyed = function () {
          return this.game === undefined;
      };
      /**
       * 一定間隔で定期的に処理を実行するTimerを作成して返す。
       *
       * 戻り値は作成されたTimerである。
       * 通常は `Scene#setInterval` を利用すればよく、ゲーム開発者がこのメソッドを呼び出す必要はない。
       * `Timer` はフレーム経過処理(`Scene#onUpdate`)で実現される疑似的なタイマーである。実時間の影響は受けない。
       * @param interval Timerの実行間隔（ミリ秒）
       */
      Scene.prototype.createTimer = function (interval) {
          return this._timer.createTimer(interval);
      };
      /**
       * Timerを削除する。
       * @param timer 削除するTimer
       */
      Scene.prototype.deleteTimer = function (timer) {
          this._timer.deleteTimer(timer);
      };
      /**
       * 一定間隔で定期的に実行される処理を作成する。
       *
       * `interval` ミリ秒おきに `owner` を `this` として `handler` を呼び出す。
       * 戻り値は `Scene#clearInterval` の引数に指定して定期実行を解除するために使える値である。
       * このタイマーはフレーム経過処理(`Scene#onUpdate`)で実現される疑似的なタイマーである。実時間の影響は受けない。
       * 関数は指定時間の経過直後ではなく、経過後最初のフレームで呼び出される。
       * @param handler 処理
       * @param interval 実行間隔(ミリ秒)
       * @param owner handlerの所有者。省略された場合、null
       */
      Scene.prototype.setInterval = function (handler, interval, owner) {
          return this._timer.setInterval(handler, interval, owner);
      };
      /**
       * setIntervalで作成した定期処理を解除する。
       * @param identifier 解除対象
       */
      Scene.prototype.clearInterval = function (identifier) {
          this._timer.clearInterval(identifier);
      };
      /**
       * 一定時間後に一度だけ実行される処理を作成する。
       *
       * `milliseconds` ミリ秒後(以降)に、一度だけ `owner` を `this` として `handler` を呼び出す。
       * 戻り値は `Scene#clearTimeout` の引数に指定して処理を削除するために使える値である。
       *
       * このタイマーはフレーム経過処理(`Scene#onUpdate`)で実現される疑似的なタイマーである。実時間の影響は受けない。
       * 関数は指定時間の経過直後ではなく、経過後最初のフレームで呼び出される。
       * (理想的なケースでは、30FPSなら50msのコールバックは66.6ms時点で呼び出される)
       * 時間経過に対して厳密な処理を行う必要があれば、自力で `Scene#onUpdate` 通知を処理すること。
       *
       * @param handler 処理
       * @param milliseconds 時間(ミリ秒)
       * @param owner handlerの所有者。省略された場合、null
       */
      Scene.prototype.setTimeout = function (handler, milliseconds, owner) {
          return this._timer.setTimeout(handler, milliseconds, owner);
      };
      /**
       * setTimeoutで作成した処理を削除する。
       * @param identifier 解除対象
       */
      Scene.prototype.clearTimeout = function (identifier) {
          this._timer.clearTimeout(identifier);
      };
      /**
       * このシーンが現在のシーンであるかどうかを返す。
       */
      Scene.prototype.isCurrentScene = function () {
          return this.game.scene() === this;
      };
      /**
       * 次のシーンへの遷移を要求する。
       *
       * このメソッドは、 `toPush` が真ならば `Game#pushScene()` の、でなければ `Game#replaceScene` のエイリアスである。
       * このメソッドは要求を行うだけである。呼び出し直後にはシーン遷移は行われていないことに注意。
       * このシーンが現在のシーンでない場合、 `AssertionError` がthrowされる。
       * @param next 遷移後のシーン
       * @param toPush 現在のシーンを残したままにするなら真、削除して遷移するなら偽を指定する。省略された場合偽
       */
      Scene.prototype.gotoScene = function (next, toPush) {
          if (!this.isCurrentScene())
              throw ExceptionFactory_1.ExceptionFactory.createAssertionError("Scene#gotoScene: this scene is not the current scene");
          if (toPush) {
              this.game.pushScene(next);
          }
          else {
              this.game.replaceScene(next);
          }
      };
      /**
       * このシーンの削除と、一つ前のシーンへの遷移を要求する。
       *
       * このメソッドは `Game#popScene()` のエイリアスである。
       * このメソッドは要求を行うだけである。呼び出し直後にはシーン遷移は行われていないことに注意。
       * このシーンが現在のシーンでない場合、 `AssertionError` がthrowされる。
       */
      Scene.prototype.end = function () {
          if (!this.isCurrentScene())
              throw ExceptionFactory_1.ExceptionFactory.createAssertionError("Scene#end: this scene is not the current scene");
          this.game.popScene();
      };
      /**
       * このSceneにエンティティを登録する。
       *
       * このメソッドは各エンティティに対して暗黙に呼び出される。ゲーム開発者がこのメソッドを明示的に呼び出す必要はない。
       * @param e 登録するエンティティ
       */
      Scene.prototype.register = function (e) {
          this.game.register(e);
          e.scene = this;
      };
      /**
       * このSceneからエンティティの登録を削除する。
       *
       * このメソッドは各エンティティに対して暗黙に呼び出される。ゲーム開発者がこのメソッドを明示的に呼び出す必要はない。
       * @param e 登録を削除するエンティティ
       */
      Scene.prototype.unregister = function (e) {
          // @ts-ignore
          e.scene = undefined;
          this.game.unregister(e);
      };
      /**
       * 子エンティティを追加する。
       *
       * `this.children` の末尾に `e` を追加する(`e` はそれまでに追加されたすべての子エンティティより手前に表示される)。
       *
       * @param e 子エンティティとして追加するエンティティ
       */
      Scene.prototype.append = function (e) {
          this.insertBefore(e, undefined);
      };
      /**
       * 子エンティティを挿入する。
       *
       * `this.children` の`target`の位置に `e` を挿入する。
       * `target` が`this` の子でない場合、`append(e)`と同じ動作となる。
       *
       * @param e 子エンティティとして追加するエンティティ
       * @param target 挿入位置にある子エンティティ
       */
      Scene.prototype.insertBefore = function (e, target) {
          if (e.parent)
              e.remove();
          e.parent = this;
          var index = -1;
          if (target !== undefined && (index = this.children.indexOf(target)) > -1) {
              this.children.splice(index, 0, e);
          }
          else {
              this.children.push(e);
          }
          this.modified(true);
      };
      /**
       * 子エンティティを削除する。
       * `this` の子から `e` を削除する。 `e` が `this` の子でない場合、何もしない。
       * @param e 削除する子エンティティ
       */
      Scene.prototype.remove = function (e) {
          var index = this.children.indexOf(e);
          if (index === -1)
              return;
          this.children[index].parent = undefined;
          this.children.splice(index, 1);
          this.modified(true);
      };
      /**
       * シーン内でその座標に反応する `PointSource` を返す。
       * @param point 対象の座標
       * @param force touchable指定を無視する場合真を指定する。指定されなかった場合偽
       * @param camera 対象のカメラ。指定されなかった場合undefined
       */
      Scene.prototype.findPointSourceByPoint = function (point, force, camera) {
          var mayConsumeLocalTick = this.local !== "non-local";
          var children = this.children;
          var m = camera && camera instanceof Camera2D_1.Camera2D ? camera.getMatrix() : undefined;
          for (var i = children.length - 1; i >= 0; --i) {
              var ret = children[i].findPointSourceByPoint(point, m, force);
              if (ret) {
                  ret.local = (ret.target && ret.target.local) || mayConsumeLocalTick;
                  return ret;
              }
          }
          return { target: undefined, point: undefined, local: mayConsumeLocalTick };
      };
      /**
       * アセットの先読みを要求する。
       *
       * `Scene` に必要なアセットは、通常、`Game#pushScene()` などによるシーン遷移にともなって暗黙に読み込みが開始される。
       * ゲーム開発者はこのメソッドを呼び出すことで、シーン遷移前にアセット読み込みを開始する(先読みする)ことができる。
       * 先読み開始後、シーン遷移時までに読み込みが完了していない場合、通常の読み込み処理同様にローディングシーンが表示される。
       *
       * このメソッドは `StorageLoader` についての先読み処理を行わない点に注意。
       * ストレージの場合、書き込みが行われる可能性があるため、順序を無視して先読みすることはできない。
       */
      Scene.prototype.prefetch = function () {
          if (this._loaded) {
              // _load() 呼び出し後に prefetch() する意味はない(先読みではない)。
              return;
          }
          if (this._prefetchRequested)
              return;
          this._prefetchRequested = true;
          this._sceneAssetHolder.request();
      };
      /**
       * シーンが読み込んだストレージの値をシリアライズする。
       *
       * `Scene#storageValues` の内容をシリアライズする。
       */
      Scene.prototype.serializeStorageValues = function () {
          if (!this._storageLoader)
              return undefined;
          return this._storageLoader._valueStoreSerialization;
      };
      Scene.prototype.requestAssets = function (assetIds, handler) {
          var _this = this;
          if (this._loadingState !== "ready-fired" && this._loadingState !== "loaded-fired") {
              // このメソッドは読み込み完了前には呼び出せない。これは実装上の制限である。
              // やろうと思えば _load() で読み込む対象として加えることができる。が、その場合 `handler` を呼び出す方法が単純でないので対応を見送る。
              throw ExceptionFactory_1.ExceptionFactory.createAssertionError("Scene#requestAsset(): can be called after loaded.");
          }
          var holder = new AssetHolder_1.AssetHolder({
              assetManager: this.game._assetManager,
              assetIds: assetIds,
              handlerSet: {
                  owner: this,
                  handleLoad: this._handleSceneAssetLoad,
                  handleLoadFailure: this._handleSceneAssetLoadFailure,
                  handleFinish: this._handleSceneAssetLoadFinish
              },
              userData: function () {
                  // 不要なクロージャは避けたいが生存チェックのため不可避
                  if (!_this.destroyed())
                      handler();
              }
          });
          this._assetHolders.push(holder);
          holder.request();
      };
      /**
       * @private
       */
      Scene.prototype._activate = function () {
          this.state = "active";
          this.onStateChange.fire(this.state);
      };
      /**
       * @private
       */
      Scene.prototype._deactivate = function () {
          this.state = "deactive";
          this.onStateChange.fire(this.state);
      };
      /**
       * @private
       */
      Scene.prototype._needsLoading = function () {
          return this._sceneAssetHolder.waitingAssetsCount > 0 || (!!this._storageLoader && !this._storageLoader._loaded);
      };
      /**
       * @private
       */
      Scene.prototype._load = function () {
          if (this._loaded)
              return;
          this._loaded = true;
          var needsWait = this._sceneAssetHolder.request();
          if (this._storageLoader) {
              this._storageLoader._load(this);
              needsWait = true;
          }
          if (!needsWait)
              this._notifySceneReady();
      };
      /**
       * @private
       */
      Scene.prototype._handleSceneAssetLoad = function (asset) {
          this.assets[asset.id] = asset;
          this.onAssetLoad.fire(asset);
          this.onAssetLoadComplete.fire(asset);
      };
      /**
       * @private
       */
      Scene.prototype._handleSceneAssetLoadFailure = function (failureInfo) {
          this.onAssetLoadFailure.fire(failureInfo);
          this.onAssetLoadComplete.fire(failureInfo.asset);
      };
      /**
       * @private
       */
      Scene.prototype._handleSceneAssetLoadFinish = function (holder, succeed) {
          if (!succeed) {
              this.game.terminateGame();
              return;
          }
          // 動的アセット (`requestAssets()` 由来) の場合
          if (holder.userData) {
              this.game._pushPostTickTask(holder.userData, null);
              return;
          }
          if (!this._loaded) {
              // prefetch() で開始されたアセット読み込みを完了したが、_load() がまだ呼ばれていない。
              // _notifySceneReady() は _load() 呼び出し後まで遅延する。
              return;
          }
          if (this._storageLoader && !this._storageLoader._loaded) {
              // アセット読み込みを完了したが、ストレージの読み込みが終わっていない。
              // _notifySceneReady() は  _onStorageLoaded() 呼び出し後まで遅延する。
              return;
          }
          this._notifySceneReady();
      };
      /**
       * @private
       */
      Scene.prototype._onStorageLoadError = function (_error) {
          this.game.terminateGame();
      };
      /**
       * @private
       */
      Scene.prototype._onStorageLoaded = function () {
          if (this._sceneAssetHolder.waitingAssetsCount === 0)
              this._notifySceneReady();
      };
      /**
       * @private
       */
      Scene.prototype._notifySceneReady = function () {
          // 即座に `_onReady` をfireすることはしない。tick()のタイミングで行うため、リクエストをgameに投げておく。
          this._loadingState = "ready";
          this.game._pushPostTickTask(this._fireReady, this);
      };
      /**
       * @private
       */
      Scene.prototype._fireReady = function () {
          if (this.destroyed())
              return;
          this._onReady.fire(this);
          this._loadingState = "ready-fired";
      };
      /**
       * @private
       */
      Scene.prototype._fireLoaded = function () {
          if (this.destroyed())
              return;
          if (this._loadingState === "loaded-fired")
              return;
          this.onLoad.fire(this);
          this._loadingState = "loaded-fired";
      };
      return Scene;
  }());
  exports.Scene = Scene;
  