Optimizations for CPU display updates

This commit is contained in:
Justin Swanson 2020-01-09 22:27:59 -06:00
parent 36a37a04a9
commit e2fa5da973
8 changed files with 125 additions and 61 deletions

View File

@ -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<IChangeSet<TCache, TKey>> TransformAndCache<TObject, TKey, TCache>(
this IObservable<IChangeSet<TObject, TKey>> obs,
Func<TKey, TObject, TCache> onAdded,
Action<Change<TObject, TKey>, TCache> onUpdated)
{
var cache = new ChangeAwareCache<TCache, TKey>();
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);
}
}
}

View File

@ -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<CPUStatus> status, ObservableCollectionExtended<CPUDisplayVM> 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<CPUDisplayVM>.Ascending(s => s.StartTime))
.ObserveOnGuiThread()
.Bind(list)
.Subscribe();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<int, CPUDisplayVM> cpuDisplays = new Dictionary<int, CPUDisplayVM>();
this.WhenAny(x => x.Compiler.ActiveCompilation)
.SelectMany(c => c?.QueueStatus ?? Observable.Empty<CPUStatus>())
.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<CPUDisplayVM>.Ascending(s => s.StartTime))
.ObserveOnGuiThread()
.Bind(StatusList)
.Subscribe()
UIUtils.BindCpuStatus(
this.WhenAny(x => x.Compiler.ActiveCompilation)
.SelectMany(c => c?.QueueStatus ?? Observable.Empty<CPUStatus>()),
StatusList)
.DisposeWith(CompositeDisposable);
_percentCompleted = this.WhenAny(x => x.Compiler.ActiveCompilation)

View File

@ -299,28 +299,10 @@ namespace Wabbajack
})
.ToProperty(this, nameof(ProgressTitle));
Dictionary<int, CPUDisplayVM> cpuDisplays = new Dictionary<int, CPUDisplayVM>();
// Compile progress updates and populate ObservableCollection
this.WhenAny(x => x.Installer.ActiveInstallation)
.SelectMany(c => c?.QueueStatus ?? Observable.Empty<CPUStatus>())
.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<CPUDisplayVM>.Ascending(s => s.StartTime))
.Bind(StatusList)
.Subscribe()
UIUtils.BindCpuStatus(
this.WhenAny(x => x.Installer.ActiveInstallation)
.SelectMany(c => c?.QueueStatus ?? Observable.Empty<CPUStatus>()),
StatusList)
.DisposeWith(CompositeDisposable);
BeginCommand = ReactiveCommand.CreateFromTask(

View File

@ -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;

View File

@ -1,4 +1,4 @@
<UserControl
<UserControl
x:Class="Wabbajack.CpuView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@ -25,22 +25,22 @@
BorderThickness="0"
Foreground="{StaticResource DarkPrimaryVariantBrush}"
Maximum="1"
Opacity="{Binding Status.ProgressPercent, Mode=OneWay}"
Value="{Binding Status.ProgressPercent, Mode=OneWay}" />
Opacity="{Binding ProgressPercent, Mode=OneWay}"
Value="{Binding ProgressPercent, Mode=OneWay}" />
<Grid Height="1" VerticalAlignment="Bottom">
<mahapps:MetroProgressBar
Background="Transparent"
BorderThickness="0"
Foreground="{StaticResource DarkSecondaryBrush}"
Maximum="1"
Value="{Binding Status.ProgressPercent, Mode=OneWay}" />
Value="{Binding ProgressPercent, Mode=OneWay}" />
</Grid>
<TextBlock
Margin="0,0,0,2"
Text="{Binding Status.Msg}"
Text="{Binding Msg, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
ToolTip="{Binding Status.Msg}" />
ToolTip="{Binding Msg, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

View File

@ -172,6 +172,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Extensions\DynamicDataExt.cs" />
<Compile Include="UI\FilePickerVM.cs" />
<Compile Include="UI\UIUtils.cs" />
<Compile Include="Util\SystemParametersConstructor.cs" />
@ -584,8 +585,6 @@
<ItemGroup>
<SplashScreen Include="Resources\Wabba_Mouth_Small.png" />
</ItemGroup>
<ItemGroup>
<Folder Include="UserInterventions\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>