aboutsummaryrefslogtreecommitdiff
path: root/src/lib/List/Anime
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-03-01 14:20:08 -0800
committerFuwn <[email protected]>2026-03-01 15:24:03 -0800
commit3b10a1f47fd5838fe3b94c19673a52610b88cf1e (patch)
treed468a1fc12290e38686b255194ff6596b58cbf01 /src/lib/List/Anime
parentperf(match): fast-path exact normalised title matches (diff)
downloaddue.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.svelte83
-rw-r--r--src/lib/List/Anime/DueAnimeList.svelte47
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[],