Prepare spanning-block continuation spread in background (kill post-flip redraw)
For a paragraph that overflows onto the next spread, the continuation page was redrawn synchronously after the flip (drawSpread on the main thread), so the next page stayed blank for ~2.7s and then the carried-over lines popped in already ~24% revealed instead of animating from the start. Move that work off the critical path: during lookahead, prepare and cache the continuation spread's reveal plan using the not-yet-committed preview spreads (so per-line timing is computed across both spreads), then reuse it after the flip instead of redrawing. - pagination: expose the preview spread layout on the returned preview spread so the owner can detect the continuation spread (race-free; each call owns its preparedSpreads). - renderer: revealSpreadSourceOverride lets region collection use preview spreads during lookahead; prepareContinuationRevealPlan draws+caches the continuation plan (publishEvent off); takeContinuationRevealPlan reuses it, re-stamped as an activate-phase publish. - timeline: prepare the continuation plan during background (non-immediate) prepares; revealContinuationSpread reuses it, falling back to the redraw when none was prepared. Verified live on a spanning block: continuation now appears ~0.25s after the flip (was ~2.7s) at ve~3471 = the right line's duration, i.e. it animates from the start (no pop-in), runs to ~full over the TTS, no fast-forward, no continuation-missing problems. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -212,6 +212,26 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
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
|
||||
.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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const targetSpreadIndex = Math.max(0, Number(previewSpread.index || 0));
|
||||
const currentSpreadIndex = this.getVisibleSpreadIndex();
|
||||
const revealSides = this.getBlockRevealSides(previewSpread, sentence.blockId);
|
||||
@@ -477,8 +497,11 @@ class BookPlaybackTimelineModule extends BaseModule {
|
||||
async revealContinuationSpread(segment = {}, spread = null) {
|
||||
const sentence = segment.sentence;
|
||||
if (!sentence || !spread) return false;
|
||||
const revealDetail = this.createRevealDetail(sentence, spread, 'activate');
|
||||
const texturePlan = this.textureRenderer.prepareRevealBlock(revealDetail, { publishEvent: false });
|
||||
// Reuse the continuation plan prepared during lookahead (no synchronous redraw on the
|
||||
// critical path). Falls back to rebuilding the plan if none was prepared.
|
||||
const reused = this.textureRenderer.takeContinuationRevealPlan?.(segment.blockId, spread.index);
|
||||
const texturePlan = reused
|
||||
|| this.textureRenderer.prepareRevealBlock(this.createRevealDetail(sentence, spread, 'activate'), { publishEvent: false });
|
||||
if (!texturePlan) {
|
||||
this.pageCache?.recordProblem?.({
|
||||
type: 'timeline-reveal-continuation-missing',
|
||||
|
||||
Reference in New Issue
Block a user