diff options
| author | Fuwn <[email protected]> | 2026-03-01 14:20:08 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-03-01 15:24:03 -0800 |
| commit | 3b10a1f47fd5838fe3b94c19673a52610b88cf1e (patch) | |
| tree | d468a1fc12290e38686b255194ff6596b58cbf01 /src/lib/List/Anime | |
| parent | perf(match): fast-path exact normalised title matches (diff) | |
| download | due.moe-3b10a1f47fd5838fe3b94c19673a52610b88cf1e.tar.xz due.moe-3b10a1f47fd5838fe3b94c19673a52610b88cf1e.zip | |
perf: optimise list hot paths and shared timers
Diffstat (limited to 'src/lib/List/Anime')
| -rw-r--r-- | src/lib/List/Anime/CleanAnimeList.svelte | 83 | ||||
| -rw-r--r-- | src/lib/List/Anime/DueAnimeList.svelte | 47 |
2 files changed, 92 insertions, 38 deletions
diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte index 561b3b1d..aee035a4 100644 --- a/src/lib/List/Anime/CleanAnimeList.svelte +++ b/src/lib/List/Anime/CleanAnimeList.svelte @@ -37,7 +37,8 @@ export let limit: number | undefined = undefined; let showRoulette = false; - let keyCacher: ReturnType<typeof setInterval>; + let airingRefreshTimeout: ReturnType<typeof setTimeout> | undefined; + let scheduledAiringAt: number | null = null; let totalEpisodeDueCount = media .map((anime) => { if ($settings.displayTotalEpisodes && !$settings.displayTotalDueEpisodes) return 1; @@ -81,35 +82,61 @@ ? media : media.filter((m) => m.mediaListEntry?.customLists?.[selectedList]); - onMount(async () => { - if (dummy) return; + const clearAiringRefreshTimeout = () => { + if (airingRefreshTimeout) clearTimeout(airingRefreshTimeout); + + airingRefreshTimeout = undefined; + scheduledAiringAt = null; + }; + + const scheduleAiringRefresh = () => { + if (!browser) return; + + if (dummy || media.length === 0) { + clearAiringRefreshTimeout(); + + return; + } + + const nextAiringAt = media.reduce<number | null>((closest, currentMedia) => { + if (currentMedia.status !== 'RELEASING' && currentMedia.status !== 'NOT_YET_RELEASED') + return closest; + + const airingAt = currentMedia.nextAiringEpisode?.airingAt; + + if (!airingAt) return closest; + if (closest === null) return airingAt; + + return airingAt < closest ? airingAt : closest; + }, null); - keyCacher = setInterval( + if (!nextAiringAt) { + clearAiringRefreshTimeout(); + + return; + } + + if (airingRefreshTimeout && scheduledAiringAt === nextAiringAt) return; + + clearAiringRefreshTimeout(); + scheduledAiringAt = nextAiringAt; + airingRefreshTimeout = setTimeout( () => { - media = media; + const now = Date.now() / 1000; - if ( - media.some( - (m) => m.nextAiringEpisode?.airingAt && m.nextAiringEpisode.airingAt < Date.now() / 1000 - ) - ) + if (media.some((m) => m.nextAiringEpisode?.airingAt && m.nextAiringEpisode.airingAt < now)) animeLists = cleanCache(user, $identity); + + scheduleAiringRefresh(); }, - (() => { - const airingAt = media - .filter( - (m) => - (m.status === 'RELEASING' || m.status === 'NOT_YET_RELEASED') && - m.nextAiringEpisode?.airingAt - ) - .find((m) => m.nextAiringEpisode?.airingAt)?.nextAiringEpisode?.airingAt; - const untilAiring = airingAt - ? Math.round((airingAt - Date.now() / 1000) * 100) / 100 - : undefined; - - return untilAiring ? (untilAiring < 0 ? 1000 : untilAiring) : 1000; - })() + Math.max(1000, nextAiringAt * 1000 - Date.now() + 250) ); + }; + + onMount(async () => { + if (dummy) return; + + scheduleAiringRefresh(); if (browser) await localforage.setItem( @@ -120,7 +147,13 @@ ); }); - onDestroy(() => clearInterval(keyCacher)); + $: if (browser && !dummy) { + media; + + scheduleAiringRefresh(); + } + + onDestroy(() => clearAiringRefreshTimeout()); const increment = (anime: Media, progress: number) => { if (!dummy && pendingUpdate !== anime.id) { diff --git a/src/lib/List/Anime/DueAnimeList.svelte b/src/lib/List/Anime/DueAnimeList.svelte index 2db8da65..0c1e128a 100644 --- a/src/lib/List/Anime/DueAnimeList.svelte +++ b/src/lib/List/Anime/DueAnimeList.svelte @@ -16,27 +16,48 @@ let animeLists: Promise<Media[]>; let startTime: number; let endTime: number; - - const keyCacher = setInterval( - () => { - startTime = performance.now(); - endTime = -1; - animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, { - forcePrune: true, - addNotification - }); - }, - $settings.cacheMinutes * 1000 * 60 - ); + let keyCacher: ReturnType<typeof setInterval> | undefined; + let keyCacheMinutes = -1; + + const restartKeyCacher = (cacheMinutes: number) => { + if (keyCacher) clearInterval(keyCacher); + + keyCacheMinutes = cacheMinutes; + keyCacher = setInterval( + () => { + startTime = performance.now(); + endTime = -1; + animeLists = mediaListCollection( + user, + $identity, + Type.Anime, + $anime, + $lastPruneTimes.anime, + { + forcePrune: true, + addNotification + } + ); + }, + cacheMinutes * 1000 * 60 + ); + }; onMount(async () => { + restartKeyCacher($settings.cacheMinutes); + startTime = performance.now(); animeLists = mediaListCollection(user, $identity, Type.Anime, $anime, $lastPruneTimes.anime, { addNotification }); }); - onDestroy(() => clearInterval(keyCacher)); + $: if (keyCacher && keyCacheMinutes !== $settings.cacheMinutes) + restartKeyCacher($settings.cacheMinutes); + + onDestroy(() => { + if (keyCacher) clearInterval(keyCacher); + }); const cleanMedia = ( anime: Media[], |