Fix stale restore after game restart

This commit is contained in:
2026-05-20 22:27:36 +02:00
parent 8258ea2321
commit beac5a2be3
12 changed files with 167 additions and 44 deletions
+40 -4
View File
@@ -30,6 +30,9 @@ class GameLoopModule extends BaseModule {
this.autoSaveQueued = false;
this.resumeAttempted = false;
this.lastInkState = null;
this.clientResetGeneration = 0;
this.restoreGeneration = 0;
this.pendingHistoryRestoreCleanup = null;
// Bind methods using parent's bindMethods utility
this.bindMethods([
@@ -52,6 +55,17 @@ class GameLoopModule extends BaseModule {
'resetClientPlaybackAndDisplay'
]);
}
clearPendingHistoryRestore(reason = 'cancelled') {
if (this.pendingHistoryRestoreCleanup) {
this.pendingHistoryRestoreCleanup(reason);
this.pendingHistoryRestoreCleanup = null;
return;
}
document.dispatchEvent(new CustomEvent('story:history-restoring', {
detail: { active: false, reason }
}));
}
async initialize() {
this.reportProgress(100, "Game loop initialized");
@@ -402,6 +416,12 @@ class GameLoopModule extends BaseModule {
if (options.resetDisplay) {
await this.resetClientPlaybackAndDisplay();
}
const restoreGeneration = ++this.restoreGeneration;
const resetGeneration = this.clientResetGeneration;
const isCurrentRestore = () =>
restoreGeneration === this.restoreGeneration &&
resetGeneration === this.clientResetGeneration;
this.clearPendingHistoryRestore(`${reason}-superseded`);
document.dispatchEvent(new CustomEvent('story:history-restoring', {
detail: { active: true, reason }
}));
@@ -416,10 +436,12 @@ class GameLoopModule extends BaseModule {
const uiController = this.getModule('ui-controller');
if (browserSave && uiController?.displayHandler?.restoreFromHistory) {
await uiController.displayHandler.restoreFromHistory(browserSave);
if (!isCurrentRestore()) return;
}
const audioManager = this.getModule('audio-manager');
if (browserSave?.musicState && audioManager?.restoreMusicState) {
await audioManager.restoreMusicState(browserSave.musicState);
if (!isCurrentRestore()) return;
}
const hasUnrenderedHistory = this.hasUnrenderedHistory(browserSave);
if (hasUnrenderedHistory) {
@@ -430,19 +452,27 @@ class GameLoopModule extends BaseModule {
}));
}
if (hasUnrenderedHistory) {
await this.queueUnrenderedHistoryBlocks(browserSave);
await this.queueUnrenderedHistoryBlocks(browserSave, isCurrentRestore);
if (!isCurrentRestore()) return;
}
if (!hasUnrenderedHistory) {
document.dispatchEvent(new CustomEvent('story:history-restoring', {
detail: { active: false, reason: `${reason}-complete` }
}));
} else {
const clearRestoring = () => {
const clearRestoring = (eventOrReason = 'pending-output-drained') => {
const clearReason = typeof eventOrReason === 'string'
? eventOrReason
: 'pending-output-drained';
document.dispatchEvent(new CustomEvent('story:history-restoring', {
detail: { active: false, reason: 'pending-output-drained' }
detail: { active: false, reason: clearReason }
}));
document.removeEventListener('tts:queue-empty', clearRestoring);
if (this.pendingHistoryRestoreCleanup === clearRestoring) {
this.pendingHistoryRestoreCleanup = null;
}
};
this.pendingHistoryRestoreCleanup = clearRestoring;
document.addEventListener('tts:queue-empty', clearRestoring);
}
}
@@ -522,7 +552,7 @@ class GameLoopModule extends BaseModule {
}
}
async queueUnrenderedHistoryBlocks(saveRecord = {}) {
async queueUnrenderedHistoryBlocks(saveRecord = {}, isCurrentRestore = null) {
const storyHistory = this.getModule('story-history');
const textBuffer = this.getModule('text-buffer');
if (!storyHistory || !textBuffer || typeof textBuffer.addBlocks !== 'function') return;
@@ -530,10 +560,16 @@ class GameLoopModule extends BaseModule {
const end = Math.max(0, Number(saveRecord.latestBlockId || 0));
if (end < start) return;
const blocks = await storyHistory.getBlocksRange(saveRecord.gameId, start, end);
if (typeof isCurrentRestore === 'function' && !isCurrentRestore()) return;
if (saveRecord.gameId && storyHistory.currentGameId !== saveRecord.gameId) return;
textBuffer.addBlocks(blocks);
}
async resetClientPlaybackAndDisplay() {
this.clientResetGeneration += 1;
this.restoreGeneration += 1;
this.clearPendingHistoryRestore('client-reset');
const playbackCoordinator = this.getModule('playback-coordinator');
if (playbackCoordinator && typeof playbackCoordinator.stop === 'function') {
await playbackCoordinator.stop();