From 5d5e3a6671ab72db426dd079a1e26e8cf69a257a Mon Sep 17 00:00:00 2001 From: Mbucari <37587114+Mbucari@users.noreply.github.com> Date: Sat, 8 Jul 2023 13:53:01 -0600 Subject: [PATCH] improve startup time --- .../ViewModels/MainVM.BackupCounts.cs | 14 +++-- .../ViewModels/ProductsDisplayViewModel.cs | 28 ++------- .../Views/MainWindow.axaml.cs | 2 +- .../Views/ProductsDisplay.axaml.cs | 2 +- .../GridView/LibraryBookEntry[TStatus].cs | 37 ++++++++++++ .../GridView/SeriesEntry[TStatus].cs | 58 +++++++++++++++++++ Source/LibationWinForms/Form1.BackupCounts.cs | 9 ++- Source/LibationWinForms/Form1.cs | 14 +++-- .../GridView/GridEntryBindingList.cs | 1 - .../GridView/ProductsDisplay.cs | 8 +-- .../LibationWinForms/GridView/ProductsGrid.cs | 31 +++++----- Source/LibationWinForms/Program.cs | 13 ++++- 12 files changed, 157 insertions(+), 60 deletions(-) diff --git a/Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs b/Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs index 48f719eb..b0a103ea 100644 --- a/Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs +++ b/Source/LibationAvalonia/ViewModels/MainVM.BackupCounts.cs @@ -1,7 +1,10 @@ using ApplicationServices; using Avalonia.Threading; +using DataLayer; using LibationFileManager; using ReactiveUI; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace LibationAvalonia.ViewModels @@ -41,16 +44,17 @@ namespace LibationAvalonia.ViewModels private void Configure_BackupCounts() { - MainWindow.Loaded += setBackupCounts; - LibraryCommands.LibrarySizeChanged += setBackupCounts; - LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts; + MainWindow.LibraryLoaded += (_, e) => setBackupCounts(e.Where(l => !l.Book.IsEpisodeParent())); + LibraryCommands.LibrarySizeChanged += (_,_) => setBackupCounts(); + LibraryCommands.BookUserDefinedItemCommitted += (_, _) => setBackupCounts(); } - private async void setBackupCounts(object _, object __) + private async void setBackupCounts(IEnumerable libraryBooks = null) { if (updateCountsTask?.IsCompleted ?? true) { - updateCountsTask = Task.Run(() => LibraryCommands.GetCounts()); + libraryBooks ??= DbContexts.GetLibrary_Flat_NoTracking(); + updateCountsTask = Task.Run(() => LibraryCommands.GetCounts(libraryBooks)); var stats = await updateCountsTask; await Dispatcher.UIThread.InvokeAsync(() => LibraryStats = stats); diff --git a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs index 8f6f1485..7797bd6e 100644 --- a/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProductsDisplayViewModel.cs @@ -91,37 +91,21 @@ namespace LibationAvalonia.ViewModels #region Display Functions - internal void BindToGrid(List dbBooks) + internal async Task BindToGridAsync(List dbBooks) { GridEntries = new(SOURCE) { Filter = CollectionFilter }; - var geList = dbBooks - .Where(lb => lb.Book.IsProduct()) - .Select(b => new LibraryBookEntry(b)) - .ToList(); + var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks); - var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild()).ToList(); + var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks); - var seriesBooks = dbBooks.Where(lb => lb.Book.IsEpisodeParent()).ToList(); - - foreach (var parent in seriesBooks) - { - var seriesEpisodes = episodes.FindChildren(parent); - - if (!seriesEpisodes.Any()) continue; - - var seriesEntry = new SeriesEntry(parent, seriesEpisodes); - seriesEntry.Liberate.Expanded = false; - - geList.Add(seriesEntry); - } + SOURCE.AddRange(geList.Concat(seriesEntries).OrderDescending(new RowComparer(null))); //Create the filtered-in list before adding entries to avoid a refresh - FilteredInGridEntries = geList.Union(geList.OfType().SelectMany(s => s.Children)).FilterEntries(FilterString); - SOURCE.AddRange(geList.OrderDescending(new RowComparer(null))); + FilteredInGridEntries = geList.Union(seriesEntries.SelectMany(s => s.Children)).FilterEntries(FilterString); //Add all children beneath their parent - foreach (var series in SOURCE.OfType().ToList()) + foreach (var series in seriesEntries) { var seriesIndex = SOURCE.IndexOf(series); foreach (var child in series.Children) diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml.cs b/Source/LibationAvalonia/Views/MainWindow.axaml.cs index 96e0edb6..c708258c 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml.cs +++ b/Source/LibationAvalonia/Views/MainWindow.axaml.cs @@ -61,7 +61,7 @@ namespace LibationAvalonia.Views if (QuickFilters.UseDefault) await ViewModel.PerformFilter(QuickFilters.Filters.FirstOrDefault()); - ViewModel.ProductsDisplay.BindToGrid(dbBooks); + await ViewModel.ProductsDisplay.BindToGridAsync(dbBooks); } private void selectAndFocusSearchBox() diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index c5313917..54154426 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -51,7 +51,7 @@ namespace LibationAvalonia.Views catch { sampleEntries = new(); } var pdvm = new ProductsDisplayViewModel(); - pdvm.BindToGrid(sampleEntries); + _ = pdvm.BindToGridAsync(sampleEntries); DataContext = pdvm; return; diff --git a/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs b/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs index 93ef84d9..e8ee3f0e 100644 --- a/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs +++ b/Source/LibationUiBase/GridView/LibraryBookEntry[TStatus].cs @@ -1,6 +1,10 @@ using DataLayer; using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Threading.Tasks; +using System.Threading; +using System.Linq; namespace LibationUiBase.GridView { @@ -29,6 +33,39 @@ namespace LibationUiBase.GridView LoadCover(); } + + public static async Task> GetAllProductsAsync(IEnumerable libraryBooks) + { + var products = libraryBooks.Where(lb => lb.Book.IsProduct()).ToArray(); + + int parallelism = int.Max(1, Environment.ProcessorCount - 1); + + (int numPer, int rem) = int.DivRem(products.Length, parallelism); + if (rem != 0) numPer++; + + var tasks = new Task[parallelism]; + var syncContext = SynchronizationContext.Current; + + for (int i = 0; i < parallelism; i++) + { + int start = i * numPer; + tasks[i] = Task.Run(() => + { + SynchronizationContext.SetSynchronizationContext(syncContext); + + int length = int.Min(numPer, products.Length - start); + var result = new IGridEntry[length]; + + for (int j = 0; j < length; j++) + result[j] = new LibraryBookEntry(products[start + j]); + + return result; + }); + } + + return (await Task.WhenAll(tasks)).SelectMany(a => a).ToList(); + } + protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); } } diff --git a/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs b/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs index eaf49fcb..7ca1f73e 100644 --- a/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs +++ b/Source/LibationUiBase/GridView/SeriesEntry[TStatus].cs @@ -1,7 +1,11 @@ using DataLayer; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; namespace LibationUiBase.GridView { @@ -54,6 +58,60 @@ namespace LibationUiBase.GridView LoadCover(); } + public static async Task> GetAllSeriesEntriesAsync(IEnumerable libraryBooks) + { + 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]; + + for (int i = 0; i < parallelism; i++) + { + tasks[i] = Task.Run(() => + { + SynchronizationContext.SetSynchronizationContext(syncContext); + + while (q.TryTake(out var entry, -1)) + { + var parent = seriesEntries[entry.Item1]; + var episodeBag = seriesEpisodes[entry.Item1]; + episodeBag.Add(new LibraryBookEntry(entry.episode, parent)); + } + }); + } + + 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++) + { + var series = seriesEntries[i]; + series.Children.AddRange(seriesEpisodes[i].OrderByDescending(c => c.SeriesOrder)); + series.UpdateLibraryBook(series.LibraryBook); + } + + return seriesEntries.Where(s => s.Children.Count != 0).ToList(); + } + public void RemoveChild(ILibraryBookEntry lbe) { Children.Remove(lbe); diff --git a/Source/LibationWinForms/Form1.BackupCounts.cs b/Source/LibationWinForms/Form1.BackupCounts.cs index 386c7afc..941cedf4 100644 --- a/Source/LibationWinForms/Form1.BackupCounts.cs +++ b/Source/LibationWinForms/Form1.BackupCounts.cs @@ -1,6 +1,8 @@ using ApplicationServices; +using DataLayer; using Dinah.Core; using Dinah.Core.Threading; +using System.Collections.Generic; namespace LibationWinForms { @@ -14,7 +16,6 @@ namespace LibationWinForms beginBookBackupsToolStripMenuItem.Format(0); beginPdfBackupsToolStripMenuItem.Format(0); - Load += setBackupCounts; LibraryCommands.LibrarySizeChanged += setBackupCounts; LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts; @@ -40,7 +41,11 @@ namespace LibationWinForms while (runBackupCountsAgain) { runBackupCountsAgain = false; - e.Result = LibraryCommands.GetCounts(); + + if (e.Argument is not IEnumerable lbs) + lbs = DbContexts.GetLibrary_Flat_NoTracking(); + + e.Result = LibraryCommands.GetCounts(lbs); } } diff --git a/Source/LibationWinForms/Form1.cs b/Source/LibationWinForms/Form1.cs index 956e53c0..79754f6d 100644 --- a/Source/LibationWinForms/Form1.cs +++ b/Source/LibationWinForms/Form1.cs @@ -4,10 +4,8 @@ using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using ApplicationServices; -using Dinah.Core; -using Dinah.Core.Threading; +using DataLayer; using LibationFileManager; -using LibationWinForms.Dialogs; namespace LibationWinForms { @@ -57,8 +55,7 @@ namespace LibationWinForms // Configure_Grid(); // since it's just this, can keep here. If it needs more, then give grid it's own 'partial class Form1' { - this.Load += (_, __) => productsDisplay.Display(); - LibraryCommands.LibrarySizeChanged += (_, __) => this.UIThreadAsync(() => productsDisplay.Display()); + LibraryCommands.LibrarySizeChanged += (_, __) => Invoke(productsDisplay.DisplayAsync); } Shown += Form1_Shown; } @@ -78,6 +75,13 @@ namespace LibationWinForms } } + public async Task InitLibraryAsync(List libraryBooks) + { + runBackupCountsAgain = true; + updateCountsBw.RunWorkerAsync(libraryBooks.Where(b => !b.Book.IsEpisodeParent())); + await productsDisplay.DisplayAsync(libraryBooks); + } + private void Form1_Load(object sender, EventArgs e) { if (this.DesignMode) diff --git a/Source/LibationWinForms/GridView/GridEntryBindingList.cs b/Source/LibationWinForms/GridView/GridEntryBindingList.cs index 571db313..38102266 100644 --- a/Source/LibationWinForms/GridView/GridEntryBindingList.cs +++ b/Source/LibationWinForms/GridView/GridEntryBindingList.cs @@ -30,7 +30,6 @@ namespace LibationWinForms.GridView { SearchEngineCommands.SearchEngineUpdated += SearchEngineCommands_SearchEngineUpdated; ListChanged += GridEntryBindingList_ListChanged; - refreshEntries(); } /// All items in the list, including those filtered out. diff --git a/Source/LibationWinForms/GridView/ProductsDisplay.cs b/Source/LibationWinForms/GridView/ProductsDisplay.cs index 75996447..e63cea00 100644 --- a/Source/LibationWinForms/GridView/ProductsDisplay.cs +++ b/Source/LibationWinForms/GridView/ProductsDisplay.cs @@ -306,22 +306,22 @@ namespace LibationWinForms.GridView #region UI display functions - public void Display() + public async Task DisplayAsync(List libraryBooks = null) { try { // don't return early if lib size == 0. this will not update correctly if all books are removed - var lib = DbContexts.GetLibrary_Flat_NoTracking(includeParents: true); + libraryBooks ??= DbContexts.GetLibrary_Flat_NoTracking(includeParents: true); if (!hasBeenDisplayed) { // bind - productsGrid.BindToGrid(lib); + await productsGrid.BindToGridAsync(libraryBooks); hasBeenDisplayed = true; InitialLoaded?.Invoke(this, new()); } else - productsGrid.UpdateGrid(lib); + productsGrid.UpdateGrid(libraryBooks); } catch (Exception ex) { diff --git a/Source/LibationWinForms/GridView/ProductsGrid.cs b/Source/LibationWinForms/GridView/ProductsGrid.cs index 50172673..4d304883 100644 --- a/Source/LibationWinForms/GridView/ProductsGrid.cs +++ b/Source/LibationWinForms/GridView/ProductsGrid.cs @@ -5,8 +5,11 @@ using LibationUiBase.GridView; using System; using System.Collections.Generic; using System.Data; +using System.Diagnostics; using System.Drawing; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; namespace LibationWinForms.GridView @@ -160,27 +163,23 @@ namespace LibationWinForms.GridView } } - internal void BindToGrid(List dbBooks) + internal async Task BindToGridAsync(List dbBooks) { - var geList = dbBooks - .Where(lb => lb.Book.IsProduct()) - .Select(b => new LibraryBookEntry(b)) - .ToList(); + var geList = await LibraryBookEntry.GetAllProductsAsync(dbBooks); - var episodes = dbBooks.Where(lb => lb.Book.IsEpisodeChild()); + var seriesEntries = await SeriesEntry.GetAllSeriesEntriesAsync(dbBooks); - var seriesBooks = dbBooks.Where(lb => lb.Book.IsEpisodeParent()).ToList(); + geList.AddRange(seriesEntries); + //Sort descending by date (default sort property) + var comparer = new RowComparer(); + geList.Sort((a, b) => comparer.Compare(b, a)); - foreach (var parent in seriesBooks) + //Add all children beneath their parent + foreach (var series in seriesEntries) { - var seriesEpisodes = episodes.FindChildren(parent); - - if (!seriesEpisodes.Any()) continue; - - var seriesEntry = new SeriesEntry(parent, seriesEpisodes); - - geList.Add(seriesEntry); - geList.AddRange(seriesEntry.Children); + var seriesIndex = geList.IndexOf(series); + foreach (var child in series.Children) + geList.Insert(++seriesIndex, child); } bindingList = new GridEntryBindingList(geList); diff --git a/Source/LibationWinForms/Program.cs b/Source/LibationWinForms/Program.cs index 340c29b7..36161424 100644 --- a/Source/LibationWinForms/Program.cs +++ b/Source/LibationWinForms/Program.cs @@ -2,12 +2,13 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using System.Windows.Forms; +using ApplicationServices; using AppScaffolding; -using Dinah.Core; +using DataLayer; using LibationFileManager; using LibationWinForms.Dialogs; -using Serilog; namespace LibationWinForms { @@ -20,6 +21,7 @@ namespace LibationWinForms [STAThread] static void Main() { + Task> libraryLoadTask; try { //// Uncomment to see Console. Must be called before anything writes to Console. @@ -48,6 +50,9 @@ namespace LibationWinForms // migrations which require Forms or are long-running RunWindowsOnlyMigrations(config); + //start loading the db as soon as possible + libraryLoadTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true)); + MessageBoxLib.VerboseLoggingWarning_ShowIfTrue(); // logging is init'd here @@ -71,7 +76,9 @@ namespace LibationWinForms // global exception handling (ShowAdminAlert) attempts to use logging. only call it after logging has been init'd postLoggingGlobalExceptionHandling(); - Application.Run(new Form1()); + var form1 = new Form1(); + form1.Load += async (_, _) => await form1.InitLibraryAsync(await libraryLoadTask); + Application.Run(form1); } private static void RunInstaller(Configuration config)