-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support 'prepare' phase before the scene load #456
Conversation
src/__tests__/GameSpec.ts
Outdated
sequence.push("scene1 prepared"); | ||
done(); | ||
}, | ||
100 // この値にとくに根據は無い |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
全くどうでもいいんですが、旧字体の「據」よりは常用漢字の「拠」にしときませんか。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a824ca7 ミスでした。修正済みです。
src/Game.ts
Outdated
preserveCurrent?: boolean; | ||
prepare?: (done: () => void) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このあたりのプロパティにもコメントつけておきたいです。(ここにないと API リファレンスに prepare
の説明が出るところがなさそうです)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fca39e8 こちらコメント追加しました。
src/Game.ts
Outdated
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.loadingScene
をここで代入する形になっていますが、よく見ると _doPushScene()
が引数で loadingScene
指定できるので、そちらで済まないでしょうか?
Game#loadingScene
を書き換えると正しく戻せるかどうかかなり見極めが必要そうなので (特に pushScene() が同期的に複数回呼ばれた場合) 、書き換えなしで済むならその方がすっきりするかと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a84007a おっしゃるとおり_doPushScene()
を使うパターンでもテスト上問題なさそうだったため修正しました。
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_doPopScene()
の中にもローディングシーンを積み直すパスがありますが、この時 prepare
が呼ばれるか確認したいです。(難儀そうな気がしています)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
popScene()
の際にも prepare
を呼ぶ (待つ) べきでしょうか。念のため確認しておきたいです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
はい。当初考えていませんでしたが、状況によって待つ必要がありそうに思えています。(念のため: これは popScene()
に引数を追加して popScene(scene, { prepare })
にするという話ではありません)
具体的には次のような状況を気にしています。
const scene1 = new g.Scene({ ..., assetIds: ["foo"] }); // なんらかのアセット読み込みが必要なシーン
const scene2 = new g.Scene(...); // 適当なシーン
g.game.pushScene(scene1, { prepare: (done) => { someAsyncOp().then(done); } });
g.game.pushScene(scene2);
g.game.popScene();
ロード中のシーン (scene1) のロード完了を待たずに次のシーン (scene2) が push された状況では、pop して scene1 に戻る時にローディングシーンを積み直してロード完了を改めて待つ必要があります。(過去、この「pop 時に発生するロード待ち」ができておらず不具合となったことがあります) このとき、prepare が指定されているなら呼ばないと不味そうです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、確かにシーンロード中に新たに別のシーンをロードするケースは想定できていませんでした。対応を考えます。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4e782df にて対応しました。
g.Scene#_currentPrepare
を新設し、g.Game#popScene()
時に g.Game
がその中身を参照するように修正しています。
src/Scene.ts
Outdated
return ( | ||
this._sceneAssetHolder.waitingAssetsCount > 0 || | ||
(!!this._storageLoader && !this._storageLoader._loaded) || | ||
!!this._currentPrepare |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
読み込むアセットの無い scene に対して prepare
を設定していた場合に prepare
を待たず onLoad
が発火してしまっていたため条件を変更しました。
src/Game.ts
Outdated
}); | ||
// ローディングシーンを保持するためクロージャを許容 | ||
loadingScene.onTargetReady.addOnce(() => { | ||
const done = loadingScene.end.bind(loadingScene); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これ、 prepare
が外部の非同期処理を想定する関係上、 done
が呼ばれた時に「 Scene
や Game
が既に破棄されていた状況」を想定しなければならないことに気づきました。
loadingScene#end
そのまま渡すのではなく、「" Game
が死んでいたら何もせず抜ける" などチェックをした上で end
を呼ぶ関数」を使う必要がありそうです。
(類例として、非同期処理後に逐一破棄チェックしている GameDriver
があります。あちらは正確には 例外で強引に処理を潰してる のでいい状態でもありませんが)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
49bd624 こちら対応しました。
src/Game.ts
Outdated
explicitEnd: true, | ||
name | ||
}); | ||
// ローディングシーンを保持するためクロージャを許容 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
今更ですがこれコメント正しいでしょうか。クロージャで保持してるものは prepare
のように見えます。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
89d0fe9 こちら実態に沿うようにコメント修正しました。
src/Scene.ts
Outdated
@@ -961,5 +980,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) でクリアすべき |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO コメントになっていますが、これ _createPreparingLoadingScene()
で prepare
の代わりに Scene
を引数にとり、 onTargetReady
のハンドラで prepare
を呼ぶ直前にクリアすればよいのではないでしょうか。
これには二つの意味があるように思えています:
- a)
_currentPrepare
を触る箇所を (Game
に) 限定できて嬉しい (TODO コメントどおり) - b)
prepare()
を呼ぶ前に_currentPrepare
をクリアしないと、二重に呼ぶ懸念が残りそう
後者が問題になるケースとして考えているのは「 prepare()
の呼び出し後 done()
で返るまでの間にまた pushScene()
〜 popScene()
が走った時」です。(アセットロード中に pushScene() されるケースを織り込む一方で、 prepare 中のそれを想定しないのは片手落ちなので)
ただこのケースは、真面目に考慮しはじめると大掛かりになりすぎる気もします。どちらかというと (もともとエッジケースを十分に考慮できていたとは言えない) explicitEnd
の問題修正を始めることになってしまうので……。この PR では一旦「 prepare 中にシーンスタックを操作してはいけない」という制限事項を加えて済ませるのも手かと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
備忘: 危うそうなところ
prepare()
の呼び出し後 done()
までの間にまた pushScene()
され (a)、さらにその後 popScene()
された (b) 時、
- i) (b) 時点で、
Scene#_currentPrepare
が残っているともう一回prepare
を呼びそう - ii) (a) 時点では「
Game
も対象のScene
も生きているが、 自分 (LoadingScene
) がスタックトップにいない」ので、ここでdone()
されるとまずそうdone()
はLoadingScene#end()
を呼び、そこでpopSceneRaw()
が走ってスタックトップから自身 (ローディングシーン) を取り除こうとする=おそらくスタックが壊れる- しかし逆に
LoadingScene#end()
しないと、「 prepare が走ってdone()
も呼ばれた」対象のシーンのonLoad
を呼ぶ契機がなくなる
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_createPreparingLoadingScene()
でscene
とprepare
を指定するように修正しました。- 呼び出し箇所が複数あり
scene._currentPrepare
への代入処理をまとめるため引数を分けました
- 呼び出し箇所が複数あり
prepare 中にシーンスタックを操作してはいけない
という旨のコメントし、一旦は上記を制限事項とする方針にしました。
src/Game.ts
Outdated
prepare(done); | ||
} else { | ||
// NOTE: 異常系ではあるが prepare が存在しない場合は loadingScene.end() を直接呼ぶ | ||
this._postTickTasks.unshift({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これ unshift が必要でしょうか? _pushPostTickTask() でよさそうですが。どっちみち相当な異常系なのでこだわりませんが、事情があるならコメントしておきたいように思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
04ff1b3 こちら修正しました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
コメント加えていますが approved.
このpull requestが解決する内容
g.Scene
のアセット読み込み後、任意の非同期処理を行うためのprepare
をサポートします。g.Scene#vars
を追加します。この値はゲームエンジンのロジックからは使用されず、ゲーム開発者が任意の値を代入することができます。コードイメージは以下のようになります。
破壊的な変更を含んでいるか?