Stabilize WebGL book pagination restore

This commit is contained in:
2026-06-09 16:42:12 +02:00
parent fe51410a3b
commit 171cafeb65
7 changed files with 315 additions and 85 deletions
+84 -6
View File
@@ -19,11 +19,16 @@ class BookPaginationModule extends BaseModule {
this.latestBlockId = 0;
this.latestRenderedBlockId = 0;
this.appliedPageReserveBlocks = new Set();
this.preparedBlockCache = new Map();
this.bindMethods([
'initialize',
'refreshFromHistory',
'preparePendingBlock',
'getPreparedBlockCacheKey',
'rememberPreparedBlock',
'takePreparedBlock',
'clearPreparedBlocks',
'buildSpreads',
'buildPages',
'buildSpreadsFromPages',
@@ -86,6 +91,10 @@ class BookPaginationModule extends BaseModule {
this.setCurrentSpread(this.currentSpreadIndex + direction);
}
});
this.pages = this.buildPages([]);
this.spreads = this.buildSpreadsFromPages(this.pages);
this.currentSpreadIndex = 0;
this.publish({ reason: 'initial-title-spread', allowFutureUnrendered: true });
this.reportProgress(100, 'Book pagination ready');
return true;
}
@@ -93,11 +102,13 @@ class BookPaginationModule extends BaseModule {
handlePageCountChanged(event) {
this.pageFormat?.setPageCount?.(event.detail?.pageCount);
this.metrics = this.pageFormat.getTextureMetrics(this.pageFormat.getTextureWidth?.());
this.clearPreparedBlocks();
this.refreshFromHistory();
}
async refreshFromHistory(event = null) {
const token = ++this.refreshToken;
this.clearPreparedBlocks();
const detail = event?.detail || {};
const gameId = detail.gameId || this.storyHistory?.currentGameId || null;
const latestRenderedBlockId = Math.max(
@@ -117,7 +128,7 @@ class BookPaginationModule extends BaseModule {
this.latestRenderedBlockId = 0;
this.currentSpreadIndex = 0;
this.appliedPageReserveBlocks.clear();
this.publish();
this.publish({ reason: 'empty-history' });
return;
}
@@ -135,7 +146,7 @@ class BookPaginationModule extends BaseModule {
: renderedSpreadIndex >= 0
? renderedSpreadIndex
: Math.max(0, Math.min(this.currentSpreadIndex, Math.max(0, this.spreads.length - 1)));
this.publish();
this.publish({ reason: 'history-refresh', allowFutureUnrendered: true });
}
getContinuationBlockId(latestBlockId = 0, latestRenderedBlockId = 0) {
@@ -158,6 +169,31 @@ class BookPaginationModule extends BaseModule {
const historyEndBlockId = options.includeUnrenderedHistory
? Math.max(0, pendingBlockId - 1)
: latestRenderedBlockId;
const cacheKey = this.getPreparedBlockCacheKey(gameId, pendingBlockId, historyEndBlockId, latestRenderedBlockId, options);
const cached = options.activate !== false ? this.takePreparedBlock(cacheKey) : null;
if (cached) {
this.latestBlockId = pendingBlockId;
this.latestRenderedBlockId = latestRenderedBlockId;
this.pages = cached.pages;
this.spreads = cached.spreads;
this.currentSpreadIndex = cached.targetSpread
? cached.targetSpread.index
: Math.max(0, Math.min(this.currentSpreadIndex, Math.max(0, this.spreads.length - 1)));
if (options.publish !== false) this.publish({ reason: 'prepared-cache-activate' });
document.dispatchEvent(new CustomEvent('book-pagination:block-prepared', {
detail: {
blockId: pendingBlockId,
spread: cached.targetSpread || this.getCurrentSpread(),
spreadIndex: cached.targetSpread?.index ?? this.currentSpreadIndex,
latestBlockId: pendingBlockId,
latestRenderedBlockId,
preloadOnly: false,
reusedPreparedPagination: true
}
}));
return cached.targetSpread || this.getCurrentSpread();
}
const historyBlocks = historyEndBlockId > 0
? await this.storyHistory.getBlocksRange(gameId, 1, historyEndBlockId)
: [];
@@ -181,6 +217,13 @@ class BookPaginationModule extends BaseModule {
const lines = Array.isArray(spread?.[side]) ? spread[side] : [];
return lines.some(line => Number(line?.blockId || 0) === pendingBlockId);
}));
if (options.activate === false) {
this.rememberPreparedBlock(cacheKey, {
pages: preparedPages,
spreads: preparedSpreads,
targetSpread: targetSpread || null
});
}
if (options.activate !== false) {
this.latestBlockId = pendingBlockId;
this.latestRenderedBlockId = latestRenderedBlockId;
@@ -189,7 +232,7 @@ class BookPaginationModule extends BaseModule {
this.currentSpreadIndex = Math.max(0, Math.min(this.spreads.length - 1, this.currentSpreadIndex));
if (targetSpread) this.currentSpreadIndex = targetSpread.index;
}
if (options.publish !== false) this.publish();
if (options.publish !== false) this.publish({ reason: options.activate === false ? 'prepare-preload' : 'prepare-activate' });
document.dispatchEvent(new CustomEvent('book-pagination:block-prepared', {
detail: {
blockId: pendingBlockId,
@@ -203,6 +246,39 @@ class BookPaginationModule extends BaseModule {
return targetSpread || (options.activate === false ? preparedSpreads[0] : this.getCurrentSpread());
}
getPreparedBlockCacheKey(gameId, blockId, historyEndBlockId, latestRenderedBlockId, options = {}) {
const includeUnrendered = options.includeUnrenderedHistory === true ? 'unrendered' : 'rendered';
return [
gameId || 'game',
Math.max(0, Number(blockId || 0)),
Math.max(0, Number(historyEndBlockId || 0)),
Math.max(0, Number(latestRenderedBlockId || 0)),
includeUnrendered,
this.metrics?.width || 0,
this.metrics?.height || 0
].join(':');
}
rememberPreparedBlock(key, prepared) {
if (!key || !prepared?.pages || !prepared?.spreads) return;
this.preparedBlockCache.set(key, prepared);
while (this.preparedBlockCache.size > 12) {
const oldestKey = this.preparedBlockCache.keys().next().value;
this.preparedBlockCache.delete(oldestKey);
}
}
takePreparedBlock(key) {
if (!key || !this.preparedBlockCache.has(key)) return null;
const prepared = this.preparedBlockCache.get(key);
this.preparedBlockCache.delete(key);
return prepared;
}
clearPreparedBlocks() {
this.preparedBlockCache.clear();
}
buildSpreads(blocks = []) {
this.pages = this.buildPages(blocks);
return this.buildSpreadsFromPages(this.pages);
@@ -891,11 +967,11 @@ class BookPaginationModule extends BaseModule {
setCurrentSpread(index = 0) {
this.currentSpreadIndex = Math.max(0, Math.min(Math.round(Number(index || 0)), Math.max(0, this.spreads.length - 1)));
this.publish();
this.publish({ reason: 'set-current-spread', allowFutureUnrendered: true });
return this.currentSpreadIndex;
}
publish() {
publish(options = {}) {
const writtenPageLimit = Math.max(0, (Math.max(0, this.spreads.length - 1) * 2) - 1);
document.dispatchEvent(new CustomEvent('book-pagination:spread-updated', {
detail: {
@@ -904,7 +980,9 @@ class BookPaginationModule extends BaseModule {
spreadCount: this.spreads.length,
writtenPageLimit,
latestBlockId: this.latestBlockId,
latestRenderedBlockId: this.latestRenderedBlockId
latestRenderedBlockId: this.latestRenderedBlockId,
reason: options.reason || 'publish',
allowFutureUnrendered: options.allowFutureUnrendered === true
}
}));
}