Skip to content

Commit

Permalink
Merge pull request #456 from akashic-games/support-scene-prepare
Browse files Browse the repository at this point in the history
feat: support 'prepare' phase before the scene load
  • Loading branch information
yu-ogi authored Aug 15, 2023
2 parents 8ae388f + 3a8eaa1 commit ae7c647
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 11 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# ChangeLog

# 3.14.1
* `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 に追従
* `"binary"` アセットに対応
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
113 changes: 106 additions & 7 deletions src/Game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ interface PostTickPushSceneTask {
* 遷移先になるシーン。
*/
scene: Scene;

/**
* 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。
* prepare 中にシーンスタックを操作してはいけない点に注意。
*/
prepare?: (done: () => void) => void;
}

/**
Expand All @@ -114,6 +120,12 @@ interface PostTickReplaceSceneTask {
* 現在のシーンを破棄するか否か。
*/
preserveCurrent: boolean;

/**
* 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。
* prepare 中にシーンスタックを操作してはいけない点に注意。
*/
prepare?: (done: () => void) => void;
}

/**
Expand Down Expand Up @@ -206,6 +218,32 @@ export interface EventTriggerMap {
operation: Trigger<OperationEvent>;
}

/**
* Game#pushScene() のオプション
*/
export interface PushSceneOption {
/**
* 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。
* prepare 中にシーンスタックを操作してはいけない点に注意。
*/
prepare?: (done: () => void) => void;
}

/**
* Game#replaceScene() のオプション
*/
export interface ReplaceSceneOption {
/**
* 現在のシーンを破棄するか否か。
*/
preserveCurrent?: boolean;
/**
* 現在のシーンのアセット読み込み後、任意の非同期処理を行うためのハンドラ。
* prepare 中にシーンスタックを操作してはいけない点に注意。
*/
prepare?: (done: () => void) => void;
}

export type GameMainFunction = (g: any, args: GameMainParameterObject) => void;

/**
Expand Down Expand Up @@ -969,11 +1007,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
});
}

Expand All @@ -990,11 +1030,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
});
}

Expand Down Expand Up @@ -1792,12 +1849,24 @@ export class Game {
if (oldScene) {
oldScene._deactivate();
}
this._doPushScene(req.scene, false);
this._doPushScene(
req.scene,
false,
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);
this._doPushScene(req.scene, false);
this._doPushScene(
req.scene,
false,
req.prepare
? this._createPreparingLoadingScene(req.scene, req.prepare, `akashic:preparing-${req.scene.name}`)
: undefined
);
break;
case PostTickTaskType.PopScene:
this._doPopScene(req.preserveCurrent, false, true);
Expand Down Expand Up @@ -1885,7 +1954,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._waitingPrepare
? this._createPreparingLoadingScene(nextScene, nextScene._waitingPrepare, `akashic:preparing-${nextScene.name}`)
: this.loadingScene ?? this._defaultLoadingScene;
this._doPushScene(loadingScene, true, this._defaultLoadingScene);
loadingScene.reset(nextScene);
}
Expand Down Expand Up @@ -1972,6 +2043,34 @@ export class Game {
this._modified = true;
}

/**
* 引数に指定したハンドラが完了するまで待機する空のローディングシーンを作成する。
*/
private _createPreparingLoadingScene(scene: Scene, prepare: (done: () => void) => void, name?: string): LoadingScene {
scene._waitingPrepare = prepare;
const loadingScene = new LoadingScene({
game: this,
explicitEnd: true,
name
});
// prepare 対象シーンを保持するためクロージャを許容
loadingScene.onTargetReady.addOnce(() => {
const done = (): void => {
if (this._isTerminated) return;
loadingScene.end();
};
const prepare = scene._waitingPrepare;
scene._waitingPrepare = undefined;
if (prepare) {
prepare(done);
} else {
// NOTE: 異常系ではあるが prepare が存在しない場合は loadingScene.end() を直接呼ぶ
this._pushPostTickTask(loadingScene.end, loadingScene);
}
});
return loadingScene;
}

private _cleanDB(): void {
this.db.clean();
this._localDb.clean();
Expand Down
21 changes: 20 additions & 1 deletion src/Scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,13 @@ export class Scene implements StorageLoaderHandler {
*/
operation: Trigger<OperationEvent>;

/**
* ゲーム開発者向けのコンテナ。
*
* この値はゲームエンジンのロジックからは使用されず、ゲーム開発者は任意の目的に使用してよい。
*/
vars: any;

/**
* @private
*/
Expand Down Expand Up @@ -429,6 +436,11 @@ export class Scene implements StorageLoaderHandler {
*/
_assetHolders: AssetHolder<SceneRequestAssetHandler>[];

/**
* @private
*/
_waitingPrepare: ((done: () => void) => void) | undefined;

/**
* 各種パラメータを指定して `Scene` のインスタンスを生成する。
* @param param 初期化に用いるパラメータのオブジェクト
Expand Down Expand Up @@ -464,6 +476,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;
Expand Down Expand Up @@ -558,6 +571,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();
Expand All @@ -566,6 +580,7 @@ export class Scene implements StorageLoaderHandler {
this._storageLoader = undefined;

this.game = undefined!;
this._waitingPrepare = undefined;

this.state = "destroyed";
this.onStateChange.fire(this.state);
Expand Down Expand Up @@ -858,7 +873,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._waitingPrepare
);
}

/**
Expand Down
Loading

0 comments on commit ae7c647

Please sign in to comment.