perf(tui): carry measured widths through rendering
Carry exact visible widths from Text and Box render results into their owners, and through Editor wrap/layout/render state. Stamp derived widths and render caches with a monotonic Hangul width-config epoch. Retain sidecar entries only by WeakMap owner identity, with immutable publication-time line/width proof, and conservatively remeasure escape-leading padded rows. Before: static_redraw 1633.11 us/op editor_edits 1249.71 us/op After hardened repair: static_redraw 1321.51 us/op (1.236x) editor_edits 1150.32 us/op (1.086x) Win: Exact byte/grid/scrollback hashes match across all five phases; every phase CV is below 20%, with no B/C regression. Memory: retained heap 251,393,541 -> 251,500,114 bytes (+0.042%). Op: carry exact widths through render ownership and editor layout Restores: repeated visible-width measurement in core TUI hot paths
This commit is contained in:
@@ -30,14 +30,62 @@ export function getHangulCompatibilityJamoWidth(): HangulCompatibilityJamoWidth
|
||||
return hangulCompatibilityJamoWidth;
|
||||
}
|
||||
|
||||
// Monotonic epoch for width-affecting runtime configuration. Any cache or
|
||||
// carried-width sidecar derived from `visibleWidth` results must be stamped
|
||||
// with the epoch at computation time and discarded on mismatch, so a Hangul
|
||||
// Compatibility Jamo width change invalidates every derived width.
|
||||
let widthConfigEpoch = 0;
|
||||
|
||||
export function getWidthConfigEpoch(): number {
|
||||
return widthConfigEpoch;
|
||||
}
|
||||
|
||||
interface LineWidthsEntry {
|
||||
epoch: number;
|
||||
lines: readonly string[];
|
||||
widths: readonly number[];
|
||||
}
|
||||
|
||||
// Per-render-result visible widths, keyed by the exact lines array a component
|
||||
// returned. The copied strings and widths are the single publication snapshot:
|
||||
// they cannot be changed through either publisher array and do not retain the
|
||||
// WeakMap key. Entries therefore die with their lines-array owners.
|
||||
const lineWidthSidecar = new WeakMap<readonly string[], LineWidthsEntry>();
|
||||
|
||||
/** Publish exact per-line visible widths for a rendered lines array. */
|
||||
export function publishLineWidths(lines: readonly string[], widths: readonly number[]): void {
|
||||
if (lines.length !== widths.length) {
|
||||
throw new RangeError(`Cannot publish ${widths.length} widths for ${lines.length} lines`);
|
||||
}
|
||||
lineWidthSidecar.set(lines, {
|
||||
epoch: widthConfigEpoch,
|
||||
lines: [...lines],
|
||||
widths: Object.freeze([...widths]),
|
||||
});
|
||||
}
|
||||
|
||||
/** Exact per-line visible widths for an unchanged `lines` array under the current width config. */
|
||||
export function getPublishedLineWidths(lines: readonly string[]): readonly number[] | undefined {
|
||||
const entry = lineWidthSidecar.get(lines);
|
||||
if (entry === undefined || entry.epoch !== widthConfigEpoch || entry.lines.length !== lines.length) {
|
||||
return undefined;
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (entry.lines[i] !== lines[i]) return undefined;
|
||||
}
|
||||
return entry.widths;
|
||||
}
|
||||
|
||||
export function setHangulCompatibilityJamoWidth(width: HangulCompatibilityJamoWidth): boolean {
|
||||
const changed = hangulCompatibilityJamoWidth !== width;
|
||||
hangulCompatibilityJamoWidth = width;
|
||||
if (changed) widthConfigEpoch++;
|
||||
nativeSetHangulCompatJamoWidthOverride(nativeHangulCompatibilityJamoOverride(width));
|
||||
return changed;
|
||||
}
|
||||
|
||||
export function resetHangulCompatibilityJamoWidthForTests(): void {
|
||||
if (hangulCompatibilityJamoWidth !== "platform") widthConfigEpoch++;
|
||||
hangulCompatibilityJamoWidth = "platform";
|
||||
nativeSetHangulCompatJamoWidthOverride(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user