Speed up cold diffs and wrapped text measurement (#4676)

* perf(app): optimize wrapped measurements and checkpoint native layout experiment

Compute full grapheme geometry only for final wrap fragments. Preserve shaped-text search behavior while bounding additive wrap probes. Keep the Android background layout experiment isolated from the normal diff renderer; native renderer integration and iOS bindings remain unfinished.

* perf(server): reduce cold diff generation work

Keep active diff reads owned across invalidation and wait for the initial watcher inventory before cold generation. Batch patch and blob reads while preserving per-file size isolation, bounded output, and daemon responsiveness. Avoid constructing fallback tokens when full-file highlighting already covers both sides.

* test(server): exercise oversized highlighted diff rejection

The single-line fixture now bypasses highlighting under the long-line guard, so it no longer reaches the structured payload limit. Use short dense lines to keep testing oversized rejection and continued socket usability.

* fix(server): queue fresh diffs for forced reads

A forced request following a repository mutation must not reuse an older active build. Invalidate that result and coalesce waiting callers onto one subsequent build. Give the cache a named object input and completed-value type.

* test(server): share deferred diff loader setup

* test(server): name completed diff cache setup

* refactor(server): use aliases for new diff imports

* fix(server): keep shipped diff imports resolvable by Node

The TypeScript build preserves path aliases without a runtime resolver. Restore relative imports for shipped code and declarations, and correct the server import guideline that prompted the regression.
This commit is contained in:
Mohamed Boudra
2026-09-11 08:07:01 +02:00
committed by GitHub
parent 16d932651c
commit 33a52d0ae7
32 changed files with 1409 additions and 186 deletions
@@ -10,6 +10,7 @@ import { codeTextColor } from "./palette";
import {
createCachedAsciiTextMetrics,
createChunkedAdvanceMeasurer,
createChunkedWidthMeasurer,
requiresShaping,
type CachedAsciiTextMetrics,
} from "./text-measurement";
@@ -202,43 +203,40 @@ export function createNativeTextMeasurer(input: {
};
const asciiMetrics = createCachedAsciiTextMetrics(primary);
const black = Skia.Color("black");
const measureParagraph = (text: string) => {
const paragraph = createParagraph({
const shapeParagraph = (text: string) =>
createParagraph({
text,
families,
fontSize: input.fontSize,
lineHeight: Math.round(input.fontSize * 1.5),
color: black,
});
const width = paragraph.getLongestLine();
paragraph.dispose();
return width;
};
return {
measure(text) {
return requiresRetainedParagraph(text, asciiMetrics)
? measureParagraph(text)
: asciiMetrics.measure(text);
if (!requiresRetainedParagraph(text, asciiMetrics)) return asciiMetrics.measure(text);
const paragraph = shapeParagraph(text);
const width = paragraph.getLongestLine();
paragraph.dispose();
return width;
},
measureWidth: createChunkedWidthMeasurer((graphemes) => {
const text = graphemes.join("");
if (!requiresRetainedParagraph(text, asciiMetrics))
return asciiMetrics.measureWidth(graphemes);
const paragraph = shapeParagraph(text);
const width = paragraphAdvance(paragraph, text.length);
paragraph.dispose();
return width;
}),
measureAdvances: createChunkedAdvanceMeasurer({
requiresShaping: (text) => requiresRetainedParagraph(text, asciiMetrics),
measureAdditive: (graphemes) => asciiMetrics.measureAdvances(graphemes),
measureShaped(graphemes) {
const paragraph = createParagraph({
text: graphemes.join(""),
families,
fontSize: input.fontSize,
lineHeight: Math.round(input.fontSize * 1.5),
color: black,
});
const paragraph = shapeParagraph(graphemes.join(""));
let end = 0;
const advances = graphemes.map((grapheme) => {
end += grapheme.length;
const rectangles = paragraph.getRectsForRange(0, end);
return rectangles.reduce(
(right, rectangle) => Math.max(right, rectangle.x + rectangle.width),
0,
);
return paragraphAdvance(paragraph, end);
});
paragraph.dispose();
return advances;
@@ -247,6 +245,12 @@ export function createNativeTextMeasurer(input: {
};
}
function paragraphAdvance(paragraph: SkParagraph, end: number): number {
return paragraph
.getRectsForRange(0, end)
.reduce((right, rectangle) => Math.max(right, rectangle.x + rectangle.width), 0);
}
function requiresRetainedParagraph(text: string, asciiMetrics: CachedAsciiTextMetrics): boolean {
return requiresShaping(text) || !asciiMetrics.hasEveryGlyph(text);
}