mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-27 19:52:49 +00:00
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:
@@ -0,0 +1,92 @@
|
||||
import { LRUCache } from "lru-cache";
|
||||
import type { CheckoutDiffCompare, CheckoutDiffResult } from "../utils/checkout-git.js";
|
||||
|
||||
interface ActiveRead {
|
||||
promise: Promise<CheckoutDiffResult>;
|
||||
invalidated: boolean;
|
||||
}
|
||||
|
||||
interface CachedDiff {
|
||||
value: CheckoutDiffResult;
|
||||
loadedAt: number;
|
||||
lastReadStarted: number;
|
||||
}
|
||||
|
||||
interface CheckoutDiffReadInput {
|
||||
cwd: string;
|
||||
compare: CheckoutDiffCompare;
|
||||
force?: boolean;
|
||||
reason?: string;
|
||||
load: () => Promise<CheckoutDiffResult>;
|
||||
}
|
||||
|
||||
/** Completed payloads are bounded; active work survives invalidation and eviction. */
|
||||
export class CheckoutDiffCache {
|
||||
private readonly values = new LRUCache<string, CachedDiff>({
|
||||
max: 64,
|
||||
});
|
||||
private readonly active = new Map<string, ActiveRead>();
|
||||
|
||||
constructor(private readonly now: () => number) {}
|
||||
|
||||
read(input: CheckoutDiffReadInput): Promise<CheckoutDiffResult> {
|
||||
const { cwd, compare, force, reason, load } = input;
|
||||
if (force && !reason) throw new Error("WorkspaceGitService forced read requires a reason");
|
||||
const key = JSON.stringify([
|
||||
cwd,
|
||||
compare.mode,
|
||||
compare.mode === "base" ? (compare.baseRef ?? null) : null,
|
||||
compare.ignoreWhitespace === true,
|
||||
compare.includeStructured === true,
|
||||
]);
|
||||
const cached = this.values.get(key);
|
||||
const now = this.now();
|
||||
if (
|
||||
!force &&
|
||||
cached &&
|
||||
(now - cached.loadedAt <= 15_000 || now - cached.lastReadStarted < 2_000)
|
||||
)
|
||||
return Promise.resolve(cached.value);
|
||||
const pending = this.active.get(key);
|
||||
if (pending) {
|
||||
if (force) {
|
||||
pending.invalidated = true;
|
||||
this.values.delete(key);
|
||||
}
|
||||
if (!pending.invalidated) return pending.promise;
|
||||
// A caller arriving after a change needs a fresh read, once the old one
|
||||
// finishes. The original caller can still receive its snapshot promptly.
|
||||
// The older result cannot enter the cache. Drop force when retrying so
|
||||
// callers waiting on that same result share the next fresh build.
|
||||
const reload = () => this.read({ ...input, force: false });
|
||||
return pending.promise.then(reload, reload);
|
||||
}
|
||||
if (cached) cached.lastReadStarted = now;
|
||||
|
||||
// Defer load until ownership is installed, including synchronous invalidations.
|
||||
const read: ActiveRead = {
|
||||
invalidated: false,
|
||||
promise: Promise.resolve()
|
||||
.then(load)
|
||||
.then((value) => {
|
||||
// Never repopulate the cache with a snapshot invalidated during its build.
|
||||
if (!read.invalidated)
|
||||
this.values.set(key, { value, loadedAt: this.now(), lastReadStarted: now });
|
||||
return value;
|
||||
})
|
||||
.finally(() => this.active.delete(key)),
|
||||
};
|
||||
this.active.set(key, read);
|
||||
return read.promise;
|
||||
}
|
||||
|
||||
invalidate(cwd: string, mode: CheckoutDiffCompare["mode"]): void {
|
||||
for (const key of new Set([...this.values.keys(), ...this.active.keys()])) {
|
||||
const [cachedCwd, cachedMode] = JSON.parse(key) as string[];
|
||||
if (cachedCwd !== cwd || cachedMode !== mode) continue;
|
||||
this.values.delete(key);
|
||||
const pending = this.active.get(key);
|
||||
if (pending) pending.invalidated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user