mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
perf(workbench): LRU-evict PageCarousel's mounted-cards cache
Previously the intersection-observer cache grew monotonically: once a card mounted its ListView + Dexie liveQuery, it stayed mounted for the lifetime of the workbench page. A user who scrolled through 20 apps kept 20 parallel liveQueries alive. Now the cache is capped at MAX_MOUNTED=8 with insertion-order LRU semantics: re-intersecting a mounted card bumps it to MRU, and the oldest gets evicted when a new mount pushes the set over cap. Set insertion-order is used for the LRU list so the template's has() check stays O(1). The cap is well above typical working-set sizes (3–6 apps) so regular workbench use never hits the eviction path. Users with large scenes pay at most one extra liveQuery + chunk re-request when scrolling back to an evicted card. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2c0d866287
commit
4d82381737
1 changed files with 18 additions and 7 deletions
|
|
@ -56,17 +56,28 @@
|
|||
// Strategy: render a fixed-size placeholder until the wrapper enters
|
||||
// the viewport (50% horizontal overshoot so the next card is ready
|
||||
// before the user scrolls to it), then swap in the real snippet.
|
||||
// Sticky-cache: once a card has been mounted we never tear it down
|
||||
// — re-running a liveQuery + re-fetching a chunk costs more than
|
||||
// keeping the DOM tree resident. Memory still scales with how many
|
||||
// apps the user has actively scrolled through, not how many they
|
||||
// have open.
|
||||
//
|
||||
// LRU cache: keep the MAX_MOUNTED most-recently-intersected cards
|
||||
// mounted and let older ones drop back to a placeholder. Re-mounting
|
||||
// a re-intersected card costs one more liveQuery + chunk request,
|
||||
// but memory now stays bounded regardless of how many apps the user
|
||||
// scrolls through. The cap is set high enough to cover typical
|
||||
// working sets (3–6 apps) with headroom.
|
||||
const MAX_MOUNTED = 8;
|
||||
let mountedIds = $state<Set<string>>(new Set());
|
||||
function markMounted(id: string) {
|
||||
if (mountedIds.has(id)) return;
|
||||
// Replace the Set so $state notices the change.
|
||||
// Set preserves insertion order → MRU sits at the back. Already-MRU
|
||||
// is a no-op so we don't churn reactivity on every intersect tick.
|
||||
if (Array.from(mountedIds).at(-1) === id) return;
|
||||
const next = new Set(mountedIds);
|
||||
// delete+add re-seats the id at the back of insertion order.
|
||||
next.delete(id);
|
||||
next.add(id);
|
||||
while (next.size > MAX_MOUNTED) {
|
||||
const oldest = next.values().next().value;
|
||||
if (!oldest) break;
|
||||
next.delete(oldest);
|
||||
}
|
||||
mountedIds = next;
|
||||
}
|
||||
// Mount the first card eagerly so initial paint always shows
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue