diff --git a/Wabbajack.Common/Extensions/RxExt.cs b/Wabbajack.Common/Extensions/RxExt.cs
index bdefd987..0a1b643e 100644
--- a/Wabbajack.Common/Extensions/RxExt.cs
+++ b/Wabbajack.Common/Extensions/RxExt.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
@@ -67,7 +67,7 @@ namespace Wabbajack
/// On/Off signal of whether to subscribe to source observable
/// Value to fire when switching off
/// Observable that publishes data from source, if the switch is on.
- public static IObservable FilterSwitch(this IObservable source, IObservable filterSwitch, T valueWhenOff)
+ public static IObservable FlowSwitch(this IObservable source, IObservable filterSwitch, T valueWhenOff)
{
return filterSwitch
.DistinctUntilChanged()
@@ -226,5 +226,21 @@ namespace Wabbajack
.Select(_ => true)
.StartWith(false));
}
+
+ public static IObservable DisposeOld(this IObservable source)
+ where T : IDisposable
+ {
+ return source
+ .StartWith(default(T))
+ .Pairwise()
+ .Do(x =>
+ {
+ if (x.Previous != null)
+ {
+ x.Previous.Dispose();
+ }
+ })
+ .Select(x => x.Current);
+ }
}
}
diff --git a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
index 78128111..d993df53 100644
--- a/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
+++ b/Wabbajack/View Models/Compilers/MO2CompilerVM.cs
@@ -143,7 +143,7 @@ namespace Wabbajack
.DisposeWith(CompositeDisposable);
// If Mo2 folder changes and download location is empty, set it for convenience
- (this).WhenAny(x => x.Mo2Folder)
+ this.WhenAny(x => x.Mo2Folder)
.DelayInitial(TimeSpan.FromMilliseconds(100))
.Where(x => Directory.Exists(x))
.FlowSwitch(
diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs
index 29d262e4..6d229346 100644
--- a/Wabbajack/View Models/Installers/InstallerVM.cs
+++ b/Wabbajack/View Models/Installers/InstallerVM.cs
@@ -185,9 +185,19 @@ namespace Wabbajack
if (!File.Exists(modListPath)) return default(ModListVM);
return new ModListVM(modListPath);
})
+ .DisposeOld()
.ObserveOnGuiThread()
.StartWith(default(ModListVM))
.ToProperty(this, nameof(ModList));
+
+ // Force GC collect when modlist changes, just to make sure we clean up any loose large items immediately
+ this.WhenAny(x => x.ModList)
+ .Delay(TimeSpan.FromMilliseconds(50))
+ .Subscribe(x =>
+ {
+ GC.Collect();
+ });
+
_LoadingModlist = Observable.Merge(
// When active path changes, mark as loading
activePath
diff --git a/Wabbajack/View Models/ModListGalleryVM.cs b/Wabbajack/View Models/ModListGalleryVM.cs
index 84ff0a96..6c95081f 100644
--- a/Wabbajack/View Models/ModListGalleryVM.cs
+++ b/Wabbajack/View Models/ModListGalleryVM.cs
@@ -21,30 +21,43 @@ namespace Wabbajack
public ObservableCollectionExtended ModLists { get; } = new ObservableCollectionExtended();
- public IReactiveCommand RefreshCommand { get; }
-
private int missingHashFallbackCounter;
public ModListGalleryVM(MainWindowVM mainWindowVM)
: base(mainWindowVM)
{
MWVM = mainWindowVM;
- RefreshCommand = ReactiveCommand.Create(() => { });
- RefreshCommand.StartingExecution()
- .StartWith(Unit.Default)
+ Observable.Return(Unit.Default)
.ObserveOn(RxApp.TaskpoolScheduler)
.SelectTask(async _ =>
{
return (await ModlistMetadata.LoadFromGithub())
.AsObservableChangeSet(x => x.DownloadMetadata?.Hash ?? $"Fallback{missingHashFallbackCounter++}");
})
+ // Unsubscribe and release when not active
+ .FlowSwitch(
+ this.WhenAny(x => x.IsActive),
+ valueWhenOff: Observable.Return(ChangeSet.Empty))
+ // Convert to VM and bind to resulting list
.Switch()
.ObserveOnGuiThread()
.Transform(m => new ModListMetadataVM(this, m))
+ .DisposeMany()
.Bind(ModLists)
.Subscribe()
.DisposeWith(CompositeDisposable);
+
+ // Extra GC when navigating away, just to immediately clean up modlist metadata
+ this.WhenAny(x => x.IsActive)
+ .Where(x => !x)
+ .Skip(1)
+ .Delay(TimeSpan.FromMilliseconds(50))
+ .Subscribe(_ =>
+ {
+ GC.Collect();
+ })
+ .DisposeWith(CompositeDisposable);
}
}
}
diff --git a/Wabbajack/View Models/ModListVM.cs b/Wabbajack/View Models/ModListVM.cs
index 94962a4e..4bdb4009 100644
--- a/Wabbajack/View Models/ModListVM.cs
+++ b/Wabbajack/View Models/ModListVM.cs
@@ -13,7 +13,7 @@ namespace Wabbajack
{
public class ModListVM : ViewModel
{
- public ModList SourceModList { get; }
+ public ModList SourceModList { get; private set; }
public Exception Error { get; }
public string ModListPath { get; }
public string Name => SourceModList?.Name;
@@ -123,5 +123,13 @@ namespace Wabbajack
}
}
}
+
+ public override void Dispose()
+ {
+ base.Dispose();
+ // Just drop reference explicitly, as it's large, so it can be GCed
+ // Even if someone is holding a stale reference to the VM
+ this.SourceModList = null;
+ }
}
}
diff --git a/Wabbajack/View Models/SlideShow.cs b/Wabbajack/View Models/SlideShow.cs
index 625eb603..74bf9a80 100644
--- a/Wabbajack/View Models/SlideShow.cs
+++ b/Wabbajack/View Models/SlideShow.cs
@@ -80,19 +80,20 @@ namespace Wabbajack
{
if (modList?.SourceModList?.Archives == null)
{
- return Observable.Empty()
+ return Observable.Empty()
.ToObservableChangeSet(x => x.ModID);
}
return modList.SourceModList.Archives
.Select(m => m.State)
.OfType()
- .Select(nexus => new ModVM(nexus))
// Shuffle it
.Shuffle(_random)
.AsObservableChangeSet(x => x.ModID);
})
// Switch to the new list after every ModList change
.Switch()
+ .Transform(nexus => new ModVM(nexus))
+ .DisposeMany()
// Filter out any NSFW slides if we don't want them
.AutoRefreshOnObservable(slide => this.WhenAny(x => x.ShowNSFW))
.Filter(slide => !slide.IsNSFW || ShowNSFW)