aboutsummaryrefslogtreecommitdiff
path: root/src/lib/List
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
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')
-rw-r--r--src/lib/List/Anime/CleanAnimeList.svelte83
-rw-r--r--src/lib/List/Anime/DueAnimeList.svelte47
-rw-r--r--src/lib/List/CleanGrid.svelte4
-rw-r--r--src/lib/List/CleanList.svelte4
-rw-r--r--src/lib/List/Manga/MangaListTemplate.svelte45
5 files changed, 129 insertions, 54 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[],
diff --git a/src/lib/List/CleanGrid.svelte b/src/lib/List/CleanGrid.svelte
index 1e6f5a12..4f628c3c 100644
--- a/src/lib/List/CleanGrid.svelte
+++ b/src/lib/List/CleanGrid.svelte
@@ -17,7 +17,7 @@
let uniqueID = new Date().getTime();
- $: sortedMedia = reverseSort ? media.reverse() : media;
+ $: sortedMedia = reverseSort ? [...media].reverse() : media;
$: processedMedia = limit !== undefined ? sortedMedia.slice(0, limit) : sortedMedia;
</script>
@@ -25,7 +25,7 @@
class="covers"
style={`grid-template-columns: repeat(auto-fill, minmax(${$settings.displayCoverWidth}px, 1fr))`}
>
- {#each processedMedia as title, index}
+ {#each processedMedia as title, index (title.id)}
{@const progress = (title.mediaListEntry || { progress: 0 }).progress}
{@const isAboveFold = index < 6}
diff --git a/src/lib/List/CleanList.svelte b/src/lib/List/CleanList.svelte
index 63656ab3..bf8c44ff 100644
--- a/src/lib/List/CleanList.svelte
+++ b/src/lib/List/CleanList.svelte
@@ -12,11 +12,11 @@
export let lastUpdatedMedia: number;
export let reverseSort = false;
- $: processedMedia = reverseSort ? media.reverse() : media;
+ $: processedMedia = reverseSort ? [...media].reverse() : media;
</script>
<ul>
- {#each processedMedia as title}
+ {#each processedMedia as title (title.id)}
{@const progress = (title.mediaListEntry || { progress: 0 }).progress}
{#if type === 'anime' ? upcoming || notYetReleased || progress !== (title.nextAiringEpisode?.episode || 9999) - 1 : progress !== title.episodes}
diff --git a/src/lib/List/Manga/MangaListTemplate.svelte b/src/lib/List/Manga/MangaListTemplate.svelte
index c2fc0513..f549496d 100644
--- a/src/lib/List/Manga/MangaListTemplate.svelte
+++ b/src/lib/List/Manga/MangaListTemplate.svelte
@@ -45,19 +45,35 @@
let rateLimited = false;
let forceFlag = false;
let lastListSize = 5;
-
- const keyCacher = setInterval(
- () => {
- startTime = performance.now();
- endTime = -1;
- mangaLists = mediaListCollection(user, $identity, Type.Manga, $manga, $lastPruneTimes.manga, {
- 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;
+ mangaLists = mediaListCollection(
+ user,
+ $identity,
+ Type.Manga,
+ $manga,
+ $lastPruneTimes.manga,
+ {
+ addNotification
+ }
+ );
+ },
+ cacheMinutes * 1000 * 60
+ );
+ };
onMount(async () => {
+ restartKeyCacher(Math.max($settings.cacheMangaMinutes, 5));
+
if (browser) {
const lastStoredList = (await localforage.getItem(
`last${due ? '' : 'Completed'}MangaListLength`
@@ -96,7 +112,12 @@
}
});
- onDestroy(() => clearInterval(keyCacher));
+ $: if (keyCacher && keyCacheMinutes !== Math.max($settings.cacheMangaMinutes, 5))
+ restartKeyCacher(Math.max($settings.cacheMangaMinutes, 5));
+
+ onDestroy(() => {
+ if (keyCacher) clearInterval(keyCacher);
+ });
const cleanMedia = async (manga: Media[], displayUnresolved: boolean, force: boolean) => {
progress = 0;