From 31414c587446e4008031871df6e17912175890af Mon Sep 17 00:00:00 2001 From: Yuri Tatishchev Date: Wed, 11 Feb 2026 22:56:49 -0800 Subject: [PATCH] perf(player): add idToIndex Map for O(1) track lookups Replace all linear find/findIndex/some scans with a derived Map keyed by track.id. This makes currentTrack, currentIndex, hasTrack, add, playNext, remove, and addAll all O(1) for ID lookups. --- src/lib/player/store.svelte.ts | 42 +++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/lib/player/store.svelte.ts b/src/lib/player/store.svelte.ts index 592c374..b37ec15 100644 --- a/src/lib/player/store.svelte.ts +++ b/src/lib/player/store.svelte.ts @@ -26,16 +26,26 @@ class PlayerStore { isMuted = $state(false); uiOpen = $state(false); // Mobile UI state - // Derived - currentTrack = $derived( - this.currentId - ? (this.queue.find((t) => t.id === this.currentId) ?? null) - : null, - ); + // O(1) index: track.id → index in queue + private idToIndex = $derived.by(() => { + const map = new Map(); + for (let i = 0; i < this.queue.length; i++) { + map.set(this.queue[i].id, i); + } + return map; + }); - currentIndex = $derived( - this.currentId ? this.queue.findIndex((t) => t.id === this.currentId) : -1, - ); + // Derived + currentTrack = $derived.by(() => { + if (this.currentId == null) return null; + const idx = this.idToIndex.get(this.currentId); + return idx !== undefined ? this.queue[idx] : null; + }); + + currentIndex = $derived.by(() => { + if (this.currentId == null) return -1; + return this.idToIndex.get(this.currentId) ?? -1; + }); displayQueue = $derived( this.isShuffled @@ -46,7 +56,7 @@ class PlayerStore { ); hasTrack(id: number) { - return this.queue.some((t) => t.id === id); + return this.idToIndex.has(id); } constructor() { @@ -105,9 +115,9 @@ class PlayerStore { // Actions add(track: Track, playNow = false) { - const existingIdx = this.queue.findIndex((t) => t.id === track.id); + const exists = this.hasTrack(track.id); - if (existingIdx !== -1) { + if (exists) { if (playNow) { this.playNext(track); this.playId(track.id); @@ -133,7 +143,7 @@ class PlayerStore { } playNext(track: Track) { - const existingIdx = this.queue.findIndex((t) => t.id === track.id); + const existingIdx = this.idToIndex.get(track.id) ?? -1; const targetTrack = track; if (existingIdx !== -1) { @@ -173,7 +183,7 @@ class PlayerStore { const newTracks: Track[] = []; for (const track of tracks) { // Check existence inline to avoid O(n) per-track via add() - if (!this.queue.some((t) => t.id === track.id)) { + if (!this.hasTrack(track.id)) { newTracks.push(track); } } @@ -200,8 +210,8 @@ class PlayerStore { } remove(id: number) { - const idx = this.queue.findIndex((t) => t.id === id); - if (idx === -1) return; + const idx = this.idToIndex.get(id); + if (idx === undefined) return; const wasCurrent = this.currentId === id;