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>;
|
||||
|
||||
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 {
|
||||
const cached = cache.get(key);
|
||||
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 {
|
||||
const encodedParts: string[] = [];
|
||||
for (const part of parts) {
|
||||
pushEncodedCachePart(encodedParts, part);
|
||||
let encoded = '';
|
||||
for (let index = 0; index < parts.length; index += 1) {
|
||||
if (index > 0) encoded += '|';
|
||||
const part = parts[index];
|
||||
encoded += `${part.length}:${part}`;
|
||||
}
|
||||
return encodedParts.join('|');
|
||||
return encoded;
|
||||
}
|
||||
|
||||
export function taskRefsCacheSignature(taskRefs?: readonly TaskRef[]): string {
|
||||
if (!taskRefs || taskRefs.length === 0) return '';
|
||||
const encodedParts: string[] = [];
|
||||
for (const ref of taskRefs) {
|
||||
pushEncodedCachePart(encodedParts, ref.taskId);
|
||||
pushEncodedCachePart(encodedParts, ref.displayId);
|
||||
pushEncodedCachePart(encodedParts, ref.teamName ?? '');
|
||||
const cached = taskRefsSignatureCache.get(taskRefs);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
let encoded = '';
|
||||
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 {
|
||||
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 {
|
||||
if (!map || map.size === 0) return '';
|
||||
const entries = [...map.entries()].sort(([a], [b]) => a.localeCompare(b));
|
||||
const encodedParts: string[] = [];
|
||||
for (const [key, value] of entries) {
|
||||
pushEncodedCachePart(encodedParts, key);
|
||||
pushEncodedCachePart(encodedParts, value);
|
||||
}
|
||||
return encodedParts.join('|');
|
||||
}
|
||||
const cached = stringMapSignatureCache.get(map);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
function pushEncodedCachePart(encodedParts: string[], part: string): void {
|
||||
encodedParts.push(`${part.length}:${part}`);
|
||||
const entries = [...map.entries()].sort(([a], [b]) => a.localeCompare(b));
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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