From 813ce6dda65a53ab014e647884d272396a0d0ef9 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 26 Jul 2023 14:51:08 +0900 Subject: [PATCH 01/16] feat: add g.Scene#vars --- CHANGELOG.md | 3 +++ src/Scene.ts | 9 +++++++++ src/__tests__/SceneSpec.ts | 1 + 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78ec2d2bc..18d465bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # ChangeLog +# unreleased changes +* `g.Scene#vars` を追加 + ## 3.14.0 * @akashic/pdi-types@1.10.0 に追従 * `"binary"` アセットに対応 diff --git a/src/Scene.ts b/src/Scene.ts index 8974bafba..2adae01f3 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -360,6 +360,13 @@ export class Scene implements StorageLoaderHandler { */ operation: Trigger; + /** + * ゲーム開発者向けのコンテナ。 + * + * この値はゲームエンジンのロジックからは使用されず、ゲーム開発者は任意の目的に使用してよい。 + */ + vars: any; + /** * @private */ @@ -464,6 +471,7 @@ export class Scene implements StorageLoaderHandler { this._ready = this._onReady; this.assets = {}; this.asset = new AssetAccessor(game._assetManager); + this.vars = {}; this._loaded = false; this._prefetchRequested = false; @@ -558,6 +566,7 @@ export class Scene implements StorageLoaderHandler { this.onAssetLoadFailure.destroy(); this.onAssetLoadComplete.destroy(); this.assets = {}; + this.vars = {}; // アセットを参照しているEより先に解放しないよう最後に解放する for (let i = 0; i < this._assetHolders.length; ++i) this._assetHolders[i].destroy(); diff --git a/src/__tests__/SceneSpec.ts b/src/__tests__/SceneSpec.ts index f43c51ec5..f3a978f38 100644 --- a/src/__tests__/SceneSpec.ts +++ b/src/__tests__/SceneSpec.ts @@ -49,6 +49,7 @@ describe("test Scene", () => { expect(scene.local).toBe("interpolate-local"); expect(scene.tickGenerationMode).toBe("manual"); expect(scene.name).toEqual("myScene"); + expect(scene.vars).toEqual({}); expect(scene.onUpdate instanceof Trigger).toBe(true); expect(scene.onLoad instanceof Trigger).toBe(true); From 6568a29a3d19c73ec88d0d143004f722aede444a Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 26 Jul 2023 14:59:04 +0900 Subject: [PATCH 02/16] feat: support 'prepare' phase before the scene load --- src/Game.ts | 73 +++++++++++++++++++-- src/__tests__/GameSpec.ts | 130 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 4 deletions(-) diff --git a/src/Game.ts b/src/Game.ts index cce444463..72f0319fb 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -94,6 +94,11 @@ interface PostTickPushSceneTask { * 遷移先になるシーン。 */ scene: Scene; + + /** + * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのコールバック。 + */ + prepare?: (done: () => void) => void; } /** @@ -114,6 +119,11 @@ interface PostTickReplaceSceneTask { * 現在のシーンを破棄するか否か。 */ preserveCurrent: boolean; + + /** + * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのコールバック。 + */ + prepare?: (done: () => void) => void; } /** @@ -206,6 +216,21 @@ export interface EventTriggerMap { operation: Trigger; } +/** + * Game#pushScene() のオプション + */ +export interface PushSceneOption { + prepare?: (done: () => void) => void; +} + +/** + * Game#replaceScene() のオプション + */ +export interface ReplaceSceneOption { + preserveCurrent?: boolean; + prepare?: (done: () => void) => void; +} + export type GameMainFunction = (g: any, args: GameMainParameterObject) => void; /** @@ -969,11 +994,13 @@ export class Game { * このメソッドの呼び出しにより、現在のシーンの `stateChanged` が引数 `"deactive"` でfireされる。 * その後 `scene.stateChanged` が引数 `"active"` でfireされる。 * @param scene 遷移後のシーン + * @param option 遷移時のオプション */ - pushScene(scene: Scene): void { + pushScene(scene: Scene, option?: PushSceneOption): void { this._postTickTasks.push({ type: PostTickTaskType.PushScene, - scene: scene + scene, + prepare: option?.prepare }); } @@ -990,11 +1017,28 @@ export class Game { * @param scene 遷移後のシーン * @param preserveCurrent 真の場合、現在のシーンを破棄しない(ゲーム開発者が明示的に破棄せねばならない)。省略された場合、偽 */ - replaceScene(scene: Scene, preserveCurrent?: boolean): void { + replaceScene(scene: Scene, preserveCurrent?: boolean): void; + /** + * 現在のシーンの置き換えを要求する。 + * + * @param scene 遷移後のシーン + * @param option 遷移時のオプション + */ + replaceScene(scene: Scene, option?: ReplaceSceneOption): void; + replaceScene(scene: Scene, preserveCurrentOrOption?: boolean | ReplaceSceneOption): void { + let preserveCurrent: boolean; + let prepare: ((done: () => void) => void) | undefined; + if (typeof preserveCurrentOrOption === "object") { + preserveCurrent = !!preserveCurrentOrOption.preserveCurrent; + prepare = preserveCurrentOrOption.prepare; + } else { + preserveCurrent = !!preserveCurrentOrOption; + } this._postTickTasks.push({ type: PostTickTaskType.ReplaceScene, scene: scene, - preserveCurrent: !!preserveCurrent + preserveCurrent, + prepare }); } @@ -1792,11 +1836,17 @@ export class Game { if (oldScene) { oldScene._deactivate(); } + if (req.prepare) { + this._interruptSceneLoading(req.prepare); + } this._doPushScene(req.scene, false); break; case PostTickTaskType.ReplaceScene: // NOTE: replaceSceneの場合、pop時点では_sceneChangedをfireしない。_doPushScene() で一度だけfireする。 this._doPopScene(req.preserveCurrent, false, false); + if (req.prepare) { + this._interruptSceneLoading(req.prepare); + } this._doPushScene(req.scene, false); break; case PostTickTaskType.PopScene: @@ -1972,6 +2022,21 @@ export class Game { this._modified = true; } + private _interruptSceneLoading(prepare: (done: () => void) => void): void { + const origLoadingScene = this.loadingScene; + const loadingScene = new LoadingScene({ + game: this, + explicitEnd: true + }); + // 元のローディングシーンを保持するためクロージャを許容 + loadingScene.onTargetReady.addOnce(() => { + this.loadingScene = origLoadingScene; + const done = loadingScene.end.bind(loadingScene); + prepare(done); + }); + this.loadingScene = loadingScene; + } + private _cleanDB(): void { this.db.clean(); this._localDb.clean(); diff --git a/src/__tests__/GameSpec.ts b/src/__tests__/GameSpec.ts index 766bfe4d7..949d5cf23 100644 --- a/src/__tests__/GameSpec.ts +++ b/src/__tests__/GameSpec.ts @@ -513,6 +513,136 @@ describe("test Game", () => { game._startLoadingGlobalAssets(); }); + it("pushScene - prepare", done => { + const game = new Game({ + width: 320, + height: 320, + main: "", + assets: { + foo: { + type: "image", + path: "/path1.png", + virtualPath: "path1.png", + width: 1, + height: 1 + } + } + }); + + // game.scenes テストのため _loaded を待つ必要がある + game._onLoad.add(() => { + const sequence: string[] = []; + const scene1 = new Scene({ game, assetIds: ["foo"], name: "scene1" }); + const scene2 = new Scene({ game, assetIds: ["foo"], name: "scene2" }); + scene1.onLoad.addOnce(() => { + sequence.push("scene1 loaded"); + }); + scene2.onLoad.addOnce(() => { + sequence.push("scene2 loaded"); + }); + + game.pushScene(scene1, { + prepare: done => { + setTimeout( + () => { + sequence.push("scene1 prepared"); + done(); + }, + 100 // この値にとくに根據は無い + ); + } + }); + + scene1.onLoad.addOnce(() => { + game.pushScene(scene2, { + prepare: done => { + setTimeout( + () => { + sequence.push("scene2 prepared"); + done(); + }, + 100 // この値にとくに根據は無い + ); + } + }); + }); + scene2.onLoad.addOnce(() => { + // Scene#onLoad の前に prepare が完了していることを確認 + expect(sequence).toEqual(["scene1 prepared", "scene1 loaded", "scene2 prepared", "scene2 loaded"]); + done(); + }); + }); + game._startLoadingGlobalAssets(); + }); + + it("replaceScene - prepare", done => { + const game = new Game({ + width: 320, + height: 320, + main: "", + assets: { + foo: { + type: "image", + path: "/path1.png", + virtualPath: "path1.png", + width: 1, + height: 1 + } + } + }); + + // game.scenes テストのため _loaded を待つ必要がある + game._onLoad.add(() => { + // 初期シーンを replace することはできたいため一旦ダミーのシーンを push しておく + const scene = new Scene({ game }); + game.pushScene(scene); + scene.onLoad.addOnce(() => { + const sequence: string[] = []; + const scene1 = new Scene({ game, assetIds: ["foo"], name: "scene1" }); + const scene2 = new Scene({ game, assetIds: ["foo"], name: "scene2" }); + + scene1.onLoad.addOnce(() => { + sequence.push("scene1 loaded"); + }); + scene2.onLoad.addOnce(() => { + sequence.push("scene2 loaded"); + }); + + game.replaceScene(scene1, { + prepare: done => { + setTimeout( + () => { + sequence.push("scene1 prepared"); + done(); + }, + 100 // この値にとくに根據は無い + ); + } + }); + + scene1.onLoad.addOnce(() => { + game.replaceScene(scene2, { + prepare: done => { + setTimeout( + () => { + sequence.push("scene2 prepared"); + done(); + }, + 100 // この値にとくに根據は無い + ); + } + }); + }); + scene2.onLoad.addOnce(() => { + // Scene#onLoad の前に prepare が完了していることを確認 + expect(sequence).toEqual(["scene1 prepared", "scene1 loaded", "scene2 prepared", "scene2 loaded"]); + done(); + }); + }); + }); + game._startLoadingGlobalAssets(); + }); + it("tick", done => { const assets: { [id: string]: AssetConfiguration } = { mainScene: { From 500fc8c7a25942738beb6b0ca941fcfc75b4008d Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 26 Jul 2023 15:03:22 +0900 Subject: [PATCH 03/16] chore: update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d465bc1..ed5a983f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ # unreleased changes * `g.Scene#vars` を追加 +* `g.Scene` のアセット読み込み後に任意の非同期処理を行うための `prepare` をサポート + * `g.Game#pushScene()` に第2引数 `PushSceneOption` を追加 + * `g.Game#replaceScene()` の第2引数を `boolean | ReplaceSceneOption` に変更 ## 3.14.0 * @akashic/pdi-types@1.10.0 に追従 From a824ca7399933d9b6aebf918aba5e3fcdf91b7be Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Thu, 27 Jul 2023 12:12:00 +0900 Subject: [PATCH 04/16] =?UTF-8?q?chore:=20=E6=A0=B9=E6=93=9A=20->=20?= =?UTF-8?q?=E6=A0=B9=E6=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/GameSpec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/__tests__/GameSpec.ts b/src/__tests__/GameSpec.ts index 949d5cf23..da39bbe7e 100644 --- a/src/__tests__/GameSpec.ts +++ b/src/__tests__/GameSpec.ts @@ -548,7 +548,7 @@ describe("test Game", () => { sequence.push("scene1 prepared"); done(); }, - 100 // この値にとくに根據は無い + 100 // この値にとくに根拠は無い ); } }); @@ -561,7 +561,7 @@ describe("test Game", () => { sequence.push("scene2 prepared"); done(); }, - 100 // この値にとくに根據は無い + 100 // この値にとくに根拠は無い ); } }); @@ -615,7 +615,7 @@ describe("test Game", () => { sequence.push("scene1 prepared"); done(); }, - 100 // この値にとくに根據は無い + 100 // この値にとくに根拠は無い ); } }); @@ -628,7 +628,7 @@ describe("test Game", () => { sequence.push("scene2 prepared"); done(); }, - 100 // この値にとくに根據は無い + 100 // この値にとくに根拠は無い ); } }); From a84007a1e8f253cc8cc4b308f051264cd22e59fa Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Thu, 27 Jul 2023 13:58:01 +0900 Subject: [PATCH 05/16] fix: do not modify the loading scene --- src/Game.ts | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Game.ts b/src/Game.ts index 72f0319fb..d70173899 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1836,18 +1836,12 @@ export class Game { if (oldScene) { oldScene._deactivate(); } - if (req.prepare) { - this._interruptSceneLoading(req.prepare); - } - this._doPushScene(req.scene, false); + this._doPushScene(req.scene, false, req.prepare ? this._createPreparingLoadingScene(req.prepare) : undefined); break; case PostTickTaskType.ReplaceScene: // NOTE: replaceSceneの場合、pop時点では_sceneChangedをfireしない。_doPushScene() で一度だけfireする。 this._doPopScene(req.preserveCurrent, false, false); - if (req.prepare) { - this._interruptSceneLoading(req.prepare); - } - this._doPushScene(req.scene, false); + this._doPushScene(req.scene, false, req.prepare ? this._createPreparingLoadingScene(req.prepare) : undefined); break; case PostTickTaskType.PopScene: this._doPopScene(req.preserveCurrent, false, true); @@ -2022,19 +2016,20 @@ export class Game { this._modified = true; } - private _interruptSceneLoading(prepare: (done: () => void) => void): void { - const origLoadingScene = this.loadingScene; + /** + * 引数に指定したハンドラが完了するまで待機する空のローディングシーンを作成する。 + */ + private _createPreparingLoadingScene(prepare: (done: () => void) => void): LoadingScene { const loadingScene = new LoadingScene({ game: this, explicitEnd: true }); - // 元のローディングシーンを保持するためクロージャを許容 + // ローディングシーンを保持するためクロージャを許容 loadingScene.onTargetReady.addOnce(() => { - this.loadingScene = origLoadingScene; const done = loadingScene.end.bind(loadingScene); prepare(done); }); - this.loadingScene = loadingScene; + return loadingScene; } private _cleanDB(): void { From fca39e8828ed9539afd526ec4ff985121a2b5745 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Thu, 27 Jul 2023 15:18:43 +0900 Subject: [PATCH 06/16] chore: add comments --- src/Game.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Game.ts b/src/Game.ts index d70173899..ba657abea 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -220,6 +220,9 @@ export interface EventTriggerMap { * Game#pushScene() のオプション */ export interface PushSceneOption { + /** + * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのコールバック。 + */ prepare?: (done: () => void) => void; } @@ -227,7 +230,13 @@ export interface PushSceneOption { * Game#replaceScene() のオプション */ export interface ReplaceSceneOption { + /** + * 現在のシーンを破棄するか否か。 + */ preserveCurrent?: boolean; + /** + * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのコールバック。 + */ prepare?: (done: () => void) => void; } From 9813ec07e6d4e980b203afaf5ba7b43d905c9e39 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 2 Aug 2023 13:58:12 +0900 Subject: [PATCH 07/16] chore: fix comments --- src/Game.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Game.ts b/src/Game.ts index ba657abea..c19f0ded1 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -96,7 +96,7 @@ interface PostTickPushSceneTask { scene: Scene; /** - * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのコールバック。 + * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。 */ prepare?: (done: () => void) => void; } @@ -121,7 +121,7 @@ interface PostTickReplaceSceneTask { preserveCurrent: boolean; /** - * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのコールバック。 + * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。 */ prepare?: (done: () => void) => void; } @@ -221,7 +221,7 @@ export interface EventTriggerMap { */ export interface PushSceneOption { /** - * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのコールバック。 + * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。 */ prepare?: (done: () => void) => void; } @@ -235,7 +235,7 @@ export interface ReplaceSceneOption { */ preserveCurrent?: boolean; /** - * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのコールバック。 + * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。 */ prepare?: (done: () => void) => void; } From 1e967313c56775473dee6c73bebff8a7accfb0a7 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 2 Aug 2023 14:01:37 +0900 Subject: [PATCH 08/16] fix: support for async loading scenes --- src/Scene.ts | 6 +++++- src/__tests__/GameSpec.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Scene.ts b/src/Scene.ts index 2adae01f3..4e4999e80 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -867,7 +867,11 @@ export class Scene implements StorageLoaderHandler { * @private */ _needsLoading(): boolean { - return this._sceneAssetHolder.waitingAssetsCount > 0 || (!!this._storageLoader && !this._storageLoader._loaded); + return ( + this._sceneAssetHolder.waitingAssetsCount > 0 || + (!!this._storageLoader && !this._storageLoader._loaded) || + !!this._currentPrepare + ); } /** diff --git a/src/__tests__/GameSpec.ts b/src/__tests__/GameSpec.ts index da39bbe7e..089c56308 100644 --- a/src/__tests__/GameSpec.ts +++ b/src/__tests__/GameSpec.ts @@ -532,7 +532,7 @@ describe("test Game", () => { // game.scenes テストのため _loaded を待つ必要がある game._onLoad.add(() => { const sequence: string[] = []; - const scene1 = new Scene({ game, assetIds: ["foo"], name: "scene1" }); + const scene1 = new Scene({ game, name: "scene1" }); const scene2 = new Scene({ game, assetIds: ["foo"], name: "scene2" }); scene1.onLoad.addOnce(() => { sequence.push("scene1 loaded"); From 4e782df1587e48e6e7f051c14c888986d3f2f23a Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 2 Aug 2023 17:53:31 +0900 Subject: [PATCH 09/16] fix: modify to wait prepare when popping a scene --- src/Game.ts | 23 +++++++--- src/Scene.ts | 6 +++ src/__tests__/GameSpec.ts | 94 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 5 deletions(-) diff --git a/src/Game.ts b/src/Game.ts index c19f0ded1..973ce16c0 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1845,12 +1845,22 @@ export class Game { if (oldScene) { oldScene._deactivate(); } - this._doPushScene(req.scene, false, req.prepare ? this._createPreparingLoadingScene(req.prepare) : undefined); + req.scene._currentPrepare = req.prepare; + this._doPushScene( + req.scene, + false, + req.prepare ? this._createPreparingLoadingScene(req.prepare, `akashic:preparing-${req.scene.name}`) : undefined + ); break; case PostTickTaskType.ReplaceScene: // NOTE: replaceSceneの場合、pop時点では_sceneChangedをfireしない。_doPushScene() で一度だけfireする。 this._doPopScene(req.preserveCurrent, false, false); - this._doPushScene(req.scene, false, req.prepare ? this._createPreparingLoadingScene(req.prepare) : undefined); + req.scene._currentPrepare = req.prepare; + this._doPushScene( + req.scene, + false, + req.prepare ? this._createPreparingLoadingScene(req.prepare, `akashic:preparing-${req.scene.name}`) : undefined + ); break; case PostTickTaskType.PopScene: this._doPopScene(req.preserveCurrent, false, true); @@ -1938,7 +1948,9 @@ export class Game { // 取り除いた結果スタックトップがロード中のシーンになった場合はローディングシーンを積み直す const nextScene = this.scene(); if (nextScene && nextScene._needsLoading() && nextScene._loadingState !== "loaded-fired") { - const loadingScene = this.loadingScene ?? this._defaultLoadingScene; + const loadingScene = nextScene._currentPrepare + ? this._createPreparingLoadingScene(nextScene._currentPrepare, `akashic:preparing-${nextScene.name}`) + : this.loadingScene ?? this._defaultLoadingScene; this._doPushScene(loadingScene, true, this._defaultLoadingScene); loadingScene.reset(nextScene); } @@ -2028,10 +2040,11 @@ export class Game { /** * 引数に指定したハンドラが完了するまで待機する空のローディングシーンを作成する。 */ - private _createPreparingLoadingScene(prepare: (done: () => void) => void): LoadingScene { + private _createPreparingLoadingScene(prepare: (done: () => void) => void, name?: string): LoadingScene { const loadingScene = new LoadingScene({ game: this, - explicitEnd: true + explicitEnd: true, + name }); // ローディングシーンを保持するためクロージャを許容 loadingScene.onTargetReady.addOnce(() => { diff --git a/src/Scene.ts b/src/Scene.ts index 4e4999e80..72b72e5ff 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -436,6 +436,11 @@ export class Scene implements StorageLoaderHandler { */ _assetHolders: AssetHolder[]; + /** + * @private + */ + _currentPrepare: ((done: () => void) => void) | undefined; + /** * 各種パラメータを指定して `Scene` のインスタンスを生成する。 * @param param 初期化に用いるパラメータのオブジェクト @@ -974,5 +979,6 @@ export class Scene implements StorageLoaderHandler { if (this._loadingState === "loaded-fired") return; this.onLoad.fire(this); this._loadingState = "loaded-fired"; + this._currentPrepare = undefined; // TODO: 本来は _currentPrepare に値を代入する側 (e.g. g.Game) でクリアすべき } } diff --git a/src/__tests__/GameSpec.ts b/src/__tests__/GameSpec.ts index 089c56308..aa2701b05 100644 --- a/src/__tests__/GameSpec.ts +++ b/src/__tests__/GameSpec.ts @@ -394,6 +394,100 @@ describe("test Game", () => { game._startLoadingGlobalAssets(); }); + it("popScene - waiting prepare", done => { + const game = new Game({ + width: 320, + height: 320, + main: "", + assets: { + img1: { + type: "image", + path: "/path1.png", + virtualPath: "path1.png", + width: 1, + height: 1 + }, + img2: { + type: "image", + path: "/path2.png", + virtualPath: "path2.png", + width: 1, + height: 1 + } + } + }); + + game._onLoad.add(() => { + // game.scenes テストのため _loaded を待つ必要がある + const scene1 = new Scene({ game: game, name: "SCENE1", assetIds: ["img1"] }); + const scene2 = new Scene({ game: game, name: "SCENE2" }); + const scene3 = new Scene({ game: game, name: "SCENE3", assetIds: ["img2"] }); + const sequence: string[] = []; + + game.pushScene(scene1, { + prepare: done => { + setTimeout( + () => { + sequence.push("scene1 prepared"); + done(); + }, + 100 // この値にとくに根拠は無い + ); + } + }); + game.pushScene(scene2, { + prepare: done => { + setTimeout( + () => { + sequence.push("scene2 prepared"); + done(); + }, + 100 // この値にとくに根拠は無い + ); + } + }); + game.pushScene(scene3, { + prepare: done => { + setTimeout( + () => { + sequence.push("scene3 prepared"); + done(); + }, + 100 // この値にとくに根拠は無い + ); + } + }); + + scene1.onLoad.addOnce(() => { + sequence.push("scene1 loaded"); + }); + scene2.onLoad.addOnce(() => { + sequence.push("scene2 loaded"); + game.popScene(); + game._flushPostTickTasks(); + }); + scene3.onLoad.addOnce(() => { + sequence.push("scene3 loaded"); + game.popScene(); + game._flushPostTickTasks(); + }); + + scene1.onLoad.addOnce(() => { + expect(sequence).toEqual([ + "scene3 prepared", + "scene3 loaded", + "scene2 prepared", + "scene2 loaded", + "scene1 prepared", + "scene1 loaded" + ]); + done(); + }); + game._flushPostTickTasks(); + }); + game._startLoadingGlobalAssets(); + }); + it("replaceScene", done => { const game = new Game({ width: 320, From af64dd2950eb701d184238785688a62d08a39a66 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Thu, 3 Aug 2023 16:17:19 +0900 Subject: [PATCH 10/16] chore: release _currentPrepare when destroyed --- src/Scene.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Scene.ts b/src/Scene.ts index 72b72e5ff..2fcce2695 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -580,6 +580,7 @@ export class Scene implements StorageLoaderHandler { this._storageLoader = undefined; this.game = undefined!; + this._currentPrepare = undefined; this.state = "destroyed"; this.onStateChange.fire(this.state); From 49bd62401e50da64d695c7e3105d16de4ea1d47f Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Mon, 7 Aug 2023 18:36:13 +0900 Subject: [PATCH 11/16] fix: do not call end() if the game has been destroyed --- src/Game.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Game.ts b/src/Game.ts index 973ce16c0..e9ca0cd59 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -2048,7 +2048,10 @@ export class Game { }); // ローディングシーンを保持するためクロージャを許容 loadingScene.onTargetReady.addOnce(() => { - const done = loadingScene.end.bind(loadingScene); + const done = (): void => { + if (this._isTerminated) return; + loadingScene.end(); + }; prepare(done); }); return loadingScene; From 3acb3e48e34c9162a3550cf0d37c8a7361484cb9 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 9 Aug 2023 15:29:59 +0900 Subject: [PATCH 12/16] fix: modify method arguments --- src/Game.ts | 32 +++++++++++++++++++++++++------- src/Scene.ts | 1 - 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/Game.ts b/src/Game.ts index e9ca0cd59..cb015d6b6 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -97,6 +97,7 @@ interface PostTickPushSceneTask { /** * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。 + * prepare 中にシーンスタックを操作してはいけない点に注意。 */ prepare?: (done: () => void) => void; } @@ -122,6 +123,7 @@ interface PostTickReplaceSceneTask { /** * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。 + * prepare 中にシーンスタックを操作してはいけない点に注意。 */ prepare?: (done: () => void) => void; } @@ -222,6 +224,7 @@ export interface EventTriggerMap { export interface PushSceneOption { /** * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。 + * prepare 中にシーンスタックを操作してはいけない点に注意。 */ prepare?: (done: () => void) => void; } @@ -236,6 +239,7 @@ export interface ReplaceSceneOption { preserveCurrent?: boolean; /** * 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。 + * prepare 中にシーンスタックを操作してはいけない点に注意。 */ prepare?: (done: () => void) => void; } @@ -1845,21 +1849,23 @@ export class Game { if (oldScene) { oldScene._deactivate(); } - req.scene._currentPrepare = req.prepare; this._doPushScene( req.scene, false, - req.prepare ? this._createPreparingLoadingScene(req.prepare, `akashic:preparing-${req.scene.name}`) : undefined + req.prepare + ? this._createPreparingLoadingScene(req.scene, req.prepare, `akashic:preparing-${req.scene.name}`) + : undefined ); break; case PostTickTaskType.ReplaceScene: // NOTE: replaceSceneの場合、pop時点では_sceneChangedをfireしない。_doPushScene() で一度だけfireする。 this._doPopScene(req.preserveCurrent, false, false); - req.scene._currentPrepare = req.prepare; this._doPushScene( req.scene, false, - req.prepare ? this._createPreparingLoadingScene(req.prepare, `akashic:preparing-${req.scene.name}`) : undefined + req.prepare + ? this._createPreparingLoadingScene(req.scene, req.prepare, `akashic:preparing-${req.scene.name}`) + : undefined ); break; case PostTickTaskType.PopScene: @@ -1949,7 +1955,7 @@ export class Game { const nextScene = this.scene(); if (nextScene && nextScene._needsLoading() && nextScene._loadingState !== "loaded-fired") { const loadingScene = nextScene._currentPrepare - ? this._createPreparingLoadingScene(nextScene._currentPrepare, `akashic:preparing-${nextScene.name}`) + ? this._createPreparingLoadingScene(nextScene, nextScene._currentPrepare, `akashic:preparing-${nextScene.name}`) : this.loadingScene ?? this._defaultLoadingScene; this._doPushScene(loadingScene, true, this._defaultLoadingScene); loadingScene.reset(nextScene); @@ -2040,7 +2046,8 @@ export class Game { /** * 引数に指定したハンドラが完了するまで待機する空のローディングシーンを作成する。 */ - private _createPreparingLoadingScene(prepare: (done: () => void) => void, name?: string): LoadingScene { + private _createPreparingLoadingScene(scene: Scene, prepare: (done: () => void) => void, name?: string): LoadingScene { + scene._currentPrepare = prepare; const loadingScene = new LoadingScene({ game: this, explicitEnd: true, @@ -2052,7 +2059,18 @@ export class Game { if (this._isTerminated) return; loadingScene.end(); }; - prepare(done); + const prepare = scene._currentPrepare; + scene._currentPrepare = undefined; + if (prepare) { + prepare(done); + } else { + // NOTE: 異常系ではあるが prepare が存在しない場合は loadingScene.end() を直接呼ぶ + this._postTickTasks.unshift({ + type: PostTickTaskType.Call, + fun: loadingScene.end, + owner: loadingScene + }); + } }); return loadingScene; } diff --git a/src/Scene.ts b/src/Scene.ts index 2fcce2695..2cf8ab1b2 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -980,6 +980,5 @@ export class Scene implements StorageLoaderHandler { if (this._loadingState === "loaded-fired") return; this.onLoad.fire(this); this._loadingState = "loaded-fired"; - this._currentPrepare = undefined; // TODO: 本来は _currentPrepare に値を代入する側 (e.g. g.Game) でクリアすべき } } From 32ca1b45cd0571376abfa8feb26f039dc940e53e Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 9 Aug 2023 15:32:27 +0900 Subject: [PATCH 13/16] chore: _currentPrepare -> _waitingPrepare --- src/Game.ts | 10 +++++----- src/Scene.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Game.ts b/src/Game.ts index cb015d6b6..5e075397f 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1954,8 +1954,8 @@ export class Game { // 取り除いた結果スタックトップがロード中のシーンになった場合はローディングシーンを積み直す const nextScene = this.scene(); if (nextScene && nextScene._needsLoading() && nextScene._loadingState !== "loaded-fired") { - const loadingScene = nextScene._currentPrepare - ? this._createPreparingLoadingScene(nextScene, nextScene._currentPrepare, `akashic:preparing-${nextScene.name}`) + const loadingScene = nextScene._waitingPrepare + ? this._createPreparingLoadingScene(nextScene, nextScene._waitingPrepare, `akashic:preparing-${nextScene.name}`) : this.loadingScene ?? this._defaultLoadingScene; this._doPushScene(loadingScene, true, this._defaultLoadingScene); loadingScene.reset(nextScene); @@ -2047,7 +2047,7 @@ export class Game { * 引数に指定したハンドラが完了するまで待機する空のローディングシーンを作成する。 */ private _createPreparingLoadingScene(scene: Scene, prepare: (done: () => void) => void, name?: string): LoadingScene { - scene._currentPrepare = prepare; + scene._waitingPrepare = prepare; const loadingScene = new LoadingScene({ game: this, explicitEnd: true, @@ -2059,8 +2059,8 @@ export class Game { if (this._isTerminated) return; loadingScene.end(); }; - const prepare = scene._currentPrepare; - scene._currentPrepare = undefined; + const prepare = scene._waitingPrepare; + scene._waitingPrepare = undefined; if (prepare) { prepare(done); } else { diff --git a/src/Scene.ts b/src/Scene.ts index 2cf8ab1b2..792e1bda5 100644 --- a/src/Scene.ts +++ b/src/Scene.ts @@ -439,7 +439,7 @@ export class Scene implements StorageLoaderHandler { /** * @private */ - _currentPrepare: ((done: () => void) => void) | undefined; + _waitingPrepare: ((done: () => void) => void) | undefined; /** * 各種パラメータを指定して `Scene` のインスタンスを生成する。 @@ -580,7 +580,7 @@ export class Scene implements StorageLoaderHandler { this._storageLoader = undefined; this.game = undefined!; - this._currentPrepare = undefined; + this._waitingPrepare = undefined; this.state = "destroyed"; this.onStateChange.fire(this.state); @@ -876,7 +876,7 @@ export class Scene implements StorageLoaderHandler { return ( this._sceneAssetHolder.waitingAssetsCount > 0 || (!!this._storageLoader && !this._storageLoader._loaded) || - !!this._currentPrepare + !!this._waitingPrepare ); } From 89d0fe9985d66b84eead64a9c83096a9123b7c02 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Wed, 9 Aug 2023 16:02:41 +0900 Subject: [PATCH 14/16] chore: fix comment --- src/Game.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Game.ts b/src/Game.ts index 5e075397f..89867de57 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -2053,7 +2053,7 @@ export class Game { explicitEnd: true, name }); - // ローディングシーンを保持するためクロージャを許容 + // prepare 対象シーンを保持するためクロージャを許容 loadingScene.onTargetReady.addOnce(() => { const done = (): void => { if (this._isTerminated) return; From 04ff1b3a6bcd4c3ad69556e54af71da029eeb0b9 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Mon, 14 Aug 2023 15:21:07 +0900 Subject: [PATCH 15/16] fix: use _pushPostTickTask() --- src/Game.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Game.ts b/src/Game.ts index 89867de57..76a7ec668 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -2065,11 +2065,7 @@ export class Game { prepare(done); } else { // NOTE: 異常系ではあるが prepare が存在しない場合は loadingScene.end() を直接呼ぶ - this._postTickTasks.unshift({ - type: PostTickTaskType.Call, - fun: loadingScene.end, - owner: loadingScene - }); + this._pushPostTickTask(loadingScene.end, loadingScene); } }); return loadingScene; From 3a8eaa1e899ae2f953df19a208605db2f969e871 Mon Sep 17 00:00:00 2001 From: yu-ogi Date: Tue, 15 Aug 2023 16:12:40 +0900 Subject: [PATCH 16/16] chore: v3.14.1 --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5a983f0..e37631353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # ChangeLog -# unreleased changes +# 3.14.1 * `g.Scene#vars` を追加 * `g.Scene` のアセット読み込み後に任意の非同期処理を行うための `prepare` をサポート * `g.Game#pushScene()` に第2引数 `PushSceneOption` を追加 diff --git a/package-lock.json b/package-lock.json index c7552cc40..78bf5e267 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@akashic/akashic-engine", - "version": "3.14.0", + "version": "3.14.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@akashic/akashic-engine", - "version": "3.14.0", + "version": "3.14.1", "license": "MIT", "dependencies": { "@akashic/game-configuration": "~1.12.0", diff --git a/package.json b/package.json index c104414cd..d4a8af730 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@akashic/akashic-engine", - "version": "3.14.0", + "version": "3.14.1", "description": "The core library of Akashic Engine", "main": "index.js", "dependencies": {