From ff2010134cc399542fdd729a1004f5eb10d2c527 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Sat, 12 Oct 2019 16:04:14 -0500 Subject: [PATCH] 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 --- Wabbajack/AppState.cs | 73 ++++++++++++--------------- Wabbajack/Extensions/ReactiveUIExt.cs | 33 ++++++++++++ Wabbajack/UI/MainWindow.xaml | 2 +- Wabbajack/UI/MainWindow.xaml.cs | 1 - Wabbajack/UI/SlideShow.cs | 11 ++-- 5 files changed, 68 insertions(+), 52 deletions(-) diff --git a/Wabbajack/AppState.cs b/Wabbajack/AppState.cs index cee6e7d6..bcd5a190 100644 --- a/Wabbajack/AppState.cs +++ b/Wabbajack/AppState.cs @@ -22,18 +22,17 @@ using Wabbajack.NexusApi; using Wabbajack.UI; using DynamicData; using DynamicData.Binding; +using System.Reactive; namespace Wabbajack { public enum TaskMode { INSTALLING, BUILDING } - internal class AppState : ViewModel, IDataErrorInfo + public class AppState : ViewModel, IDataErrorInfo { public const bool GcCollect = true; private SlideShow _slideShow; - public bool installing = false; - private string _mo2Folder; private readonly BitmapImage _wabbajackLogo = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner.png"); @@ -59,7 +58,7 @@ namespace Wabbajack private BitmapImage _SplashScreenImage; public BitmapImage SplashScreenImage { get => _SplashScreenImage; set => this.RaiseAndSetIfChanged(ref _SplashScreenImage, value); } - + private BitmapImage _NextIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.next.png"); public BitmapImage NextIcon { get => _NextIcon; set => this.RaiseAndSetIfChanged(ref _NextIcon, value); } @@ -69,6 +68,9 @@ namespace Wabbajack private string _HTMLReport; 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 public IReactiveCommand ChangePathCommand => ReactiveCommand.Create(ExecuteChangePath); public IReactiveCommand ChangeDownloadPathCommand => ReactiveCommand.Create(ExecuteChangeDownloadPath); @@ -77,7 +79,10 @@ namespace Wabbajack public IReactiveCommand VisitNexusSiteCommand => ReactiveCommand.Create(VisitNexusSite); public IReactiveCommand OpenReadmeCommand { get; } 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 _slideshowCommandTriggeredSubject = new Subject(); + public ReactiveCommand SlideShowNextItemCommand => ReactiveCommand.Create(() => _slideshowCommandTriggeredSubject.OnNext(Unit.Default)); public AppState() { @@ -121,7 +126,6 @@ namespace Wabbajack .DisposeWith(this.CompositeDisposable); _slideShow = new SlideShow(this, true); - this.SlideShowNextItemCommand = ReactiveCommand.Create(_slideShow.UpdateSlideShowItem); // Update splashscreen when modlist changes Observable.CombineLatest( @@ -170,10 +174,24 @@ namespace Wabbajack .Subscribe(bitmap => this.SplashScreenImage = bitmap) .DisposeWith(this.CompositeDisposable); - // Trigger a slideshow update if enabled - this.WhenAny(x => x.EnableSlideShow) - .Skip(1) // Don't fire initially - .WhenAny(enable => enable) + /// Wire slideshow updates + // Merge all the sources that trigger a slideshow update + Observable.Merge( + // 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()) .DisposeWith(this.CompositeDisposable); @@ -192,17 +210,8 @@ namespace Wabbajack .Bind(this.Status) .Subscribe() .DisposeWith(this.CompositeDisposable); - - slideshowThread = new Thread(UpdateLoop) - { - Priority = ThreadPriority.BelowNormal, - IsBackground = true - }; - slideshowThread.Start(); } - public DateTime lastSlideShowUpdate = new DateTime(); - public ObservableCollection Log { get; } = new ObservableCollection(); private string _Location; @@ -316,8 +325,7 @@ namespace Wabbajack private string _SplashScreenSummary; public string SplashScreenSummary { get => _SplashScreenSummary; set => this.RaiseAndSetIfChanged(ref _SplashScreenSummary, value); } private bool _splashShowNSFW = false; - public bool SplashShowNSFW { get => _splashShowNSFW; set => this.RaiseAndSetIfChanged(ref _splashShowNSFW, value); } - private readonly Thread slideshowThread = null; + public bool SplashShowNSFW { get => _splashShowNSFW; set => this.RaiseAndSetIfChanged(ref _splashShowNSFW, value); } public string Error => "Error"; @@ -355,23 +363,6 @@ namespace Wabbajack 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) { Application.Current.Dispatcher.Invoke(() => Log.Add(msg)); @@ -425,7 +416,7 @@ namespace Wabbajack UIReady = false; if (Mode == TaskMode.INSTALLING) { - installing = true; + this.Installing = true; var installer = new Installer(this.ModListPath, this.ModList, Location) { DownloadFolder = DownloadLocation @@ -447,9 +438,7 @@ namespace Wabbajack finally { UIReady = true; - Running = false; - installing = false; - slideshowThread.Abort(); + this.Installing = false; } }) { diff --git a/Wabbajack/Extensions/ReactiveUIExt.cs b/Wabbajack/Extensions/ReactiveUIExt.cs index 171c80ed..7274bca3 100644 --- a/Wabbajack/Extensions/ReactiveUIExt.cs +++ b/Wabbajack/Extensions/ReactiveUIExt.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Linq.Expressions; +using System.Reactive; using System.Reactive.Linq; using DynamicData; using DynamicData.Kernel; @@ -29,6 +30,38 @@ namespace Wabbajack return source.ObserveOn(RxApp.MainThreadScheduler); } + public static IObservable Unit(this IObservable source) + { + return source.Select(_ => System.Reactive.Unit.Default); + } + + /// + /// 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. + /// + /// + /// Source observable to subscribe to if on + /// On/Off signal of whether to subscribe to source observable + /// Observable that publishes data from source, if the switch is on. + public static IObservable FilterSwitch(this IObservable source, IObservable filterSwitch) + { + return filterSwitch + .DistinctUntilChanged() + .Select(on => + { + if (on) + { + return source; + } + else + { + return Observable.Empty(); + } + }) + .Switch(); + } + /// 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. #region Dynamic Data EnsureUniqueChanges diff --git a/Wabbajack/UI/MainWindow.xaml b/Wabbajack/UI/MainWindow.xaml index e6c20a8c..f17f5bba 100644 --- a/Wabbajack/UI/MainWindow.xaml +++ b/Wabbajack/UI/MainWindow.xaml @@ -149,7 +149,7 @@ Height="30" HorizontalAlignment="Right" Command="{Binding SlideShowNextItemCommand}" - ToolTip="Spamming this button will result in problems"> + ToolTip="Skip to next slide"> diff --git a/Wabbajack/UI/MainWindow.xaml.cs b/Wabbajack/UI/MainWindow.xaml.cs index 356f52c8..82fd12f2 100644 --- a/Wabbajack/UI/MainWindow.xaml.cs +++ b/Wabbajack/UI/MainWindow.xaml.cs @@ -68,7 +68,6 @@ namespace Wabbajack MessageBoxImage.Error); Dispatcher.Invoke(() => { - context.Running = false; ExitWhenClosing = false; var window = new ModeSelectionWindow { diff --git a/Wabbajack/UI/SlideShow.cs b/Wabbajack/UI/SlideShow.cs index cd837c51..69e0cd64 100644 --- a/Wabbajack/UI/SlideShow.cs +++ b/Wabbajack/UI/SlideShow.cs @@ -74,7 +74,7 @@ namespace Wabbajack.UI public void UpdateSlideShowItem() { - if (!_appState.EnableSlideShow || !_appState.installing || SlidesQueue.Count==0) return; + if (SlidesQueue.Count == 0) return; var slide = SlidesQueue.Peek(); while (CachedSlides.Count >= MaxCacheSize) @@ -98,11 +98,8 @@ namespace Wabbajack.UI _appState.SplashScreenImage = _appState._noneImage; 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; @@ -111,8 +108,6 @@ namespace Wabbajack.UI _appState._nexusSiteURL = slide.ModURL; } - _appState.lastSlideShowUpdate = DateTime.Now; - SlidesQueue.Dequeue(); QueueRandomSlide(false, true); }