Slideshow update thread removed in favor of Rx

Will want to replace the throttle /w a debounce call.
Also will want to remove unnecessary subject once command IsExecuting callbacks are researched
This commit is contained in:
Justin Swanson 2019-10-12 16:04:14 -05:00
parent c808f9a4ed
commit ff2010134c
5 changed files with 68 additions and 52 deletions

View File

@ -22,18 +22,17 @@ using Wabbajack.NexusApi;
using Wabbajack.UI; using Wabbajack.UI;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using System.Reactive;
namespace Wabbajack namespace Wabbajack
{ {
public enum TaskMode { INSTALLING, BUILDING } public enum TaskMode { INSTALLING, BUILDING }
internal class AppState : ViewModel, IDataErrorInfo public class AppState : ViewModel, IDataErrorInfo
{ {
public const bool GcCollect = true; public const bool GcCollect = true;
private SlideShow _slideShow; private SlideShow _slideShow;
public bool installing = false;
private string _mo2Folder; private string _mo2Folder;
private readonly BitmapImage _wabbajackLogo = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner.png"); private readonly BitmapImage _wabbajackLogo = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner.png");
@ -59,7 +58,7 @@ namespace Wabbajack
private BitmapImage _SplashScreenImage; private BitmapImage _SplashScreenImage;
public BitmapImage SplashScreenImage { get => _SplashScreenImage; set => this.RaiseAndSetIfChanged(ref _SplashScreenImage, value); } public BitmapImage SplashScreenImage { get => _SplashScreenImage; set => this.RaiseAndSetIfChanged(ref _SplashScreenImage, value); }
private BitmapImage _NextIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.next.png"); private BitmapImage _NextIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.next.png");
public BitmapImage NextIcon { get => _NextIcon; set => this.RaiseAndSetIfChanged(ref _NextIcon, value); } public BitmapImage NextIcon { get => _NextIcon; set => this.RaiseAndSetIfChanged(ref _NextIcon, value); }
@ -69,6 +68,9 @@ namespace Wabbajack
private string _HTMLReport; private string _HTMLReport;
public string HTMLReport { get => _HTMLReport; set => this.RaiseAndSetIfChanged(ref _HTMLReport, value); } public string HTMLReport { get => _HTMLReport; set => this.RaiseAndSetIfChanged(ref _HTMLReport, value); }
private bool _Installing;
public bool Installing { get => _Installing; set => this.RaiseAndSetIfChanged(ref _Installing, value); }
// Command properties // Command properties
public IReactiveCommand ChangePathCommand => ReactiveCommand.Create(ExecuteChangePath); public IReactiveCommand ChangePathCommand => ReactiveCommand.Create(ExecuteChangePath);
public IReactiveCommand ChangeDownloadPathCommand => ReactiveCommand.Create(ExecuteChangeDownloadPath); public IReactiveCommand ChangeDownloadPathCommand => ReactiveCommand.Create(ExecuteChangeDownloadPath);
@ -77,7 +79,10 @@ namespace Wabbajack
public IReactiveCommand VisitNexusSiteCommand => ReactiveCommand.Create(VisitNexusSite); public IReactiveCommand VisitNexusSiteCommand => ReactiveCommand.Create(VisitNexusSite);
public IReactiveCommand OpenReadmeCommand { get; } public IReactiveCommand OpenReadmeCommand { get; }
public IReactiveCommand OpenModListPropertiesCommand => ReactiveCommand.Create(OpenModListProperties); public IReactiveCommand OpenModListPropertiesCommand => ReactiveCommand.Create(OpenModListProperties);
public IReactiveCommand SlideShowNextItemCommand { get; } // ToDo
// This subject is not desirable. Need to research why command's IsExecuting observable not firing, as we'd prefer to hook onto that instead
private Subject<Unit> _slideshowCommandTriggeredSubject = new Subject<Unit>();
public ReactiveCommand<Unit, Unit> SlideShowNextItemCommand => ReactiveCommand.Create(() => _slideshowCommandTriggeredSubject.OnNext(Unit.Default));
public AppState() public AppState()
{ {
@ -121,7 +126,6 @@ namespace Wabbajack
.DisposeWith(this.CompositeDisposable); .DisposeWith(this.CompositeDisposable);
_slideShow = new SlideShow(this, true); _slideShow = new SlideShow(this, true);
this.SlideShowNextItemCommand = ReactiveCommand.Create(_slideShow.UpdateSlideShowItem);
// Update splashscreen when modlist changes // Update splashscreen when modlist changes
Observable.CombineLatest( Observable.CombineLatest(
@ -170,10 +174,24 @@ namespace Wabbajack
.Subscribe(bitmap => this.SplashScreenImage = bitmap) .Subscribe(bitmap => this.SplashScreenImage = bitmap)
.DisposeWith(this.CompositeDisposable); .DisposeWith(this.CompositeDisposable);
// Trigger a slideshow update if enabled /// Wire slideshow updates
this.WhenAny(x => x.EnableSlideShow) // Merge all the sources that trigger a slideshow update
.Skip(1) // Don't fire initially Observable.Merge(
.WhenAny(enable => enable) // If the natural timer fires
Observable.Interval(TimeSpan.FromSeconds(10)).Unit(),
// If user requests one manually
_slideshowCommandTriggeredSubject)
// When enabled, fire an initial signal
.StartWith(Unit.Default)
// Only subscribe to slideshow triggers if enabled and installing
.FilterSwitch(
Observable.CombineLatest(
this.WhenAny(x => x.EnableSlideShow),
this.WhenAny(x => x.Installing),
resultSelector: (enabled, installing) => enabled && installing))
// Don't ever update more than once every half second. ToDo: Update to debounce
.Throttle(TimeSpan.FromMilliseconds(500), RxApp.MainThreadScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => _slideShow.UpdateSlideShowItem()) .Subscribe(_ => _slideShow.UpdateSlideShowItem())
.DisposeWith(this.CompositeDisposable); .DisposeWith(this.CompositeDisposable);
@ -192,17 +210,8 @@ namespace Wabbajack
.Bind(this.Status) .Bind(this.Status)
.Subscribe() .Subscribe()
.DisposeWith(this.CompositeDisposable); .DisposeWith(this.CompositeDisposable);
slideshowThread = new Thread(UpdateLoop)
{
Priority = ThreadPriority.BelowNormal,
IsBackground = true
};
slideshowThread.Start();
} }
public DateTime lastSlideShowUpdate = new DateTime();
public ObservableCollection<string> Log { get; } = new ObservableCollection<string>(); public ObservableCollection<string> Log { get; } = new ObservableCollection<string>();
private string _Location; private string _Location;
@ -316,8 +325,7 @@ namespace Wabbajack
private string _SplashScreenSummary; private string _SplashScreenSummary;
public string SplashScreenSummary { get => _SplashScreenSummary; set => this.RaiseAndSetIfChanged(ref _SplashScreenSummary, value); } public string SplashScreenSummary { get => _SplashScreenSummary; set => this.RaiseAndSetIfChanged(ref _SplashScreenSummary, value); }
private bool _splashShowNSFW = false; private bool _splashShowNSFW = false;
public bool SplashShowNSFW { get => _splashShowNSFW; set => this.RaiseAndSetIfChanged(ref _splashShowNSFW, value); } public bool SplashShowNSFW { get => _splashShowNSFW; set => this.RaiseAndSetIfChanged(ref _splashShowNSFW, value); }
private readonly Thread slideshowThread = null;
public string Error => "Error"; public string Error => "Error";
@ -355,23 +363,6 @@ namespace Wabbajack
return validationMessage; return validationMessage;
} }
private void UpdateLoop()
{
while (Running)
{
if (_slideShow.SlidesQueue.Any())
{
if (DateTime.Now - lastSlideShowUpdate > TimeSpan.FromSeconds(10))
{
_slideShow.UpdateSlideShowItem();
}
}
Thread.Sleep(1000);
}
}
public bool Running { get; set; } = true;
public void LogMsg(string msg) public void LogMsg(string msg)
{ {
Application.Current.Dispatcher.Invoke(() => Log.Add(msg)); Application.Current.Dispatcher.Invoke(() => Log.Add(msg));
@ -425,7 +416,7 @@ namespace Wabbajack
UIReady = false; UIReady = false;
if (Mode == TaskMode.INSTALLING) if (Mode == TaskMode.INSTALLING)
{ {
installing = true; this.Installing = true;
var installer = new Installer(this.ModListPath, this.ModList, Location) var installer = new Installer(this.ModListPath, this.ModList, Location)
{ {
DownloadFolder = DownloadLocation DownloadFolder = DownloadLocation
@ -447,9 +438,7 @@ namespace Wabbajack
finally finally
{ {
UIReady = true; UIReady = true;
Running = false; this.Installing = false;
installing = false;
slideshowThread.Abort();
} }
}) })
{ {

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using DynamicData; using DynamicData;
using DynamicData.Kernel; using DynamicData.Kernel;
@ -29,6 +30,38 @@ namespace Wabbajack
return source.ObserveOn(RxApp.MainThreadScheduler); return source.ObserveOn(RxApp.MainThreadScheduler);
} }
public static IObservable<Unit> Unit<T>(this IObservable<T> source)
{
return source.Select(_ => System.Reactive.Unit.Default);
}
/// <summary>
/// Convenience operator to subscribe to the source observable, only when a second "switch" observable is on.
/// When the switch is on, the source will be subscribed to, and its updates passed through.
/// When the switch is off, the subscription to the source observable will be stopped, and no signal will be published.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">Source observable to subscribe to if on</param>
/// <param name="filterSwitch">On/Off signal of whether to subscribe to source observable</param>
/// <returns>Observable that publishes data from source, if the switch is on.</returns>
public static IObservable<T> FilterSwitch<T>(this IObservable<T> source, IObservable<bool> filterSwitch)
{
return filterSwitch
.DistinctUntilChanged()
.Select(on =>
{
if (on)
{
return source;
}
else
{
return Observable.Empty<T>();
}
})
.Switch();
}
/// These snippets were provided by RolandPheasant (author of DynamicData) /// These snippets were provided by RolandPheasant (author of DynamicData)
/// They'll be going into the official library at some point, but are here for now. /// They'll be going into the official library at some point, but are here for now.
#region Dynamic Data EnsureUniqueChanges #region Dynamic Data EnsureUniqueChanges

View File

@ -149,7 +149,7 @@
Height="30" Height="30"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Command="{Binding SlideShowNextItemCommand}" Command="{Binding SlideShowNextItemCommand}"
ToolTip="Spamming this button will result in problems"> ToolTip="Skip to next slide">
<DockPanel> <DockPanel>
<Image Source="{Binding NextIcon}" Stretch="Fill" /> <Image Source="{Binding NextIcon}" Stretch="Fill" />
</DockPanel> </DockPanel>

View File

@ -68,7 +68,6 @@ namespace Wabbajack
MessageBoxImage.Error); MessageBoxImage.Error);
Dispatcher.Invoke(() => Dispatcher.Invoke(() =>
{ {
context.Running = false;
ExitWhenClosing = false; ExitWhenClosing = false;
var window = new ModeSelectionWindow var window = new ModeSelectionWindow
{ {

View File

@ -74,7 +74,7 @@ namespace Wabbajack.UI
public void UpdateSlideShowItem() public void UpdateSlideShowItem()
{ {
if (!_appState.EnableSlideShow || !_appState.installing || SlidesQueue.Count==0) return; if (SlidesQueue.Count == 0) return;
var slide = SlidesQueue.Peek(); var slide = SlidesQueue.Peek();
while (CachedSlides.Count >= MaxCacheSize) while (CachedSlides.Count >= MaxCacheSize)
@ -98,11 +98,8 @@ namespace Wabbajack.UI
_appState.SplashScreenImage = _appState._noneImage; _appState.SplashScreenImage = _appState._noneImage;
if (slide.ImageURL != null && slide.Image != null) if (slide.ImageURL != null && slide.Image != null)
{ {
System.Windows.Application.Current.Dispatcher.Invoke(() => if (!CachedSlides.ContainsKey(slide.ModID)) return;
{ _appState.SplashScreenImage = slide.Image;
if (!CachedSlides.ContainsKey(slide.ModID)) return;
_appState.SplashScreenImage = slide.Image;
});
} }
_appState.SplashScreenModName = slide.ModName; _appState.SplashScreenModName = slide.ModName;
@ -111,8 +108,6 @@ namespace Wabbajack.UI
_appState._nexusSiteURL = slide.ModURL; _appState._nexusSiteURL = slide.ModURL;
} }
_appState.lastSlideShowUpdate = DateTime.Now;
SlidesQueue.Dequeue(); SlidesQueue.Dequeue();
QueueRandomSlide(false, true); QueueRandomSlide(false, true);
} }