Checkpoint Eibenreith ink architecture
This commit is contained in:
@@ -38,6 +38,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
this.scrollTargetLine = null;
|
||||
this.scrollRequestId = 0;
|
||||
this.renderWindowToken = 0;
|
||||
this.displayGeneration = 0;
|
||||
this.wheelLineAccumulator = 0;
|
||||
this.viewportLineCount = 1;
|
||||
this.lineHeightPx = 24;
|
||||
@@ -124,6 +125,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
'rerenderStory',
|
||||
'clear',
|
||||
'scheduleRerender',
|
||||
'isDisplayGenerationCurrent',
|
||||
'measureText',
|
||||
'loadCSS',
|
||||
'showChoices',
|
||||
@@ -955,15 +957,35 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
return null;
|
||||
}
|
||||
|
||||
const generation = this.displayGeneration;
|
||||
const sentenceGameId = sentence.gameId || null;
|
||||
const isCurrent = () => this.isDisplayGenerationCurrent(generation, sentenceGameId);
|
||||
|
||||
try {
|
||||
await this.ensureLiveTailWindow();
|
||||
if (!isCurrent()) return null;
|
||||
await this.scrollTo(this.getLiveEndLine(), { mode: 'enter-live-tail', smooth: false });
|
||||
if (!isCurrent()) return null;
|
||||
this.rebuildLayoutExclusions(this.renderedItems);
|
||||
this.layoutFlowLine = this.getFlowLineFromItems(this.renderedItems);
|
||||
const element = await this.renderStoryBlock(sentence, { animate: true, playback: true, placement: 'append' });
|
||||
const element = await this.renderStoryBlock(sentence, {
|
||||
animate: true,
|
||||
playback: true,
|
||||
placement: 'append',
|
||||
token: this.renderWindowToken,
|
||||
generation
|
||||
});
|
||||
if (!element) return null;
|
||||
if (!isCurrent()) {
|
||||
element.remove();
|
||||
return null;
|
||||
}
|
||||
sentence.element = element;
|
||||
await this.scrollTo(this.getLiveEndLine(), { mode: 'append-live' });
|
||||
if (!isCurrent()) {
|
||||
element.remove();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (sentence.kind === 'image') {
|
||||
this.revealImageBlock(element);
|
||||
@@ -972,6 +994,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
} else if (sentence.kind === 'music') {
|
||||
console.log('UIDisplayHandler: Music block started', sentence.metadata || {});
|
||||
}
|
||||
if (!isCurrent()) return null;
|
||||
|
||||
this.dispatchDeferredTagsForBlock(sentence);
|
||||
|
||||
@@ -990,34 +1013,51 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
async rerenderStory() {
|
||||
if (!this.paragraphContainer || this.renderedItems.length === 0) return;
|
||||
console.log('UIDisplayHandler: Re-typesetting story after page resize');
|
||||
const generation = this.displayGeneration;
|
||||
const activeLine = this.getCurrentScrollLine();
|
||||
await this.renderHistoryWindow([...this.renderedItems]);
|
||||
await this.renderHistoryWindow([...this.renderedItems], { generation });
|
||||
if (!this.isDisplayGenerationCurrent(generation)) return;
|
||||
await this.scrollTo(activeLine, { mode: 'rerender-preserve', smooth: false });
|
||||
}
|
||||
|
||||
isDisplayGenerationCurrent(generation = this.displayGeneration, gameId = null) {
|
||||
if (generation !== this.displayGeneration) return false;
|
||||
if (gameId && this.storyHistory?.currentGameId && this.storyHistory.currentGameId !== gameId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async restoreFromHistory(saveRecord = {}) {
|
||||
if (!this.paragraphContainer || !this.storyHistory || !saveRecord?.gameId) return;
|
||||
const generation = this.displayGeneration;
|
||||
const latestRenderedBlockId = Math.max(0, Number(saveRecord.latestRenderedBlockId || 0));
|
||||
if (!this.storyHistory.renderedLineCount) {
|
||||
await this.storyHistory.getRenderedLineCount(saveRecord.gameId, latestRenderedBlockId);
|
||||
if (!this.isDisplayGenerationCurrent(generation, saveRecord.gameId)) return;
|
||||
}
|
||||
const targetLine = Math.max(0, latestRenderedBlockId > 0 ? this.storyHistory.renderedLineCount - 1 : 0);
|
||||
if (latestRenderedBlockId > 0) {
|
||||
const targetBlock = await this.storyHistory.findBlockForLine(saveRecord.gameId, targetLine, latestRenderedBlockId);
|
||||
if (!this.isDisplayGenerationCurrent(generation, saveRecord.gameId)) return;
|
||||
const targetBlockId = Math.max(1, Number(targetBlock?.blockId || latestRenderedBlockId));
|
||||
await this.renderWindowForBounds({
|
||||
start: Math.max(1, targetBlockId - this.historyBufferBlocks),
|
||||
end: Math.min(latestRenderedBlockId, targetBlockId + this.historyBufferBlocks),
|
||||
targetBlockId,
|
||||
windowOriginLine: this.getTopLineForActiveLine(targetLine)
|
||||
windowOriginLine: this.getTopLineForActiveLine(targetLine),
|
||||
gameId: saveRecord.gameId,
|
||||
generation
|
||||
});
|
||||
} else {
|
||||
await this.renderHistoryWindow([], { windowOriginLine: 0 });
|
||||
await this.renderHistoryWindow([], { windowOriginLine: 0, generation });
|
||||
}
|
||||
if (!this.isDisplayGenerationCurrent(generation, saveRecord.gameId)) return;
|
||||
await this.scrollTo(targetLine, {
|
||||
mode: 'restore-bottom',
|
||||
smooth: false
|
||||
});
|
||||
if (!this.isDisplayGenerationCurrent(generation, saveRecord.gameId)) return;
|
||||
this.updateStoryScrollbar({ latestBlockId: saveRecord.latestBlockId || latestRenderedBlockId || 1 });
|
||||
}
|
||||
|
||||
@@ -1038,10 +1078,12 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
targetContainer = this.paragraphContainer,
|
||||
renderedItemsTarget = this.renderedItems,
|
||||
token = null,
|
||||
recordMetrics = true
|
||||
recordMetrics = true,
|
||||
generation = this.displayGeneration
|
||||
} = options;
|
||||
if (!item || !this.paragraphContainer) return null;
|
||||
const renderable = await this.prepareRenderableBlock(item);
|
||||
if (!this.isDisplayGenerationCurrent(generation, item.gameId || null)) return null;
|
||||
if (token != null && token !== this.renderWindowToken) return null;
|
||||
if (!renderable) return null;
|
||||
|
||||
@@ -1110,6 +1152,10 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
|
||||
if (recordMetrics && item.blockId != null) {
|
||||
const updated = await this.recordRenderedMetrics(item.blockId, element, renderable.lineCount, renderable.lineStart);
|
||||
if (!this.isDisplayGenerationCurrent(generation, item.gameId || null)) {
|
||||
element?.remove();
|
||||
return null;
|
||||
}
|
||||
if (token != null && token !== this.renderWindowToken) {
|
||||
element?.remove();
|
||||
return null;
|
||||
@@ -1130,6 +1176,8 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
|
||||
async renderHistoryWindow(blocks = [], options = {}) {
|
||||
if (!this.paragraphContainer) return;
|
||||
const generation = options.generation ?? this.displayGeneration;
|
||||
if (!this.isDisplayGenerationCurrent(generation, options.gameId || blocks[0]?.gameId || null)) return;
|
||||
const token = ++this.renderWindowToken;
|
||||
const orderedBlocks = Array.isArray(blocks)
|
||||
? [...blocks].sort((left, right) => Number(left?.blockId || 0) - Number(right?.blockId || 0))
|
||||
@@ -1152,12 +1200,15 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
placement: 'append',
|
||||
targetContainer: fragment,
|
||||
renderedItemsTarget: nextRenderedItems,
|
||||
token
|
||||
token,
|
||||
generation
|
||||
});
|
||||
if (token !== this.renderWindowToken) return;
|
||||
if (!this.isDisplayGenerationCurrent(generation, item.gameId || options.gameId || null)) return;
|
||||
}
|
||||
|
||||
if (token !== this.renderWindowToken) return;
|
||||
if (!this.isDisplayGenerationCurrent(generation, options.gameId || orderedBlocks[0]?.gameId || null)) return;
|
||||
this.paragraphContainer.replaceChildren(fragment);
|
||||
this.renderedItems = nextRenderedItems;
|
||||
this.historyWindowStartId = orderedBlocks[0]?.blockId || 1;
|
||||
@@ -1263,9 +1314,11 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
|
||||
async reflowTextBlocksForActiveExclusions(token = this.renderWindowToken) {
|
||||
if (!this.layoutExclusions.length || !this.paragraphContainer) return;
|
||||
const generation = this.displayGeneration;
|
||||
const candidates = this.renderedItems.filter(item => this.blockIntersectsExclusions(item));
|
||||
for (const item of candidates) {
|
||||
if (token !== this.renderWindowToken) return;
|
||||
if (!this.isDisplayGenerationCurrent(generation, item.gameId || null)) return;
|
||||
const oldElement = this.paragraphContainer.querySelector(`[data-story-block-id="${CSS.escape(String(item.blockId))}"]`);
|
||||
if (!oldElement) continue;
|
||||
const previousFlowLine = this.layoutFlowLine;
|
||||
@@ -1274,6 +1327,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
this.layoutFlowLine = previousFlowLine;
|
||||
this.layoutExclusions = previousExclusions;
|
||||
if (token !== this.renderWindowToken) return;
|
||||
if (!this.isDisplayGenerationCurrent(generation, item.gameId || null)) return;
|
||||
renderable.layout.lineCount = Math.max(1, Number(item.lineCount ?? item.metadata?.lineCount ?? renderable.lineCount));
|
||||
renderable.lineCount = renderable.layout.lineCount;
|
||||
const replacement = this.layoutRenderer.renderParagraph(renderable.layout, { id: renderable.id });
|
||||
@@ -1294,6 +1348,9 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
|
||||
async renderIncrementalWindow(bounds = {}, requestId = null) {
|
||||
if (!this.storyHistory || !this.paragraphContainer) return;
|
||||
const generation = bounds.generation ?? this.displayGeneration;
|
||||
const gameId = bounds.gameId || this.storyHistory.currentGameId;
|
||||
if (!this.isDisplayGenerationCurrent(generation, gameId)) return;
|
||||
if (!this.renderedItems.length) {
|
||||
await this.renderWindowForBounds(bounds, requestId);
|
||||
return;
|
||||
@@ -1310,15 +1367,17 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
const missingBeforeEnd = Math.min(end, (this.historyWindowStartId || start) - 1);
|
||||
const missingAfterStart = Math.max(start, (this.historyWindowEndId || 0) + 1);
|
||||
const missingBeforeBlocks = start <= missingBeforeEnd
|
||||
? await this.storyHistory.getBlocksRange(this.storyHistory.currentGameId, start, missingBeforeEnd)
|
||||
? await this.storyHistory.getBlocksRange(gameId, start, missingBeforeEnd)
|
||||
: [];
|
||||
if (requestId != null && requestId !== this.scrollRequestId) return;
|
||||
if (token !== this.renderWindowToken) return;
|
||||
if (!this.isDisplayGenerationCurrent(generation, gameId)) return;
|
||||
const missingAfterBlocks = missingAfterStart <= end
|
||||
? await this.storyHistory.getBlocksRange(this.storyHistory.currentGameId, missingAfterStart, end)
|
||||
? await this.storyHistory.getBlocksRange(gameId, missingAfterStart, end)
|
||||
: [];
|
||||
if (requestId != null && requestId !== this.scrollRequestId) return;
|
||||
if (token !== this.renderWindowToken) return;
|
||||
if (!this.isDisplayGenerationCurrent(generation, gameId)) return;
|
||||
|
||||
const uniqueBeforeBlocks = missingBeforeBlocks.filter(block => {
|
||||
const id = Number(block?.blockId || 0);
|
||||
@@ -1355,6 +1414,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
targetContainer: beforeFragment,
|
||||
renderedItemsTarget: beforeItems,
|
||||
token,
|
||||
generation,
|
||||
recordMetrics: false
|
||||
});
|
||||
if (token !== this.renderWindowToken || (requestId != null && requestId !== this.scrollRequestId)) {
|
||||
@@ -1378,6 +1438,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
targetContainer: afterFragment,
|
||||
renderedItemsTarget: afterItems,
|
||||
token,
|
||||
generation,
|
||||
recordMetrics: false
|
||||
});
|
||||
if (token !== this.renderWindowToken || (requestId != null && requestId !== this.scrollRequestId)) {
|
||||
@@ -1402,6 +1463,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
this.rebuildLayoutExclusions(this.renderedItems);
|
||||
await this.reflowTextBlocksForActiveExclusions(token);
|
||||
if (token !== this.renderWindowToken) return;
|
||||
if (!this.isDisplayGenerationCurrent(generation, gameId)) return;
|
||||
this.setVirtualPadding();
|
||||
this.updateStoryScrollbar();
|
||||
}
|
||||
@@ -2033,6 +2095,8 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
|
||||
async ensureLiveTailWindow() {
|
||||
if (!this.storyHistory || !this.paragraphContainer) return;
|
||||
const generation = this.displayGeneration;
|
||||
const gameId = this.storyHistory.currentGameId;
|
||||
const latestRendered = Math.max(0, Number(this.storyHistory.latestRenderedBlockId || 0));
|
||||
if (latestRendered > 0) {
|
||||
const start = Math.max(1, latestRendered - (this.visibleBlockLimit - 2));
|
||||
@@ -2043,8 +2107,11 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
start,
|
||||
end,
|
||||
targetBlockId: latestRendered,
|
||||
windowOriginLine: this.getTopLineForActiveLine(liveEndLine)
|
||||
windowOriginLine: this.getTopLineForActiveLine(liveEndLine),
|
||||
gameId,
|
||||
generation
|
||||
});
|
||||
if (!this.isDisplayGenerationCurrent(generation, gameId)) return;
|
||||
}
|
||||
} else if (this.renderedItems.length) {
|
||||
this.paragraphContainer.innerHTML = '';
|
||||
@@ -2104,11 +2171,20 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
}
|
||||
|
||||
async renderWindowForBounds(bounds = {}, requestId = null) {
|
||||
if (!this.storyHistory) return;
|
||||
const generation = bounds.generation ?? this.displayGeneration;
|
||||
const gameId = bounds.gameId || this.storyHistory.currentGameId;
|
||||
if (!this.isDisplayGenerationCurrent(generation, gameId)) return;
|
||||
const start = Math.max(1, Number(bounds.start || 1));
|
||||
const end = Math.max(start, Number(bounds.end || start));
|
||||
const blocks = await this.storyHistory.getBlocksRange(this.storyHistory.currentGameId, start, end);
|
||||
const blocks = await this.storyHistory.getBlocksRange(gameId, start, end);
|
||||
if (requestId != null && requestId !== this.scrollRequestId) return;
|
||||
await this.renderHistoryWindow(blocks, { windowOriginLine: bounds.windowOriginLine });
|
||||
if (!this.isDisplayGenerationCurrent(generation, gameId)) return;
|
||||
await this.renderHistoryWindow(blocks, {
|
||||
windowOriginLine: bounds.windowOriginLine,
|
||||
gameId,
|
||||
generation
|
||||
});
|
||||
}
|
||||
|
||||
focusTurn(turnId) {
|
||||
@@ -2335,6 +2411,7 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.displayGeneration += 1;
|
||||
this.renderWindowToken += 1;
|
||||
this.scrollRequestId += 1;
|
||||
if (this.scrollAnimationFrameId != null) {
|
||||
@@ -2370,6 +2447,10 @@ class UIDisplayHandlerModule extends BaseModule {
|
||||
this.historyWindowStartId = 1;
|
||||
this.historyWindowEndId = 0;
|
||||
this.storyTopLine = 0;
|
||||
this.scrollTargetLine = null;
|
||||
this.wheelLineAccumulator = 0;
|
||||
this.draggingStoryScrollbar = false;
|
||||
this.scrollbarPreviewLine = null;
|
||||
this.activeCenterBlockId = null;
|
||||
this.layoutFlowLine = 0;
|
||||
this.layoutExclusions = [];
|
||||
|
||||
Reference in New Issue
Block a user