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
|
// LIFECYCLE HOOKS
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
|
/** Last observed changeGeneration from the store. */
|
||||||
|
private lastChangeGen = libraryStore.changeGeneration;
|
||||||
|
|
||||||
hostConnected(): void {
|
hostConnected(): void {
|
||||||
|
this.lastChangeGen = libraryStore.changeGeneration;
|
||||||
|
|
||||||
this.unsubscribe = libraryStore.subscribe(() => {
|
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 subscribers = new Set<Subscriber>();
|
||||||
private notifyScheduled = false;
|
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() {
|
constructor() {
|
||||||
EventsOn(Events.LibraryScanComplete, () => {
|
EventsOn(Events.LibraryScanComplete, () => {
|
||||||
this.invalidate();
|
this.invalidate();
|
||||||
@@ -105,6 +112,7 @@ class LibraryStore {
|
|||||||
try {
|
try {
|
||||||
const tracks = await GetAllTracks();
|
const tracks = await GetAllTracks();
|
||||||
this.tracks = tracks;
|
this.tracks = tracks;
|
||||||
|
this.changeGen++;
|
||||||
|
|
||||||
return tracks;
|
return tracks;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -128,6 +136,7 @@ class LibraryStore {
|
|||||||
try {
|
try {
|
||||||
const albums = await GetAllAlbums();
|
const albums = await GetAllAlbums();
|
||||||
this.albums = albums;
|
this.albums = albums;
|
||||||
|
this.changeGen++;
|
||||||
|
|
||||||
return albums;
|
return albums;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -151,6 +160,7 @@ class LibraryStore {
|
|||||||
try {
|
try {
|
||||||
const artists = await GetAllArtists();
|
const artists = await GetAllArtists();
|
||||||
this.artists = artists;
|
this.artists = artists;
|
||||||
|
this.changeGen++;
|
||||||
|
|
||||||
return artists;
|
return artists;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -174,6 +184,7 @@ class LibraryStore {
|
|||||||
try {
|
try {
|
||||||
const genres = await GetAllGenresWithCounts();
|
const genres = await GetAllGenresWithCounts();
|
||||||
this.genres = genres;
|
this.genres = genres;
|
||||||
|
this.changeGen++;
|
||||||
|
|
||||||
return genres;
|
return genres;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -243,6 +254,17 @@ class LibraryStore {
|
|||||||
return this.genresLoading;
|
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
|
// SCROLL POSITION
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
@@ -271,6 +293,7 @@ class LibraryStore {
|
|||||||
if (clamped === this.coverSizeValue) return;
|
if (clamped === this.coverSizeValue) return;
|
||||||
|
|
||||||
this.coverSizeValue = clamped;
|
this.coverSizeValue = clamped;
|
||||||
|
this.changeGen++;
|
||||||
this.saveCoverSize();
|
this.saveCoverSize();
|
||||||
this.notify();
|
this.notify();
|
||||||
}
|
}
|
||||||
@@ -314,6 +337,7 @@ class LibraryStore {
|
|||||||
this.albums = null;
|
this.albums = null;
|
||||||
this.artists = null;
|
this.artists = null;
|
||||||
this.genres = null;
|
this.genres = null;
|
||||||
|
this.changeGen++;
|
||||||
this.scrollPositions = { tracks: 0, albums: 0, artists: 0, genres: 0 };
|
this.scrollPositions = { tracks: 0, albums: 0, artists: 0, genres: 0 };
|
||||||
this.notify();
|
this.notify();
|
||||||
this.eagerFetch();
|
this.eagerFetch();
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class QueueStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private subscribers = new Set<Subscriber>();
|
private subscribers = new Set<Subscriber>();
|
||||||
|
private notifyScheduled = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initializeEventListeners();
|
this.initializeEventListeners();
|
||||||
@@ -268,7 +269,14 @@ class QueueStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private notify(): void {
|
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