perf(14-03): add notification batching to queue store and granular change tracking to library store
- Queue store now uses queueMicrotask batching (matching library store pattern) - Library store adds changeGeneration counter incremented only on actual data changes - LibraryController checks changeGeneration before requestUpdate, skipping loading-only transitions - Reduces unnecessary component re-renders during data loading cycles
This commit is contained in:
@@ -29,9 +29,21 @@ export class LibraryController implements ReactiveController {
|
||||
// LIFECYCLE HOOKS
|
||||
// ===================================================================
|
||||
|
||||
/** Last observed changeGeneration from the store. */
|
||||
private lastChangeGen = libraryStore.changeGeneration;
|
||||
|
||||
hostConnected(): void {
|
||||
this.lastChangeGen = libraryStore.changeGeneration;
|
||||
|
||||
this.unsubscribe = libraryStore.subscribe(() => {
|
||||
this.host.requestUpdate();
|
||||
const gen = libraryStore.changeGeneration;
|
||||
|
||||
// Skip requestUpdate when only loading flags toggled
|
||||
// (changeGeneration unchanged means no actual data changed).
|
||||
if (gen !== this.lastChangeGen) {
|
||||
this.lastChangeGen = gen;
|
||||
this.host.requestUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,13 @@ class LibraryStore {
|
||||
private subscribers = new Set<Subscriber>();
|
||||
private notifyScheduled = false;
|
||||
|
||||
/**
|
||||
* Monotonic counter incremented only when actual data changes
|
||||
* (not loading flag transitions). Subscribers can compare against
|
||||
* a saved value to skip requestUpdate when only loading state toggled.
|
||||
*/
|
||||
private changeGen = 0;
|
||||
|
||||
constructor() {
|
||||
EventsOn(Events.LibraryScanComplete, () => {
|
||||
this.invalidate();
|
||||
@@ -105,6 +112,7 @@ class LibraryStore {
|
||||
try {
|
||||
const tracks = await GetAllTracks();
|
||||
this.tracks = tracks;
|
||||
this.changeGen++;
|
||||
|
||||
return tracks;
|
||||
} finally {
|
||||
@@ -128,6 +136,7 @@ class LibraryStore {
|
||||
try {
|
||||
const albums = await GetAllAlbums();
|
||||
this.albums = albums;
|
||||
this.changeGen++;
|
||||
|
||||
return albums;
|
||||
} finally {
|
||||
@@ -151,6 +160,7 @@ class LibraryStore {
|
||||
try {
|
||||
const artists = await GetAllArtists();
|
||||
this.artists = artists;
|
||||
this.changeGen++;
|
||||
|
||||
return artists;
|
||||
} finally {
|
||||
@@ -174,6 +184,7 @@ class LibraryStore {
|
||||
try {
|
||||
const genres = await GetAllGenresWithCounts();
|
||||
this.genres = genres;
|
||||
this.changeGen++;
|
||||
|
||||
return genres;
|
||||
} finally {
|
||||
@@ -243,6 +254,17 @@ class LibraryStore {
|
||||
return this.genresLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monotonic counter that increments only when cached data
|
||||
* actually changes (tracks, albums, artists, genres, coverSize,
|
||||
* or invalidation). Loading flag transitions do NOT increment.
|
||||
* Controllers can compare against a saved value to skip
|
||||
* unnecessary requestUpdate() calls.
|
||||
*/
|
||||
get changeGeneration(): number {
|
||||
return this.changeGen;
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// SCROLL POSITION
|
||||
// ===================================================================
|
||||
@@ -271,6 +293,7 @@ class LibraryStore {
|
||||
if (clamped === this.coverSizeValue) return;
|
||||
|
||||
this.coverSizeValue = clamped;
|
||||
this.changeGen++;
|
||||
this.saveCoverSize();
|
||||
this.notify();
|
||||
}
|
||||
@@ -314,6 +337,7 @@ class LibraryStore {
|
||||
this.albums = null;
|
||||
this.artists = null;
|
||||
this.genres = null;
|
||||
this.changeGen++;
|
||||
this.scrollPositions = { tracks: 0, albums: 0, artists: 0, genres: 0 };
|
||||
this.notify();
|
||||
this.eagerFetch();
|
||||
|
||||
@@ -52,6 +52,7 @@ class QueueStore {
|
||||
};
|
||||
|
||||
private subscribers = new Set<Subscriber>();
|
||||
private notifyScheduled = false;
|
||||
|
||||
constructor() {
|
||||
this.initializeEventListeners();
|
||||
@@ -268,7 +269,14 @@ class QueueStore {
|
||||
}
|
||||
|
||||
private notify(): void {
|
||||
this.subscribers.forEach((callback) => callback());
|
||||
if (this.notifyScheduled) return;
|
||||
this.notifyScheduled = true;
|
||||
queueMicrotask(() => {
|
||||
this.notifyScheduled = false;
|
||||
for (const sub of this.subscribers) {
|
||||
sub();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user