From 976ca409630b26739be54e3212bdf3ffbf1b6619 Mon Sep 17 00:00:00 2001 From: xRangerDE Date: Mon, 11 May 2026 04:08:42 +0200 Subject: [PATCH] perf: bound the renderer-side VOD storyboard cache (FIFO 100) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vodStoryboardClientCache was a plain Map with no eviction. Every VOD ever hovered cached its first sprite data URL — about 50-200 KB each. Browsing a long-running streamer's 2000-VOD archive could leave the renderer holding 100-400 MB of hover-only sprite data permanently, with no signal to the user that it was happening. Wrapped writes in a rememberStoryboard helper that caps the cache at 100 entries with FIFO eviction (Map iterator is insertion-ordered so .keys().next().value is always the oldest). Cache hit / miss semantics unchanged for the live set — only the dropped-off-the- back entries get re-fetched if the user scrolls back to a VOD they hovered hundreds of cards ago, and that re-fetch is fast because the main-process side has its own metadata cache that survives the renderer-side eviction. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/renderer-vod-hover.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/renderer-vod-hover.ts b/src/renderer-vod-hover.ts index a57793b..f754d6e 100644 --- a/src/renderer-vod-hover.ts +++ b/src/renderer-vod-hover.ts @@ -21,6 +21,20 @@ let pendingHoverVodId: string | null = null; const HOVER_DEBOUNCE_MS = 220; const FRAME_INTERVAL_MS = 600; const FRAMES_TO_CYCLE = 4; +// Bounded cache — each storyboard data URL is ~50-200 KB, so an +// unbounded cache could balloon to hundreds of MB on a long browsing +// session through a streamer with thousands of VODs. FIFO eviction +// keeps the working set fresh without manual cleanup. +const MAX_CLIENT_STORYBOARD_CACHE = 100; + +function rememberStoryboard(vodId: string, sb: VodStoryboard | null): void { + vodStoryboardClientCache.set(vodId, sb); + if (vodStoryboardClientCache.size > MAX_CLIENT_STORYBOARD_CACHE) { + // Map iterator is insertion-ordered — first key is the oldest. + const oldestKey = vodStoryboardClientCache.keys().next().value as string | undefined; + if (oldestKey !== undefined) vodStoryboardClientCache.delete(oldestKey); + } +} function ensureVodHoverHandlersBound(): void { const grid = document.getElementById('vodGrid'); @@ -85,7 +99,7 @@ async function activateHoverPreview(card: HTMLElement, vodId: string): Promise