A GC and dispose correctness pass

This commit is contained in:
Justin Swanson 2020-01-15 21:54:06 -06:00
parent 508fca2bf7
commit 36d73b82f0
6 changed files with 59 additions and 11 deletions

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
@ -67,7 +67,7 @@ namespace Wabbajack
/// <param name="filterSwitch">On/Off signal of whether to subscribe to source observable</param>
/// <param name="valueOnOff">Value to fire when switching off</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, T valueWhenOff)
public static IObservable<T> FlowSwitch<T>(this IObservable<T> source, IObservable<bool> filterSwitch, T valueWhenOff)
{
return filterSwitch
.DistinctUntilChanged()
@ -226,5 +226,21 @@ namespace Wabbajack
.Select(_ => true)
.StartWith(false));
}
public static IObservable<T> DisposeOld<T>(this IObservable<T> source)
where T : IDisposable
{
return source
.StartWith(default(T))
.Pairwise()
.Do(x =>
{
if (x.Previous != null)
{
x.Previous.Dispose();
}
})
.Select(x => x.Current);
}
}
}

View File

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

View File

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

View File

@ -21,30 +21,43 @@ namespace Wabbajack
public ObservableCollectionExtended<ModListMetadataVM> ModLists { get; } = new ObservableCollectionExtended<ModListMetadataVM>();
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<ModlistMetadata, string>.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);
}
}
}

View File

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

View File

@ -80,19 +80,20 @@ namespace Wabbajack
{
if (modList?.SourceModList?.Archives == null)
{
return Observable.Empty<ModVM>()
return Observable.Empty<NexusDownloader.State>()
.ToObservableChangeSet(x => x.ModID);
}
return modList.SourceModList.Archives
.Select(m => m.State)
.OfType<NexusDownloader.State>()
.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)