diff options
| author | Fuwn <[email protected]> | 2026-03-01 13:12:25 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-03-01 13:25:59 -0800 |
| commit | ceb53b18a4d766b95c7107d1f778d443a0e3d9b3 (patch) | |
| tree | 5957306e0ae159bc0c9e6d99450035ea7ab6e566 /src/lib/Media/Anime | |
| parent | perf(match): pre-index subtitle schedule by day and token (diff) | |
| download | due.moe-ceb53b18a4d766b95c7107d1f778d443a0e3d9b3.tar.xz due.moe-ceb53b18a4d766b95c7107d1f778d443a0e3d9b3.zip | |
perf(match): cache indexed schedules and per-airing match results
Diffstat (limited to 'src/lib/Media/Anime')
| -rw-r--r-- | src/lib/Media/Anime/Airing/Subtitled/match.ts | 72 |
1 files changed, 65 insertions, 7 deletions
diff --git a/src/lib/Media/Anime/Airing/Subtitled/match.ts b/src/lib/Media/Anime/Airing/Subtitled/match.ts index 2281aca8..52cf581f 100644 --- a/src/lib/Media/Anime/Airing/Subtitled/match.ts +++ b/src/lib/Media/Anime/Airing/Subtitled/match.ts @@ -26,6 +26,7 @@ interface DayScheduleIndex { interface ScheduleIndex { byDay: Map<string, DayScheduleIndex>; + version: string; } const secondsUntil = (targetTime: string, targetDay: string) => { @@ -131,11 +132,24 @@ const indexPush = (index: Map<string, number[]>, key: string, entryIndex: number else index.set(key, [entryIndex]); }; +const scheduleIndexCache = new WeakMap<SubsPlease, ScheduleIndex>(); +const closestMatchCache = new Map<string, Time | null>(); + const buildScheduleIndex = (subsPlease: SubsPlease): ScheduleIndex => { const byDay = new Map<string, DayScheduleIndex>(); + const versionParts: string[] = []; for (const [day, value] of Object.entries(subsPlease.schedule)) { const flattenedValue = Array.isArray(value) ? value.flat() : []; + const firstEntry = flattenedValue[0]; + const lastEntry = flattenedValue[flattenedValue.length - 1]; + + versionParts.push( + `${day}:${flattenedValue.length}:${firstEntry?.title || ''}:${firstEntry?.time || ''}:${ + lastEntry?.title || '' + }:${lastEntry?.time || ''}` + ); + const dayIndex: DayScheduleIndex = { entries: [], exactTitleIndex: new Map<string, number[]>(), @@ -165,11 +179,23 @@ const buildScheduleIndex = (subsPlease: SubsPlease): ScheduleIndex => { byDay.set(day, dayIndex); } - return { byDay }; + return { + byDay, + version: `${subsPlease.tz}:${versionParts.join('|')}` + }; }; export const findClosestMatch = (scheduleIndex: ScheduleIndex, anime: Media): Time | null => { - if (excludeMatch.includes(anime.id)) return null; + if (excludeMatch.includes(anime.id)) { + closestMatchCache.set(`${anime.id}:excluded`, null); + + return null; + } + + const cacheKey = `${anime.id}:${anime.nextAiringEpisode?.airingAt || 0}:${scheduleIndex.version}`; + const cached = closestMatchCache.get(cacheKey); + + if (cached !== undefined) return cached; const airingDay = new Date((anime.nextAiringEpisode?.airingAt || 0) * 1000).toLocaleString( 'en-US', @@ -177,7 +203,11 @@ export const findClosestMatch = (scheduleIndex: ScheduleIndex, anime: Media): Ti ); const dayIndex = scheduleIndex.byDay.get(airingDay); - if (!dayIndex || dayIndex.entries.length === 0) return null; + if (!dayIndex || dayIndex.entries.length === 0) { + closestMatchCache.set(cacheKey, null); + + return null; + } let bestMatch: Time | null = null; let bestScore = 0; @@ -222,10 +252,26 @@ export const findClosestMatch = (scheduleIndex: ScheduleIndex, anime: Media): Ti } } - if (bestScore < MIN_MATCH_SCORE) return null; - if (bestScore - secondBestScore < MIN_MATCH_MARGIN) return null; - if (bestNumericTokenOverlap === 0 && bestTokenOverlap < MIN_TOKEN_OVERLAP) return null; + if (bestScore < MIN_MATCH_SCORE) { + closestMatchCache.set(cacheKey, null); + + return null; + } + + if (bestScore - secondBestScore < MIN_MATCH_MARGIN) { + closestMatchCache.set(cacheKey, null); + + return null; + } + + if (bestNumericTokenOverlap === 0 && bestTokenOverlap < MIN_TOKEN_OVERLAP) { + closestMatchCache.set(cacheKey, null); + + return null; + } + closestMatchCache.set(cacheKey, bestMatch); + return bestMatch; }; @@ -289,6 +335,18 @@ export const findClosestMedia = (media: Media[], matchFor: string) => { export const clearClosestMediaCache = () => findClosestMediaCache.clear(); +const getScheduleIndex = (subsPlease: SubsPlease): ScheduleIndex => { + const cached = scheduleIndexCache.get(subsPlease); + + if (cached) return cached; + + const built = buildScheduleIndex(subsPlease); + + scheduleIndexCache.set(subsPlease, built); + + return built; +}; + export const injectAiringTime = (anime: Media, subsPlease: SubsPlease | null) => { if (season() !== anime.season) return anime; @@ -308,7 +366,7 @@ export const injectAiringTime = (anime: Media, subsPlease: SubsPlease | null) => // || !(nativeUntilAiring !== undefined && nativeUntilAiring < 24 * 60 * 60) ) ) { - const scheduleIndex = buildScheduleIndex(subsPlease); + const scheduleIndex = getScheduleIndex(subsPlease); if ((anime.nextAiringEpisode?.episode || 0) > 1) { const foundTime: Time | null = findClosestMatch(scheduleIndex, anime); |