Reuse spanning-aware prepared plan at activate (kill pre-playback pause)
A spanning block stalled ~2.1s before it began playing: activate ran prepareRevealBlock with forceRebuild, synchronously redrawing the start spread (and preloading the continuation spread) on the main thread, because the lookahead plan had been built with right-only timing before pagination committed the overflow. Build the start-spread plan spanning-aware during lookahead instead: when the preview layout shows the block overflows, derive its timing across both preview spreads (via the revealSpreadSourceOverride) and cache it. activate then reuses that plan — the same fast cached-plan path non-spanning blocks already use — with no synchronous redraw. forceRebuild is kept only as a fallback when a block spans but was not prepared spanning-aware (e.g. an immediate prepare with no preview layout), and an evicted plan still rebuilds correctly because pagination is committed by then. Verified live: the spanning block's pre-playback gap dropped from ~2088ms to 139ms (equal to non-spanning blocks), while the right line still reveals over its area share (~3.3s), the continuation still animates from the start, and there are no fast-forwards or problems. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -206,30 +206,39 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
});
|
||||
if (!previewSpread) return null;
|
||||
|
||||
const revealDetail = this.createRevealDetail(sentence, previewSpread, 'prepare');
|
||||
const texturePlan = this.textureRenderer.prepareRevealBlock(revealDetail, {
|
||||
phase: 'prepare',
|
||||
publishEvent: false
|
||||
});
|
||||
|
||||
// If this block overflows onto the next spread, prepare that continuation spread's
|
||||
// reveal plan now (lookahead/background) so revealContinuationSpread reuses it after the
|
||||
// flip instead of redrawing synchronously. Only during background prepares and when the
|
||||
// preview layout is available; otherwise the continuation falls back to the redraw path.
|
||||
if (options.immediate !== true && Array.isArray(previewSpread.previewSpreads)) {
|
||||
const startIndex = Math.max(0, Number(previewSpread.index || 0));
|
||||
const continuationSpread = previewSpread.previewSpreads
|
||||
// Detect a spanning block from the (not-yet-committed) preview layout so its reveal
|
||||
// plan can be prepared spanning-aware in the background, off the critical path. Only
|
||||
// during background prepares and when the preview layout is available; otherwise we
|
||||
// fall back to the synchronous activate rebuild + continuation redraw (today's path).
|
||||
const previewSpreads = options.immediate !== true && Array.isArray(previewSpread.previewSpreads)
|
||||
? previewSpread.previewSpreads
|
||||
: null;
|
||||
const startIndex = Math.max(0, Number(previewSpread.index || 0));
|
||||
const continuationSpread = previewSpreads
|
||||
? (previewSpreads
|
||||
.filter(spread => spread
|
||||
&& Number(spread.index) > startIndex
|
||||
&& this.getBlockRevealSides(spread, sentence.blockId).length > 0)
|
||||
.sort((a, b) => Number(a.index) - Number(b.index))[0] || null;
|
||||
if (continuationSpread) {
|
||||
this.textureRenderer.prepareContinuationRevealPlan?.({
|
||||
...revealDetail,
|
||||
previewSpreads: previewSpread.previewSpreads,
|
||||
continuationSpread
|
||||
});
|
||||
}
|
||||
.sort((a, b) => Number(a.index) - Number(b.index))[0] || null)
|
||||
: null;
|
||||
const spanningPlanPrepared = Boolean(continuationSpread);
|
||||
|
||||
const revealDetail = this.createRevealDetail(sentence, previewSpread, 'prepare');
|
||||
// For a spanning block, derive the start spread's timing across both preview spreads so
|
||||
// the cached plan already spans both pages and activate can reuse it (no rebuild).
|
||||
const texturePlan = this.textureRenderer.prepareRevealBlock(
|
||||
spanningPlanPrepared ? { ...revealDetail, previewSpreads } : revealDetail,
|
||||
{ phase: 'prepare', publishEvent: false }
|
||||
);
|
||||
|
||||
// Also prepare the continuation spread's plan so revealContinuationSpread reuses it
|
||||
// after the flip instead of redrawing synchronously.
|
||||
if (continuationSpread) {
|
||||
this.textureRenderer.prepareContinuationRevealPlan?.({
|
||||
...revealDetail,
|
||||
previewSpreads,
|
||||
continuationSpread
|
||||
});
|
||||
}
|
||||
|
||||
const targetSpreadIndex = Math.max(0, Number(previewSpread.index || 0));
|
||||
@@ -247,6 +256,9 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
revealSides,
|
||||
requiresPreFlip: targetSpreadIndex > currentSpreadIndex,
|
||||
requiresRightFlip: revealSides.includes('right') && this.requiresRightPageFlipAfterReveal(previewSpread),
|
||||
// True when this block spans and its plan was prepared spanning-aware in the
|
||||
// background, so activate can reuse it without a synchronous forceRebuild.
|
||||
spanningPlanPrepared,
|
||||
// Snapshot the reveal timings now. A reused lookahead segment can be played by
|
||||
// a sentence instance whose animation timings were lost; without them the
|
||||
// reveal can't be word-paced and stretches across the whole TTS.
|
||||
@@ -313,13 +325,14 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
}
|
||||
const spread = segment.activeSpread || segment.previewSpread;
|
||||
const revealDetail = this.createRevealDetail(sentence, spread, 'activate');
|
||||
// For a spanning block the prepared reveal plan was built during lookahead before
|
||||
// the continuation was committed, so it is right-only. Rebuild from the committed
|
||||
// spreads so the reveal timing spans both pages (right page no longer absorbs the
|
||||
// whole duration) and the continuation has reveal regions.
|
||||
// A spanning block needs timing across both pages. When the plan was prepared
|
||||
// spanning-aware during lookahead (the common case), reuse it — no synchronous redraw
|
||||
// on the critical path. Only when it spans but was NOT prepared spanning-aware (e.g. an
|
||||
// immediate prepare with no preview layout) do we rebuild from the committed spreads.
|
||||
const forceRebuild = segment.spansToNextSpread === true && segment.spanningPlanPrepared !== true;
|
||||
const texturePlan = this.textureRenderer.prepareRevealBlock(revealDetail, {
|
||||
publishEvent: false,
|
||||
forceRebuild: segment.spansToNextSpread === true
|
||||
forceRebuild
|
||||
});
|
||||
segment.activeTexturePlan = texturePlan;
|
||||
this.applyTexturePlan(texturePlan, segment, 'activate');
|
||||
|
||||
Reference in New Issue
Block a user