Restore WebGL reveal timing diagnostics
This commit is contained in:
@@ -17,6 +17,7 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
this.activeSegment = null;
|
||||
this.preparedSegments = new Map();
|
||||
this.timelineDiagnostics = [];
|
||||
this.benchmarkEntries = [];
|
||||
this.ownsPageFlipCommit = true;
|
||||
|
||||
this.bindMethods([
|
||||
@@ -29,6 +30,7 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
'createRevealDetail',
|
||||
'requiresSpreadTransition',
|
||||
'requiresRightPageFlipAfterReveal',
|
||||
'getBlockRevealSides',
|
||||
'waitForVisualCompletion',
|
||||
'waitForRevealCommit',
|
||||
'requestPageFlip',
|
||||
@@ -37,6 +39,8 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
'getPageMetaForIndex',
|
||||
'getVisibleSpreadIndex',
|
||||
'isChoiceAwaitingPlayer',
|
||||
'markBenchmark',
|
||||
'timeStage',
|
||||
'recordDiagnostic',
|
||||
'getRuntimeState'
|
||||
]);
|
||||
@@ -48,13 +52,33 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
this.pageCache = this.getModule('webgl-page-cache');
|
||||
this.playbackCoordinator = this.getModule('playback-coordinator');
|
||||
this.sentenceQueue = this.getModule('sentence-queue');
|
||||
this.addEventListener(document, 'webgl-book:page-reveal-start', (event) => {
|
||||
this.markBenchmark('reveal-start', {
|
||||
blockId: event.detail?.blockId ?? null
|
||||
});
|
||||
});
|
||||
this.addEventListener(document, 'webgl-book:reveal-committed', (event) => {
|
||||
this.markBenchmark('reveal-committed', {
|
||||
blockId: event.detail?.blockIds?.[0] ?? null,
|
||||
side: event.detail?.side || null,
|
||||
pageFlipAfterReveal: event.detail?.pageFlipAfterReveal === true
|
||||
});
|
||||
});
|
||||
this.addEventListener(document, 'webgl-book:page-flip-started', (event) => {
|
||||
this.markBenchmark('flip-started', event.detail || {});
|
||||
});
|
||||
this.addEventListener(document, 'webgl-book:page-flip-finished', (event) => {
|
||||
this.markBenchmark('flip-finished', event.detail || {});
|
||||
});
|
||||
window.BookPlaybackTimeline = this;
|
||||
this.reportProgress(100, 'Book playback timeline ready');
|
||||
return true;
|
||||
}
|
||||
|
||||
async playSentence(sentence = {}) {
|
||||
const segment = await this.prepareSentence(sentence, { immediate: true });
|
||||
const segment = await this.timeStage('prepare-current', { blockId: sentence.blockId ?? null }, () => {
|
||||
return this.prepareSentence(sentence, { immediate: true });
|
||||
});
|
||||
if (!segment) {
|
||||
return this.playbackCoordinator?.play?.(sentence);
|
||||
}
|
||||
@@ -63,11 +87,11 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
this.recordDiagnostic('segment-play:start', segment);
|
||||
|
||||
if (this.requiresSpreadTransition(segment)) {
|
||||
const flipped = await this.requestPageFlip(1, {
|
||||
const flipped = await this.timeStage('preplay-flip', segment, () => this.requestPageFlip(1, {
|
||||
reason: 'timeline-preplay-spread-transition',
|
||||
targetSpread: segment.targetSpreadIndex,
|
||||
force: true
|
||||
});
|
||||
}));
|
||||
if (!flipped) {
|
||||
this.pageCache?.recordProblem?.({
|
||||
type: 'timeline-preplay-flip-failed',
|
||||
@@ -77,10 +101,12 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
}
|
||||
}
|
||||
|
||||
await this.activatePreparedSegment(segment, sentence);
|
||||
await this.timeStage('activate', segment, () => this.activatePreparedSegment(segment, sentence));
|
||||
|
||||
const visualPromise = this.waitForVisualCompletion(segment);
|
||||
const playbackPromise = this.playbackCoordinator?.play?.(sentence) || Promise.resolve();
|
||||
const playbackPromise = this.timeStage('playback', segment, () => {
|
||||
return this.playbackCoordinator?.play?.(sentence) || Promise.resolve();
|
||||
});
|
||||
await Promise.all([playbackPromise, visualPromise]);
|
||||
|
||||
this.recordDiagnostic('segment-play:end', segment);
|
||||
@@ -94,7 +120,10 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
const existing = sentence.webglBookPresentation?.timelineSegment || this.preparedSegments.get(key);
|
||||
if (existing && options.force !== true) return existing;
|
||||
this.ensureAnimationTimings(sentence);
|
||||
const segment = await this.createPreparedSegment(sentence, options);
|
||||
const segment = await this.timeStage(options.immediate === true ? 'segment-prepare-immediate' : 'segment-prepare-lookahead', {
|
||||
blockId: sentence.blockId,
|
||||
id: sentence.id
|
||||
}, () => this.createPreparedSegment(sentence, options));
|
||||
if (!segment) return null;
|
||||
this.preparedSegments.set(segment.key, segment);
|
||||
sentence.webglBookPresentation = {
|
||||
@@ -121,6 +150,7 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
|
||||
const targetSpreadIndex = Math.max(0, Number(previewSpread.index || 0));
|
||||
const currentSpreadIndex = this.getVisibleSpreadIndex();
|
||||
const revealSides = this.getBlockRevealSides(previewSpread, sentence.blockId);
|
||||
const segment = {
|
||||
key: `${sentence.gameId || sentence.metadata?.gameId || 'game'}:${sentence.blockId}`,
|
||||
id: sentence.id,
|
||||
@@ -129,13 +159,14 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
previewSpread,
|
||||
targetSpreadIndex,
|
||||
currentSpreadIndex,
|
||||
revealSides,
|
||||
requiresPreFlip: targetSpreadIndex > currentSpreadIndex,
|
||||
requiresRightFlip: this.requiresRightPageFlipAfterReveal(previewSpread),
|
||||
requiresRightFlip: revealSides.includes('right') && this.requiresRightPageFlipAfterReveal(previewSpread),
|
||||
preparedAt: performance.now(),
|
||||
status: 'prepared'
|
||||
};
|
||||
|
||||
await this.prewarmSegmentTextures(segment);
|
||||
await this.timeStage('texture-prewarm', segment, () => this.prewarmSegmentTextures(segment));
|
||||
if (options.immediate !== true) {
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
}
|
||||
@@ -149,7 +180,9 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
});
|
||||
segment.activeSpread = activeSpread || segment.previewSpread;
|
||||
segment.targetSpreadIndex = Math.max(0, Number(segment.activeSpread?.index ?? segment.targetSpreadIndex ?? 0));
|
||||
segment.requiresRightFlip = this.requiresRightPageFlipAfterReveal(segment.activeSpread || segment.previewSpread);
|
||||
segment.revealSides = this.getBlockRevealSides(segment.activeSpread || segment.previewSpread, sentence.blockId);
|
||||
segment.requiresRightFlip = segment.revealSides.includes('right')
|
||||
&& this.requiresRightPageFlipAfterReveal(segment.activeSpread || segment.previewSpread);
|
||||
|
||||
const revealDetail = this.createRevealDetail(sentence, segment.activeSpread || segment.previewSpread, 'activate');
|
||||
this.textureRenderer.prepareRevealBlock(revealDetail);
|
||||
@@ -192,15 +225,27 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
return maxLine >= Math.max(1, Number(meta.linesPerPage || 25));
|
||||
}
|
||||
|
||||
getBlockRevealSides(spread = {}, blockId = null) {
|
||||
const id = String(blockId ?? '');
|
||||
if (!id) return [];
|
||||
return ['left', 'right'].filter((side) => {
|
||||
const lines = Array.isArray(spread?.[side]) ? spread[side] : [];
|
||||
return lines.some(line => String(line?.blockId ?? '') === id);
|
||||
});
|
||||
}
|
||||
|
||||
async waitForVisualCompletion(segment = {}) {
|
||||
if (!segment.requiresRightFlip) return;
|
||||
const committed = await this.waitForRevealCommit(segment);
|
||||
if (!segment.requiresRightFlip || !Array.isArray(segment.revealSides) || !segment.revealSides.includes('right')) {
|
||||
this.recordDiagnostic('visual-completion:no-right-flip-wait', segment);
|
||||
return;
|
||||
}
|
||||
const committed = await this.timeStage('wait-right-reveal-commit', segment, () => this.waitForRevealCommit(segment));
|
||||
if (!committed || this.isChoiceAwaitingPlayer()) return;
|
||||
await this.requestPageFlip(1, {
|
||||
await this.timeStage('right-page-flip', segment, () => this.requestPageFlip(1, {
|
||||
reason: 'timeline-right-page-filled',
|
||||
targetSpread: Math.max(0, Number(segment.targetSpreadIndex || this.getVisibleSpreadIndex()) + 1),
|
||||
force: true
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
waitForRevealCommit(segment = {}) {
|
||||
@@ -350,18 +395,58 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
blockId: segment.blockId ?? null,
|
||||
spreadIndex: segment.targetSpreadIndex ?? null,
|
||||
status: segment.status || null,
|
||||
revealSides: Array.isArray(segment.revealSides) ? segment.revealSides : [],
|
||||
at: Math.round(performance.now())
|
||||
});
|
||||
while (this.timelineDiagnostics.length > 200) this.timelineDiagnostics.shift();
|
||||
document.documentElement.dataset.webglBookTimeline = type;
|
||||
}
|
||||
|
||||
markBenchmark(stage, detail = {}, startedAt = null) {
|
||||
const now = performance.now();
|
||||
const entry = {
|
||||
stage,
|
||||
blockId: detail.blockId ?? null,
|
||||
spreadIndex: detail.targetSpreadIndex ?? detail.spreadIndex ?? detail.targetSpread ?? null,
|
||||
durationMs: Number.isFinite(Number(startedAt)) ? Math.round((now - Number(startedAt)) * 100) / 100 : null,
|
||||
at: Math.round(now),
|
||||
detail: {
|
||||
status: detail.status || null,
|
||||
revealSides: Array.isArray(detail.revealSides) ? detail.revealSides : undefined,
|
||||
reason: detail.reason || null,
|
||||
side: detail.side || null,
|
||||
pageFlipAfterReveal: detail.pageFlipAfterReveal === true
|
||||
}
|
||||
};
|
||||
this.benchmarkEntries.push(entry);
|
||||
while (this.benchmarkEntries.length > 240) this.benchmarkEntries.shift();
|
||||
document.documentElement.dataset.webglBookBenchmark = JSON.stringify(this.benchmarkEntries.slice(-40));
|
||||
return entry;
|
||||
}
|
||||
|
||||
async timeStage(stage, detail = {}, callback = null) {
|
||||
const startedAt = performance.now();
|
||||
this.markBenchmark(`${stage}:start`, detail);
|
||||
try {
|
||||
const result = await callback?.();
|
||||
this.markBenchmark(`${stage}:end`, detail, startedAt);
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.markBenchmark(`${stage}:error`, {
|
||||
...detail,
|
||||
reason: error?.message || String(error)
|
||||
}, startedAt);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
getRuntimeState() {
|
||||
return {
|
||||
activeBlockId: this.activeSegment?.blockId ?? null,
|
||||
preparedSegmentCount: this.preparedSegments.size,
|
||||
ownsPageFlipCommit: this.ownsPageFlipCommit,
|
||||
diagnostics: this.timelineDiagnostics.slice(-20)
|
||||
diagnostics: this.timelineDiagnostics.slice(-20),
|
||||
benchmark: this.benchmarkEntries.slice(-40)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user