using DynamicData; using DynamicData.Binding; using ReactiveUI; using ReactiveUI.Fody.Helpers; using System; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Windows.Media.Imaging; using Wabbajack.Common; using Wabbajack.Common.StatusFeed; using Wabbajack.Lib; namespace Wabbajack { public class CompilerVM : ViewModel { public MainWindowVM MWVM { get; } private readonly ObservableAsPropertyHelper _image; public BitmapImage Image => _image.Value; [Reactive] public ModManager SelectedCompilerType { get; set; } private readonly ObservableAsPropertyHelper _compiler; public ISubCompilerVM Compiler => _compiler.Value; private readonly ObservableAsPropertyHelper _currentModlistSettings; public ModlistSettingsEditorVM CurrentModlistSettings => _currentModlistSettings.Value; private readonly ObservableAsPropertyHelper _compiling; public bool Compiling => _compiling.Value; private readonly ObservableAsPropertyHelper _percentCompleted; public float PercentCompleted => _percentCompleted.Value; public ObservableCollectionExtended StatusList { get; } = new ObservableCollectionExtended(); public ObservableCollectionExtended Log => MWVM.Log; public IReactiveCommand BackCommand { get; } private readonly ObservableAsPropertyHelper _ActiveGlobalUserIntervention; public IUserIntervention ActiveGlobalUserIntervention => _ActiveGlobalUserIntervention.Value; public CompilerVM(MainWindowVM mainWindowVM) { MWVM = mainWindowVM; // Load settings CompilerSettings settings = MWVM.Settings.Compiler; SelectedCompilerType = settings.LastCompiledModManager; MWVM.Settings.SaveSignal .Subscribe(_ => { settings.LastCompiledModManager = SelectedCompilerType; }) .DisposeWith(CompositeDisposable); // Swap to proper sub VM based on selected type _compiler = this.WhenAny(x => x.SelectedCompilerType) // Delay so the initial VM swap comes in immediately, subVM comes right after .DelayInitial(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) .Select(type => { switch (type) { case ModManager.MO2: return new MO2CompilerVM(this); case ModManager.Vortex: return new VortexCompilerVM(this); default: return null; } }) // Unload old VM .Pairwise() .Do(pair => { pair.Previous?.Unload(); }) .Select(p => p.Current) .ToProperty(this, nameof(Compiler)); // Let sub VM determine what settings we're displaying and when _currentModlistSettings = this.WhenAny(x => x.Compiler.ModlistSettings) .ToProperty(this, nameof(CurrentModlistSettings)); _image = this.WhenAny(x => x.CurrentModlistSettings.ImagePath.TargetPath) // Throttle so that it only loads image after any sets of swaps have completed .Throttle(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler) .DistinctUntilChanged() .Select(path => { if (string.IsNullOrWhiteSpace(path)) return UIUtils.BitmapImageFromResource("Resources/Wabba_Mouth_No_Text.png"); if (UIUtils.TryGetBitmapImageFromFile(path, out var image)) { return image; } return null; }) .ToProperty(this, nameof(Image)); _compiling = this.WhenAny(x => x.Compiler.ActiveCompilation) .Select(compilation => compilation != null) .ObserveOnGuiThread() .ToProperty(this, nameof(Compiling)); BackCommand = ReactiveCommand.Create( execute: () => mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM, canExecute: this.WhenAny(x => x.Compiling) .Select(x => !x)); // Compile progress updates and populate ObservableCollection this.WhenAny(x => x.Compiler.ActiveCompilation) .SelectMany(c => c?.QueueStatus ?? Observable.Empty()) .ObserveOn(RxApp.TaskpoolScheduler) .ToObservableChangeSet(x => x.ID) .Batch(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler) .EnsureUniqueChanges() .Filter(i => i.IsWorking) .ObserveOn(RxApp.MainThreadScheduler) .Sort(SortExpressionComparer.Ascending(s => s.ID), SortOptimisations.ComparesImmutableValuesOnly) .Bind(StatusList) .Subscribe() .DisposeWith(CompositeDisposable); _percentCompleted = this.WhenAny(x => x.Compiler.ActiveCompilation) .StartWith(default(ACompiler)) .Pairwise() .Select(c => { if (c.Current == null) { return Observable.Return(c.Previous == null ? 0f : 1f); } return c.Current.PercentCompleted; }) .Switch() .Debounce(TimeSpan.FromMilliseconds(25)) .ToProperty(this, nameof(PercentCompleted)); // Listen for user interventions, and compile a dynamic list of all unhandled ones var activeInterventions = this.WhenAny(x => x.Compiler.ActiveCompilation) .SelectMany(c => c?.LogMessages ?? Observable.Empty()) .WhereCastable() .ToObservableChangeSet() .AutoRefresh(i => i.Handled) .Filter(i => !i.Handled) .AsObservableList(); // Find the top intervention /w no CPU ID to be marked as "global" _ActiveGlobalUserIntervention = activeInterventions.Connect() .Filter(x => x.CpuID == WorkQueue.UnassignedCpuId) .QueryWhenChanged(query => query.FirstOrDefault()) .ObserveOnGuiThread() .ToProperty(this, nameof(ActiveGlobalUserIntervention)); } } }