From 36a37a04a94ed8e3082186d6345656e18aa66660 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Wed, 8 Jan 2020 21:22:49 -0600 Subject: [PATCH 1/2] Swapped calls to ObserveOnGuiThread() --- Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs | 4 ++-- Wabbajack.Lib/Downloaders/NexusDownloader.cs | 4 ++-- Wabbajack/View Models/Compilers/CompilerVM.cs | 6 +++--- Wabbajack/View Models/Installers/InstallerVM.cs | 2 +- Wabbajack/View Models/MainWindowVM.cs | 2 +- Wabbajack/View Models/ModListVM.cs | 2 +- Wabbajack/View Models/ModVM.cs | 2 +- Wabbajack/View Models/SlideShow.cs | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs index 1bead261..46f5937f 100644 --- a/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs +++ b/Wabbajack.Lib/Downloaders/AbstractNeedsLoginDownloader.cs @@ -42,10 +42,10 @@ namespace Wabbajack.Lib.Downloaders TriggerLogin = ReactiveCommand.CreateFromTask( execute: () => Utils.CatchAndLog(async () => await Utils.Log(new RequestSiteLogin(this)).Task), - canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler)); + canExecute: IsLoggedIn.Select(b => !b).ObserveOnGuiThread()); ClearLogin = ReactiveCommand.Create( execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson(_encryptedKeyName)), - canExecute: IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler)); + canExecute: IsLoggedIn.ObserveOnGuiThread()); } public ICommand TriggerLogin { get; } diff --git a/Wabbajack.Lib/Downloaders/NexusDownloader.cs b/Wabbajack.Lib/Downloaders/NexusDownloader.cs index 43edb37d..3e66e7a3 100644 --- a/Wabbajack.Lib/Downloaders/NexusDownloader.cs +++ b/Wabbajack.Lib/Downloaders/NexusDownloader.cs @@ -42,10 +42,10 @@ namespace Wabbajack.Lib.Downloaders TriggerLogin = ReactiveCommand.CreateFromTask( execute: () => Utils.CatchAndLog(NexusApiClient.RequestAndCacheAPIKey), - canExecute: IsLoggedIn.Select(b => !b).ObserveOn(RxApp.MainThreadScheduler)); + canExecute: IsLoggedIn.Select(b => !b).ObserveOnGuiThread()); ClearLogin = ReactiveCommand.Create( execute: () => Utils.CatchAndLog(() => Utils.DeleteEncryptedJson("nexusapikey")), - canExecute: IsLoggedIn.ObserveOn(RxApp.MainThreadScheduler)); + canExecute: IsLoggedIn.ObserveOnGuiThread()); } public async Task GetDownloaderState(dynamic archiveINI) diff --git a/Wabbajack/View Models/Compilers/CompilerVM.cs b/Wabbajack/View Models/Compilers/CompilerVM.cs index 1e6f1a20..0eb3dd33 100644 --- a/Wabbajack/View Models/Compilers/CompilerVM.cs +++ b/Wabbajack/View Models/Compilers/CompilerVM.cs @@ -1,4 +1,4 @@ -using DynamicData; +using DynamicData; using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -123,7 +123,7 @@ namespace Wabbajack // Throttle so that it only loads image after any sets of swaps have completed .Throttle(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler) .DistinctUntilChanged() - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOnGuiThread() .Select(path => { if (string.IsNullOrWhiteSpace(path)) return UIUtils.BitmapImageFromResource("Resources/Wabba_Mouth_No_Text.png"); @@ -172,8 +172,8 @@ namespace Wabbajack .Batch(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler) .EnsureUniqueChanges() .Filter(i => i.Status.IsWorking && i.Status.ID != WorkQueue.UnassignedCpuId) - .ObserveOn(RxApp.MainThreadScheduler) .Sort(SortExpressionComparer.Ascending(s => s.StartTime)) + .ObserveOnGuiThread() .Bind(StatusList) .Subscribe() .DisposeWith(CompositeDisposable); diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index d27520ad..8fa96f86 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -317,7 +317,7 @@ namespace Wabbajack .Batch(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler) .EnsureUniqueChanges() .Filter(i => i.Status.IsWorking && i.Status.ID != WorkQueue.UnassignedCpuId) - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOnGuiThread() .Sort(SortExpressionComparer.Ascending(s => s.StartTime)) .Bind(StatusList) .Subscribe() diff --git a/Wabbajack/View Models/MainWindowVM.cs b/Wabbajack/View Models/MainWindowVM.cs index ddd70b8a..3785150a 100644 --- a/Wabbajack/View Models/MainWindowVM.cs +++ b/Wabbajack/View Models/MainWindowVM.cs @@ -63,8 +63,8 @@ namespace Wabbajack .ToObservableChangeSet() .Buffer(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler) .Where(l => l.Count > 0) - .ObserveOn(RxApp.MainThreadScheduler) .FlattenBufferResult() + .ObserveOnGuiThread() .Bind(Log) .Subscribe() .DisposeWith(CompositeDisposable); diff --git a/Wabbajack/View Models/ModListVM.cs b/Wabbajack/View Models/ModListVM.cs index 17bb3076..f504b620 100644 --- a/Wabbajack/View Models/ModListVM.cs +++ b/Wabbajack/View Models/ModListVM.cs @@ -67,7 +67,7 @@ namespace Wabbajack return default(MemoryStream); } }) - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOnGuiThread() .Select(memStream => { if (memStream == null) return default(BitmapImage); diff --git a/Wabbajack/View Models/ModVM.cs b/Wabbajack/View Models/ModVM.cs index 9e2344b7..cdfe3efe 100644 --- a/Wabbajack/View Models/ModVM.cs +++ b/Wabbajack/View Models/ModVM.cs @@ -64,7 +64,7 @@ namespace Wabbajack return default; } }) - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOnGuiThread() .Select(memStream => { if (memStream == null) return default; diff --git a/Wabbajack/View Models/SlideShow.cs b/Wabbajack/View Models/SlideShow.cs index 1797e42b..92b39917 100644 --- a/Wabbajack/View Models/SlideShow.cs +++ b/Wabbajack/View Models/SlideShow.cs @@ -108,7 +108,7 @@ namespace Wabbajack return query.Items.ElementAtOrDefault(index); }) .StartWith(default(ModVM)) - .ObserveOn(RxApp.MainThreadScheduler) + .ObserveOnGuiThread() .ToProperty(this, nameof(TargetMod)); // Mark interest and materialize image of target mod From e2fa5da973b0ba86c19d6a7d907adf6fbf32bf91 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Thu, 9 Jan 2020 22:27:59 -0600 Subject: [PATCH 2/2] Optimizations for CPU display updates --- Wabbajack/Extensions/DynamicDataExt.cs | 56 +++++++++++++++++++ Wabbajack/UI/UIUtils.cs | 24 +++++++- Wabbajack/View Models/CPUDisplayVM.cs | 31 ++++++++-- Wabbajack/View Models/Compilers/CompilerVM.cs | 28 ++-------- .../View Models/Installers/InstallerVM.cs | 26 ++------- Wabbajack/View Models/ModeSelectionVM.cs | 4 +- Wabbajack/Views/Common/CpuView.xaml | 12 ++-- Wabbajack/Wabbajack.csproj | 5 +- 8 files changed, 125 insertions(+), 61 deletions(-) create mode 100644 Wabbajack/Extensions/DynamicDataExt.cs diff --git a/Wabbajack/Extensions/DynamicDataExt.cs b/Wabbajack/Extensions/DynamicDataExt.cs new file mode 100644 index 00000000..ab4f12ea --- /dev/null +++ b/Wabbajack/Extensions/DynamicDataExt.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Text; +using System.Threading.Tasks; +using DynamicData; + +namespace Wabbajack +{ + public static class DynamicDataExt + { + public static IObservable> TransformAndCache( + this IObservable> obs, + Func onAdded, + Action, TCache> onUpdated) + { + var cache = new ChangeAwareCache(); + return obs + .Select(changeSet => + { + foreach (var change in changeSet) + { + switch (change.Reason) + { + case ChangeReason.Add: + case ChangeReason.Update: + case ChangeReason.Refresh: + var lookup = cache.Lookup(change.Key); + TCache val; + if (lookup.HasValue) + { + val = lookup.Value; + } + else + { + val = onAdded(change.Key, change.Current); + cache.Add(val, change.Key); + } + onUpdated(change, val); + break; + case ChangeReason.Remove: + cache.Remove(change.Key); + break; + case ChangeReason.Moved: + break; + default: + throw new NotImplementedException(); + } + } + return cache.CaptureChanges(); + }) + .Where(cs => cs.Count > 0); + } + } +} diff --git a/Wabbajack/UI/UIUtils.cs b/Wabbajack/UI/UIUtils.cs index 6c2de57c..4459543e 100644 --- a/Wabbajack/UI/UIUtils.cs +++ b/Wabbajack/UI/UIUtils.cs @@ -1,6 +1,10 @@ -using Microsoft.WindowsAPICodePack.Dialogs; +using DynamicData; +using DynamicData.Binding; +using Microsoft.WindowsAPICodePack.Dialogs; +using ReactiveUI; using System; using System.IO; +using System.Reactive.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -53,5 +57,23 @@ namespace Wabbajack.UI return ofd.FileName; return null; } + + public static IDisposable BindCpuStatus(IObservable status, ObservableCollectionExtended list) + { + return status.ObserveOn(RxApp.TaskpoolScheduler) + .ToObservableChangeSet(x => x.ID) + .Batch(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler) + .EnsureUniqueChanges() + .TransformAndCache( + onAdded: (key, cpu) => new CPUDisplayVM(cpu), + onUpdated: (change, vm) => vm.AbsorbStatus(change.Current)) + .AutoRefresh(x => x.IsWorking) + .AutoRefresh(x => x.StartTime) + .Filter(i => i.IsWorking && i.ID != WorkQueue.UnassignedCpuId) + .Sort(SortExpressionComparer.Ascending(s => s.StartTime)) + .ObserveOnGuiThread() + .Bind(list) + .Subscribe(); + } } } diff --git a/Wabbajack/View Models/CPUDisplayVM.cs b/Wabbajack/View Models/CPUDisplayVM.cs index 5c4d7938..4afbbbb0 100644 --- a/Wabbajack/View Models/CPUDisplayVM.cs +++ b/Wabbajack/View Models/CPUDisplayVM.cs @@ -3,23 +3,46 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using ReactiveUI.Fody.Helpers; using Wabbajack.Common; +using Wabbajack.Lib; namespace Wabbajack { - public class CPUDisplayVM + public class CPUDisplayVM : ViewModel { - public CPUStatus Status { get; set; } + [Reactive] + public int ID { get; set; } + [Reactive] public DateTime StartTime { get; set; } + [Reactive] + public bool IsWorking { get; set; } + [Reactive] + public string Msg { get; set; } + [Reactive] + public float ProgressPercent { get; set; } + + public CPUDisplayVM() + { + } + + public CPUDisplayVM(CPUStatus cpu) + { + AbsorbStatus(cpu); + } public void AbsorbStatus(CPUStatus cpu) { - bool starting = cpu.IsWorking && ((!Status?.IsWorking) ?? true); - Status = cpu; + bool starting = cpu.IsWorking && !IsWorking; if (starting) { StartTime = DateTime.Now; } + + ID = cpu.ID; + Msg = cpu.Msg; + ProgressPercent = cpu.ProgressPercent; + IsWorking = cpu.IsWorking; } } } diff --git a/Wabbajack/View Models/Compilers/CompilerVM.cs b/Wabbajack/View Models/Compilers/CompilerVM.cs index 0eb3dd33..2b390d13 100644 --- a/Wabbajack/View Models/Compilers/CompilerVM.cs +++ b/Wabbajack/View Models/Compilers/CompilerVM.cs @@ -1,4 +1,4 @@ -using DynamicData; +using DynamicData; using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -154,28 +154,10 @@ namespace Wabbajack resultSelector: (i, b) => i && b) .ObserveOnGuiThread()); - // Compile progress updates and populate ObservableCollection - Dictionary cpuDisplays = new Dictionary(); - this.WhenAny(x => x.Compiler.ActiveCompilation) - .SelectMany(c => c?.QueueStatus ?? Observable.Empty()) - .ObserveOn(RxApp.TaskpoolScheduler) - // Attach start times to incoming CPU items - .Scan( - new CPUDisplayVM(), - (_, cpu) => - { - var ret = cpuDisplays.TryCreate(cpu.ID); - ret.AbsorbStatus(cpu); - return ret; - }) - .ToObservableChangeSet(x => x.Status.ID) - .Batch(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler) - .EnsureUniqueChanges() - .Filter(i => i.Status.IsWorking && i.Status.ID != WorkQueue.UnassignedCpuId) - .Sort(SortExpressionComparer.Ascending(s => s.StartTime)) - .ObserveOnGuiThread() - .Bind(StatusList) - .Subscribe() + UIUtils.BindCpuStatus( + this.WhenAny(x => x.Compiler.ActiveCompilation) + .SelectMany(c => c?.QueueStatus ?? Observable.Empty()), + StatusList) .DisposeWith(CompositeDisposable); _percentCompleted = this.WhenAny(x => x.Compiler.ActiveCompilation) diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index 8fa96f86..d67e5913 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -299,28 +299,10 @@ namespace Wabbajack }) .ToProperty(this, nameof(ProgressTitle)); - Dictionary cpuDisplays = new Dictionary(); - // Compile progress updates and populate ObservableCollection - this.WhenAny(x => x.Installer.ActiveInstallation) - .SelectMany(c => c?.QueueStatus ?? Observable.Empty()) - .ObserveOn(RxApp.TaskpoolScheduler) - // Attach start times to incoming CPU items - .Scan( - new CPUDisplayVM(), - (_, cpu) => - { - var ret = cpuDisplays.TryCreate(cpu.ID); - ret.AbsorbStatus(cpu); - return ret; - }) - .ToObservableChangeSet(x => x.Status.ID) - .Batch(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler) - .EnsureUniqueChanges() - .Filter(i => i.Status.IsWorking && i.Status.ID != WorkQueue.UnassignedCpuId) - .ObserveOnGuiThread() - .Sort(SortExpressionComparer.Ascending(s => s.StartTime)) - .Bind(StatusList) - .Subscribe() + UIUtils.BindCpuStatus( + this.WhenAny(x => x.Installer.ActiveInstallation) + .SelectMany(c => c?.QueueStatus ?? Observable.Empty()), + StatusList) .DisposeWith(CompositeDisposable); BeginCommand = ReactiveCommand.CreateFromTask( diff --git a/Wabbajack/View Models/ModeSelectionVM.cs b/Wabbajack/View Models/ModeSelectionVM.cs index cb8b3f8f..450ec28c 100644 --- a/Wabbajack/View Models/ModeSelectionVM.cs +++ b/Wabbajack/View Models/ModeSelectionVM.cs @@ -1,6 +1,6 @@ -using Alphaleonis.Win32.Filesystem; -using ReactiveUI; +using ReactiveUI; using ReactiveUI.Fody.Helpers; +using System.IO; using System.Linq; using System.Reactive.Linq; using System.Windows.Input; diff --git a/Wabbajack/Views/Common/CpuView.xaml b/Wabbajack/Views/Common/CpuView.xaml index 2934f379..c45e729f 100644 --- a/Wabbajack/Views/Common/CpuView.xaml +++ b/Wabbajack/Views/Common/CpuView.xaml @@ -1,4 +1,4 @@ - + Opacity="{Binding ProgressPercent, Mode=OneWay}" + Value="{Binding ProgressPercent, Mode=OneWay}" /> + Value="{Binding ProgressPercent, Mode=OneWay}" /> + ToolTip="{Binding Msg, Mode=OneWay}" /> diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj index cdfeb291..cf0968af 100644 --- a/Wabbajack/Wabbajack.csproj +++ b/Wabbajack/Wabbajack.csproj @@ -172,6 +172,7 @@ MSBuild:Compile Designer + @@ -584,8 +585,6 @@ - - - + \ No newline at end of file