From bcbb7610adf7c733c65a9dbe6aeccfdbfcc52f09 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 26 Feb 2025 16:40:15 -0700 Subject: [PATCH] Refactor GetAllEntries methods for clarity and maintainability d --- .../QueryObjects/LibraryBookQueries.cs | 6 +- .../GridView/LibraryBookEntry[TStatus].cs | 27 ++----- .../GridView/SeriesEntry[TStatus].cs | 78 +++++++++---------- 3 files changed, 49 insertions(+), 62 deletions(-) diff --git a/Source/DataLayer/QueryObjects/LibraryBookQueries.cs b/Source/DataLayer/QueryObjects/LibraryBookQueries.cs index 476e617d..91f89995 100644 --- a/Source/DataLayer/QueryObjects/LibraryBookQueries.cs +++ b/Source/DataLayer/QueryObjects/LibraryBookQueries.cs @@ -21,8 +21,8 @@ namespace DataLayer .AsNoTrackingWithIdentityResolution() .GetLibrary() .AsEnumerable() - .Where(lb => !lb.Book.IsEpisodeParent() || includeParents) - .ToList(); + .Where(c => !c.Book.IsEpisodeParent() || includeParents) + .ToList(); public static LibraryBook GetLibraryBook_Flat_NoTracking(this LibationContext context, string productId) => context @@ -91,7 +91,7 @@ namespace DataLayer } #nullable disable - public static IEnumerable FindChildren(this IEnumerable bookList, LibraryBook parent) + public static List FindChildren(this IEnumerable bookList, LibraryBook parent) => bookList .Where( lb => diff --git a/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs b/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs index 5866ae40..062bd376 100644 --- a/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs +++ b/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs @@ -40,30 +40,17 @@ namespace LibationUiBase.GridView int parallelism = int.Max(1, Environment.ProcessorCount - 1); - (int numPer, int rem) = int.DivRem(products.Length, parallelism); - if (rem != 0) numPer++; + (int batchSize, int rem) = int.DivRem(products.Length, parallelism); + if (rem != 0) batchSize++; - var tasks = new Task[parallelism]; var syncContext = SynchronizationContext.Current; - for (int i = 0; i < parallelism; i++) + //Asynchronously create an ILibraryBookEntry for every book in the library + var tasks = products.Chunk(batchSize).Select(batch => Task.Run(() => { - int start = i * numPer; - tasks[i] = Task.Run(() => - { - SynchronizationContext.SetSynchronizationContext(syncContext); - - int length = int.Min(numPer, products.Length - start); - if (length < 1) return Array.Empty(); - - var result = new IGridEntry[length]; - - for (int j = 0; j < length; j++) - result[j] = new LibraryBookEntry(products[start + j]); - - return result; - }); - } + SynchronizationContext.SetSynchronizationContext(syncContext); + return batch.Select(lb => new LibraryBookEntry(lb) as IGridEntry); + })); return (await Task.WhenAll(tasks)).SelectMany(a => a).ToList(); } diff --git a/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs b/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs index 0c140946..ba3fef4f 100644 --- a/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs +++ b/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs @@ -1,6 +1,5 @@ using DataLayer; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -62,53 +61,54 @@ namespace LibationUiBase.GridView var seriesBooks = libraryBooks.Where(lb => lb.Book.IsEpisodeParent()).ToArray(); var allEpisodes = libraryBooks.Where(lb => lb.Book.IsEpisodeChild()).ToArray(); - int parallelism = int.Max(1, Environment.ProcessorCount - 1); - - var tasks = new Task[parallelism]; - var syncContext = SynchronizationContext.Current; - - var q = new BlockingCollection<(int, LibraryBook episode)>(); - var seriesEntries = new ISeriesEntry[seriesBooks.Length]; - var seriesEpisodes = new ConcurrentBag[seriesBooks.Length]; + var seriesEpisodes = new ILibraryBookEntry[seriesBooks.Length][]; - for (int i = 0; i < parallelism; i++) - { - tasks[i] = Task.Run(() => - { - SynchronizationContext.SetSynchronizationContext(syncContext); + var syncContext = SynchronizationContext.Current; + var options = new ParallelOptions { MaxDegreeOfParallelism = int.Max(1, Environment.ProcessorCount - 1) }; - while (q.TryTake(out var entry, -1)) - { - var parent = seriesEntries[entry.Item1]; - var episodeBag = seriesEpisodes[entry.Item1]; - episodeBag.Add(new LibraryBookEntry(entry.episode, parent)); - } - }); - } + //Asynchronously create an ILibraryBookEntry for every episode in the library + await Parallel.ForEachAsync(getAllEpisodes(), options, createEpisodeEntry); - for (int i = 0; i (series, Enumerable.Empty()); - seriesEpisodes[i] = new ConcurrentBag(); - - foreach (var ep in allEpisodes.FindChildren(series)) - q.Add((i, ep)); - } - - q.CompleteAdding(); - - await Task.WhenAll(tasks); - - for (int i = 0; i < seriesBooks.Length; i++) + //Match all episode entries to their corresponding parents + for (int i = seriesEntries.Length - 1; i >= 0; i--) { var series = seriesEntries[i]; - series.Children.AddRange(seriesEpisodes[i].OrderByDescending(c => c.SeriesOrder)); + + //Sort episodes by series order descending, then add them to their parent's entry + Array.Sort(seriesEpisodes[i], (a, b) => -a.SeriesOrder.CompareTo(b.SeriesOrder)); + series.Children.AddRange(seriesEpisodes[i]); series.UpdateLibraryBook(series.LibraryBook); } - return seriesEntries.Where(s => s.Children.Count != 0).ToList(); + return seriesEntries.Where(s => s.Children.Count != 0).Cast().ToList(); + + //Create a LibraryBookEntry for a single episode + ValueTask createEpisodeEntry((int seriesIndex, int episodeIndex, LibraryBook episode) data, CancellationToken cancellationToken) + { + SynchronizationContext.SetSynchronizationContext(syncContext); + var parent = seriesEntries[data.seriesIndex]; + seriesEpisodes[data.seriesIndex][data.episodeIndex] = new LibraryBookEntry(data.episode, parent); + return ValueTask.CompletedTask; + } + + //Enumeration all series episodes, along with the index to its seriesEntries entry + //and an index to its seriesEpisodes entry + IEnumerable<(int seriesIndex, int episodeIndex, LibraryBook episode)> getAllEpisodes() + { + for (int i = 0; i < seriesBooks.Length; i++) + { + var series = seriesBooks[i]; + var childEpisodes = allEpisodes.FindChildren(series); + + SynchronizationContext.SetSynchronizationContext(syncContext); + seriesEntries[i] = new SeriesEntry(series, []); + seriesEpisodes[i] = new ILibraryBookEntry[childEpisodes.Count]; + + for (int j = 0; j < childEpisodes.Count; j++) + yield return (i, j, childEpisodes[j]); + } + } } public void RemoveChild(ILibraryBookEntry lbe)