perf(renderer): cache activity render signatures
This commit is contained in:
parent
d433df78af
commit
290f01e559
2 changed files with 86 additions and 21 deletions
|
|
@ -6,6 +6,10 @@ const MAX_ACTIVITY_RENDER_CACHE_ENTRIES = 500;
|
||||||
|
|
||||||
type StringCache = Map<string, string>;
|
type StringCache = Map<string, string>;
|
||||||
|
|
||||||
|
const taskRefsSignatureCache = new WeakMap<readonly TaskRef[], string>();
|
||||||
|
const stringArraySignatureCache = new WeakMap<readonly string[], string>();
|
||||||
|
const stringMapSignatureCache = new WeakMap<ReadonlyMap<string, string>, string>();
|
||||||
|
|
||||||
export function getCachedString(cache: StringCache, key: string, buildValue: () => string): string {
|
export function getCachedString(cache: StringCache, key: string, buildValue: () => string): string {
|
||||||
const cached = cache.get(key);
|
const cached = cache.get(key);
|
||||||
if (cached !== undefined || cache.has(key)) return cached ?? '';
|
if (cached !== undefined || cache.has(key)) return cached ?? '';
|
||||||
|
|
@ -20,42 +24,60 @@ export function getCachedString(cache: StringCache, key: string, buildValue: ()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeCacheParts(parts: readonly string[]): string {
|
export function encodeCacheParts(parts: readonly string[]): string {
|
||||||
const encodedParts: string[] = [];
|
let encoded = '';
|
||||||
for (const part of parts) {
|
for (let index = 0; index < parts.length; index += 1) {
|
||||||
pushEncodedCachePart(encodedParts, part);
|
if (index > 0) encoded += '|';
|
||||||
|
const part = parts[index];
|
||||||
|
encoded += `${part.length}:${part}`;
|
||||||
}
|
}
|
||||||
return encodedParts.join('|');
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function taskRefsCacheSignature(taskRefs?: readonly TaskRef[]): string {
|
export function taskRefsCacheSignature(taskRefs?: readonly TaskRef[]): string {
|
||||||
if (!taskRefs || taskRefs.length === 0) return '';
|
if (!taskRefs || taskRefs.length === 0) return '';
|
||||||
const encodedParts: string[] = [];
|
const cached = taskRefsSignatureCache.get(taskRefs);
|
||||||
for (const ref of taskRefs) {
|
if (cached !== undefined) return cached;
|
||||||
pushEncodedCachePart(encodedParts, ref.taskId);
|
|
||||||
pushEncodedCachePart(encodedParts, ref.displayId);
|
let encoded = '';
|
||||||
pushEncodedCachePart(encodedParts, ref.teamName ?? '');
|
let hasPart = false;
|
||||||
|
for (let index = 0; index < taskRefs.length; index += 1) {
|
||||||
|
const ref = taskRefs[index];
|
||||||
|
const parts = [ref.taskId, ref.displayId, ref.teamName ?? ''];
|
||||||
|
for (const part of parts) {
|
||||||
|
if (hasPart) encoded += '|';
|
||||||
|
encoded += `${part.length}:${part}`;
|
||||||
|
hasPart = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return encodedParts.join('|');
|
taskRefsSignatureCache.set(taskRefs, encoded);
|
||||||
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringArrayCacheSignature(values?: readonly string[]): string {
|
export function stringArrayCacheSignature(values?: readonly string[]): string {
|
||||||
if (!values || values.length === 0) return '';
|
if (!values || values.length === 0) return '';
|
||||||
return encodeCacheParts(values);
|
const cached = stringArraySignatureCache.get(values);
|
||||||
|
if (cached !== undefined) return cached;
|
||||||
|
const signature = encodeCacheParts(values);
|
||||||
|
stringArraySignatureCache.set(values, signature);
|
||||||
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stringMapCacheSignature(map?: ReadonlyMap<string, string>): string {
|
export function stringMapCacheSignature(map?: ReadonlyMap<string, string>): string {
|
||||||
if (!map || map.size === 0) return '';
|
if (!map || map.size === 0) return '';
|
||||||
const entries = [...map.entries()].sort(([a], [b]) => a.localeCompare(b));
|
const cached = stringMapSignatureCache.get(map);
|
||||||
const encodedParts: string[] = [];
|
if (cached !== undefined) return cached;
|
||||||
for (const [key, value] of entries) {
|
|
||||||
pushEncodedCachePart(encodedParts, key);
|
|
||||||
pushEncodedCachePart(encodedParts, value);
|
|
||||||
}
|
|
||||||
return encodedParts.join('|');
|
|
||||||
}
|
|
||||||
|
|
||||||
function pushEncodedCachePart(encodedParts: string[], part: string): void {
|
const entries = [...map.entries()].sort(([a], [b]) => a.localeCompare(b));
|
||||||
encodedParts.push(`${part.length}:${part}`);
|
let encoded = '';
|
||||||
|
let hasPart = false;
|
||||||
|
for (const [key, value] of entries) {
|
||||||
|
if (hasPart) encoded += '|';
|
||||||
|
encoded += `${key.length}:${key}`;
|
||||||
|
hasPart = true;
|
||||||
|
encoded += `|${value.length}:${value}`;
|
||||||
|
}
|
||||||
|
stringMapSignatureCache.set(map, encoded);
|
||||||
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
const markdownPlainTextCache: StringCache = new Map();
|
const markdownPlainTextCache: StringCache = new Map();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
encodeCacheParts,
|
||||||
|
stringArrayCacheSignature,
|
||||||
|
stringMapCacheSignature,
|
||||||
|
taskRefsCacheSignature,
|
||||||
|
} from '../../../../../src/renderer/components/team/activity/activityRenderCache';
|
||||||
|
|
||||||
|
import type { TaskRef } from '../../../../../src/shared/types';
|
||||||
|
|
||||||
|
describe('activityRenderCache', () => {
|
||||||
|
it('encodes cache parts with length prefixes', () => {
|
||||||
|
expect(encodeCacheParts(['a', 'bb', ''])).toBe('1:a|2:bb|0:');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds stable task reference signatures', () => {
|
||||||
|
const refs: TaskRef[] = [
|
||||||
|
{ taskId: 'task-1', displayId: '#1', teamName: 'team-a' },
|
||||||
|
{ taskId: 'task-2', displayId: '#2' },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(taskRefsCacheSignature(refs)).toBe('6:task-1|2:#1|6:team-a|6:task-2|2:#2|0:');
|
||||||
|
expect(taskRefsCacheSignature(refs)).toBe(taskRefsCacheSignature(refs));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('builds stable string array signatures', () => {
|
||||||
|
const values = ['alice', 'bob'];
|
||||||
|
|
||||||
|
expect(stringArrayCacheSignature(values)).toBe('5:alice|3:bob');
|
||||||
|
expect(stringArrayCacheSignature(values)).toBe(stringArrayCacheSignature(values));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sorts string map signatures by key', () => {
|
||||||
|
const map = new Map([
|
||||||
|
['bob', 'blue'],
|
||||||
|
['alice', 'red'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(stringMapCacheSignature(map)).toBe('5:alice|3:red|3:bob|4:blue');
|
||||||
|
expect(stringMapCacheSignature(map)).toBe(stringMapCacheSignature(map));
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue