Skip to content

Commit

Permalink
Merge pull request #2584 from Nexus-Mods/fix-large-operations-perfrom…
Browse files Browse the repository at this point in the history
…ance

Use SelectAwait in DiagnosticManager and fix a bunch of exceptions
  • Loading branch information
Al12rs authored Feb 3, 2025
2 parents e50328d + 6e80772 commit 5c2879e
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 34 deletions.
32 changes: 31 additions & 1 deletion src/NexusMods.App.UI/Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static ReadOnlyObservableCollection<T> ToReadOnlyObservableCollection<T>(
return new ReadOnlyObservableCollection<T>(source.ToObservableCollection());
}

public static Optional<TItem> MaxByOptional<TItem, TValue>(this IEnumerable<TItem> source, Func<TItem, TValue> selector)
public static Optional<TItem> OptionalMaxBy<TItem, TValue>(this IEnumerable<TItem> source, Func<TItem, TValue> selector)
where TItem : notnull
where TValue : IComparable<TValue>
{
Expand Down Expand Up @@ -61,4 +61,34 @@ public static Optional<TItem> MaxByOptional<TItem, TValue>(this IEnumerable<TIte

return maxItem;
}

public static Optional<TItem> OptionalMinBy<TItem, TValue>(this IEnumerable<TItem> source, Func<TItem, TValue> selector)
where TItem : notnull
where TValue : IComparable<TValue>
{
var minItem = Optional<TItem>.None;
var minValue = Optional<TValue>.None;

foreach (var item in source)
{
if (!minItem.HasValue)
{
minItem = item;
minValue = selector(item);
continue;
}

var value = selector(item);
var result = value.CompareTo(minValue.Value);

// Smaller than zero: value comes before minValue
if (result < 0)
{
minItem = item;
minValue = value;
}
}

return minItem;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ public CollectionDownloadViewModel(
.Subscribe(this, static (newerRevisions, self) =>
{
self.IsUpdateAvailable.Value = newerRevisions.Length > 0;
self.NewestRevisionNumber.Value = newerRevisions.First();
self.NewestRevisionNumber.Value = newerRevisions.FirstOrOptional(_ => true);
}).AddTo(disposables);

R3.Observable.Return(collectionJsonFile)
Expand Down
5 changes: 3 additions & 2 deletions src/NexusMods.App.UI/Pages/ILibraryDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using NexusMods.Abstractions.Library.Models;
using NexusMods.Abstractions.Loadouts;
using NexusMods.App.UI.Controls;
using NexusMods.App.UI.Extensions;
using NexusMods.App.UI.Pages.LibraryPage;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.Query;
Expand Down Expand Up @@ -58,8 +59,8 @@ public static void AddInstalledDateComponent(
if (query.Count == 0) return Optional<DateTimeOffset>.None;

return query.Items
.Select(static item => item.GetCreatedAt())
.Min();
.Select(static item => item.GetCreatedAt())
.OptionalMinBy(item => item);
});

itemModel.AddObservable(
Expand Down
4 changes: 3 additions & 1 deletion src/NexusMods.App.UI/Pages/ILoadoutDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using NexusMods.Abstractions.Loadouts;
using NexusMods.Abstractions.Loadouts.Extensions;
using NexusMods.App.UI.Controls;
using NexusMods.App.UI.Extensions;
using NexusMods.App.UI.Pages.LoadoutPage;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.DatomIterators;
Expand Down Expand Up @@ -87,7 +88,8 @@ public static void AddDateComponent(
var dateObservable = linkedItemsObservable
.QueryWhenChanged(query => query.Items
.Select(static item => item.GetCreatedAt())
.Min()
.OptionalMinBy(item => item)
.ValueOr(DateTimeOffset.MinValue)
);

parentItemModel.Add(SharedColumns.InstalledDate.ComponentKey, new DateComponent(
Expand Down
4 changes: 2 additions & 2 deletions src/NexusMods.App.UI/Pages/NexusModsDataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ private CompositeItemModel<EntityId> ToLibraryItemModel(NexusModsModPageMetadata
// Downloaded date: most recent downloaded file date
var downloadedDateObservable = libraryItems
.TransformImmutable(static item => item.GetCreatedAt())
.QueryWhenChanged(query => query.Items.Max());
.QueryWhenChanged(query => query.Items.OptionalMaxBy(item => item).ValueOr(DateTimeOffset.MinValue));

parentItemModel.Add(LibraryColumns.DownloadedDate.ComponentKey, new DateComponent(
initialValue: modPage.GetCreatedAt(),
Expand All @@ -263,7 +263,7 @@ private CompositeItemModel<EntityId> ToLibraryItemModel(NexusModsModPageMetadata
})
.QueryWhenChanged(static query =>
{
var max = query.Items.MaxByOptional(static tuple => tuple.parsedVersion.ValueOr(new NuGetVersion(0, 0, 0)));
var max = query.Items.OptionalMaxBy(static tuple => tuple.parsedVersion.ValueOr(new NuGetVersion(0, 0, 0)));
if (!max.HasValue) return string.Empty;
return max.Value.rawVersion;
});
Expand Down
55 changes: 28 additions & 27 deletions src/NexusMods.DataModel/Diagnostics/DiagnosticManager.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Collections.Concurrent;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
Expand All @@ -9,10 +8,9 @@
using NexusMods.Abstractions.Diagnostics.Emitters;
using NexusMods.Abstractions.Games;
using NexusMods.Abstractions.Loadouts;
using NexusMods.Extensions.BCL;
using NexusMods.MnemonicDB.Abstractions;
using NexusMods.MnemonicDB.Abstractions.Query;
using Observable = R3.Observable;
using R3;
using CompositeDisposable = R3.CompositeDisposable;

namespace NexusMods.DataModel.Diagnostics;

Expand All @@ -23,7 +21,7 @@ internal sealed class DiagnosticManager : IDiagnosticManager
private readonly ILogger<DiagnosticManager> _logger;

private static readonly object Lock = new();
private readonly SourceCache<IConnectableObservable<Diagnostic[]>, LoadoutId> _observableCache = new(_ => throw new NotSupportedException());
private readonly SourceCache<ConnectableObservable<Diagnostic[]>, LoadoutId> _observableCache = new(_ => throw new NotSupportedException());

private bool _isDisposed;
private readonly CompositeDisposable _compositeDisposable = new();
Expand All @@ -42,35 +40,38 @@ public IObservable<Diagnostic[]> GetLoadoutDiagnostics(LoadoutId loadoutId)
lock (Lock)
{
var existingObservable = _observableCache.Lookup(loadoutId);
if (existingObservable.HasValue) return existingObservable.Value;
if (existingObservable.HasValue) return existingObservable.Value.AsSystemObservable();

var connectableObservable = Loadout.RevisionsWithChildUpdates(_connection, loadoutId)
.Throttle(dueTime: TimeSpan.FromMilliseconds(250))
.SelectMany(async _ =>
{
var db = _connection.Db;
var loadout = Loadout.Load(db, loadoutId);
if (!loadout.IsValid()) return [];

try
.ToObservable()
.Debounce(TimeSpan.FromMilliseconds(250))
.SelectAwait(async (_, cancellationToken) =>
{
// TODO: cancellation token
// TODO: optimize this a bit so we don't load the model twice, we have the datoms above, we should be able to use them
var db = _connection.Db;
var loadout = Loadout.Load(db, loadoutId);
if (!loadout.IsValid()) return [];

var cancellationToken = CancellationToken.None;
return await GetLoadoutDiagnostics(loadout, cancellationToken);
}
catch (Exception e)
{
_logger.LogError(e, "Exception while diagnosing loadout {Loadout}", loadout.Name);
return [];
}
})
try
{
return await GetLoadoutDiagnostics(loadout, cancellationToken);
}
catch (OperationCanceledException)
{
return [];
}
catch (Exception e)
{
_logger.LogError(e, "Exception while diagnosing loadout {Loadout}", loadout.Name);
return [];
}
},
AwaitOperation.Switch
)
.Replay(bufferSize: 1);

_compositeDisposable.Add(connectableObservable.Connect());
_observableCache.Edit(updater => updater.AddOrUpdate(connectableObservable, loadoutId));
return connectableObservable;
return connectableObservable.AsSystemObservable();
}
}

Expand All @@ -96,7 +97,7 @@ await Parallel.ForEachAsync(diagnosticEmitters.OfType<ILoadoutDiagnosticEmitter>
}
}
}
catch (TaskCanceledException)
catch (OperationCanceledException)
{
// ignore
}
Expand Down

0 comments on commit 5c2879e

Please sign in to comment.