From a0dbe4677ddfd063bf9b87eb7ae76c341088f8eb Mon Sep 17 00:00:00 2001 From: erri120 Date: Wed, 2 Oct 2024 12:44:44 +0200 Subject: [PATCH] Add manifest pipeline --- .../Emitters/DependencyDiagnosticEmitter.cs | 44 ++------------ .../Emitters/Helpers.cs | 41 +++++-------- ...dDatabaseCompatibilityDiagnosticEmitter.cs | 8 ++- .../Emitters/VersionDiagnosticEmitter.cs | 12 ++-- .../Pipelines.cs | 58 +++++++++++++++++++ .../NexusMods.Games.StardewValley/Services.cs | 7 ++- 6 files changed, 94 insertions(+), 76 deletions(-) create mode 100644 src/Games/NexusMods.Games.StardewValley/Pipelines.cs diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs index 3128bc8bc4..61a9b1bd5d 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/DependencyDiagnosticEmitter.cs @@ -1,24 +1,15 @@ using System.Collections.Immutable; -using System.Reactive; using System.Runtime.CompilerServices; -using System.Text; -using BitFaster.Caching.Lru; using DynamicData.Kernel; using Microsoft.Extensions.Logging; using NexusMods.Abstractions.Diagnostics; using NexusMods.Abstractions.Diagnostics.Emitters; using NexusMods.Abstractions.Diagnostics.References; -using NexusMods.Abstractions.IO; using NexusMods.Abstractions.Loadouts; using NexusMods.Abstractions.Loadouts.Extensions; using NexusMods.Abstractions.Resources; -using NexusMods.Abstractions.Resources.Caching; -using NexusMods.Abstractions.Resources.DB; -using NexusMods.Abstractions.Resources.IO; using NexusMods.Games.StardewValley.Models; using NexusMods.Games.StardewValley.WebAPI; -using NexusMods.Hashing.xxHash64; -using NexusMods.MnemonicDB.Abstractions; using NexusMods.Paths; using StardewModdingAPI; using StardewModdingAPI.Toolkit; @@ -28,46 +19,21 @@ namespace NexusMods.Games.StardewValley.Emitters; public class DependencyDiagnosticEmitter : ILoadoutDiagnosticEmitter { - private readonly ILogger _logger; - private readonly IFileStore _fileStore; + private readonly ILogger _logger; private readonly IOSInformation _os; private readonly ISMAPIWebApi _smapiWebApi; + private readonly IResourceLoader _manifestPipeline; public DependencyDiagnosticEmitter( + IServiceProvider serviceProvider, ILogger logger, - IFileStore fileStore, ISMAPIWebApi smapiWebApi, IOSInformation os) { _logger = logger; - _fileStore = fileStore; _smapiWebApi = smapiWebApi; _os = os; - } - - private static IResourceLoader CreatePipeline(IFileStore fileStore) - { - var pipeline = new FileStoreLoader(fileStore) - .ThenDo(Unit.Default, static (_, _, resource, _) => - { - var bytes = resource.Data; - var json = Encoding.UTF8.GetString(bytes); - - var manifest = Interop.SMAPIJsonHelper.Deserialize(json); - ArgumentNullException.ThrowIfNull(manifest); - - return ValueTask.FromResult(resource.WithData(manifest)); - }) - .UseCache( - keyGenerator: static hash => hash, - keyComparer: EqualityComparer.Default, - capacityPartition: new FavorWarmPartition(totalCapacity: 100) - ) - .ChangeIdentifier( - static mod => mod.Manifest.AsLoadoutFile().Hash - ); - - return pipeline; + _manifestPipeline = Pipelines.GetManifestPipeline(serviceProvider); } public async IAsyncEnumerable Diagnose(Loadout.ReadOnly loadout, [EnumeratorCancellation] CancellationToken cancellationToken) @@ -82,7 +48,7 @@ public async IAsyncEnumerable Diagnose(Loadout.ReadOnly loadout, [En } var loadoutItemIdToManifest = await Helpers - .GetAllManifestsAsync(_logger, _fileStore, loadout, onlyEnabledMods: false, cancellationToken) + .GetAllManifestsAsync(_logger, loadout, _manifestPipeline, onlyEnabledMods: false, cancellationToken) .ToDictionaryAsync(tuple => tuple.Item1.SMAPIModLoadoutItemId, tuple => tuple.Item2, cancellationToken); var uniqueIdToLoadoutItemId = loadoutItemIdToManifest diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/Helpers.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/Helpers.cs index 318347949f..b7d0d9d5f8 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/Helpers.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/Helpers.cs @@ -1,9 +1,8 @@ -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using NexusMods.Abstractions.Diagnostics.Values; -using NexusMods.Abstractions.IO; using NexusMods.Abstractions.Loadouts; +using NexusMods.Abstractions.Resources; using NexusMods.Abstractions.Telemetry; using NexusMods.Extensions.BCL; using NexusMods.Games.StardewValley.Models; @@ -28,8 +27,8 @@ public static bool TryGetSMAPI(Loadout.ReadOnly loadout, out SMAPILoadoutItem.Re public static async IAsyncEnumerable> GetAllManifestsAsync( ILogger logger, - IFileStore fileStore, Loadout.ReadOnly loadout, + IResourceLoader pipeline, bool onlyEnabledMods, [EnumeratorCancellation] CancellationToken cancellationToken) { @@ -45,32 +44,20 @@ public static bool TryGetSMAPI(Loadout.ReadOnly loadout, out SMAPILoadoutItem.Re while (await enumerator.MoveNextAsync()) { var smapiMod = enumerator.Current; - var manifest = await GetManifest(logger, fileStore, smapiMod, cancellationToken); - if (manifest is null) continue; - yield return (smapiMod, manifest); - } - } + Resource resource; - private static async ValueTask GetManifest( - ILogger logger, - IFileStore fileStore, - SMAPIModLoadoutItem.ReadOnly smapiMod, - CancellationToken cancellationToken) - { - try - { - return await Interop.GetManifest(fileStore, smapiMod, cancellationToken); - } - catch (TaskCanceledException) - { - // ignored - return null; - } - catch (Exception e) - { - logger.LogError(e, "Exception trying to get manifest for mod {Mod}", smapiMod.AsLoadoutItemGroup().AsLoadoutItem().Name); - return null; + try + { + resource = await pipeline.LoadResourceAsync(smapiMod, cancellationToken); + } + catch (Exception e) + { + logger.LogError(e, "Exception while getting manifest for `{Name}`", smapiMod.AsLoadoutItemGroup().AsLoadoutItem().Name); + continue; + } + + yield return (smapiMod, resource.Data); } } } diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs index c587004a81..8326d3b0ac 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/SMAPIModDatabaseCompatibilityDiagnosticEmitter.cs @@ -6,6 +6,7 @@ using NexusMods.Abstractions.Diagnostics.Values; using NexusMods.Abstractions.IO; using NexusMods.Abstractions.Loadouts; +using NexusMods.Abstractions.Resources; using NexusMods.Games.StardewValley.Models; using NexusMods.Games.StardewValley.WebAPI; using NexusMods.Paths; @@ -32,14 +33,16 @@ namespace NexusMods.Games.StardewValley.Emitters; /// public class SMAPIModDatabaseCompatibilityDiagnosticEmitter : ILoadoutDiagnosticEmitter { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IFileStore _fileStore; private readonly IOSInformation _os; private readonly ISMAPIWebApi _smapiWebApi; + private readonly IResourceLoader _manifestPipeline; private static readonly NamedLink DefaultWikiLink = new("SMAPI Wiki", new Uri("https://smapi.io/mods")); public SMAPIModDatabaseCompatibilityDiagnosticEmitter( + IServiceProvider serviceProvider, ILogger logger, IFileStore fileStore, IOSInformation os, @@ -49,6 +52,7 @@ public SMAPIModDatabaseCompatibilityDiagnosticEmitter( _fileStore = fileStore; _os = os; _smapiWebApi = smapiWebApi; + _manifestPipeline = Pipelines.GetManifestPipeline(serviceProvider); } public async IAsyncEnumerable Diagnose(Loadout.ReadOnly loadout, [EnumeratorCancellation] CancellationToken cancellationToken) @@ -66,7 +70,7 @@ public async IAsyncEnumerable Diagnose(Loadout.ReadOnly loadout, [En if (modDatabase is null) yield break; var smapiMods = await Helpers - .GetAllManifestsAsync(_logger, _fileStore, loadout, onlyEnabledMods: true, cancellationToken) + .GetAllManifestsAsync(_logger, loadout, _manifestPipeline, onlyEnabledMods: true, cancellationToken) .ToArrayAsync(cancellationToken); var list = new List<(SMAPIModLoadoutItem.ReadOnly smapiMod, SMAPIManifest manifest, ModDataRecordVersionedFields versionedFields)>(); diff --git a/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs b/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs index d116c75a10..d5c2376c81 100644 --- a/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs +++ b/src/Games/NexusMods.Games.StardewValley/Emitters/VersionDiagnosticEmitter.cs @@ -4,11 +4,13 @@ using NexusMods.Abstractions.Diagnostics; using NexusMods.Abstractions.Diagnostics.Emitters; using NexusMods.Abstractions.Diagnostics.References; -using NexusMods.Abstractions.IO; using NexusMods.Abstractions.Loadouts; +using NexusMods.Abstractions.Resources; +using NexusMods.Games.StardewValley.Models; using NexusMods.Games.StardewValley.WebAPI; using NexusMods.Paths; using StardewModdingAPI.Toolkit; +using SMAPIManifest = StardewModdingAPI.Toolkit.Serialization.Models.Manifest; namespace NexusMods.Games.StardewValley.Emitters; @@ -16,20 +18,20 @@ namespace NexusMods.Games.StardewValley.Emitters; public class VersionDiagnosticEmitter : ILoadoutDiagnosticEmitter { private readonly ILogger _logger; - private readonly IFileStore _fileStore; private readonly IOSInformation _os; private readonly ISMAPIWebApi _smapiWebApi; + private readonly IResourceLoader _manifestPipeline; public VersionDiagnosticEmitter( + IServiceProvider serviceProvider, ILogger logger, - IFileStore fileStore, IOSInformation os, ISMAPIWebApi smapiWebApi) { _logger = logger; - _fileStore = fileStore; _os = os; _smapiWebApi = smapiWebApi; + _manifestPipeline = Pipelines.GetManifestPipeline(serviceProvider); } public async IAsyncEnumerable Diagnose(Loadout.ReadOnly loadout, [EnumeratorCancellation] CancellationToken cancellationToken) @@ -45,7 +47,7 @@ public async IAsyncEnumerable Diagnose(Loadout.ReadOnly loadout, [En } var smapiMods = await Helpers - .GetAllManifestsAsync(_logger, _fileStore, loadout, onlyEnabledMods: true, cancellationToken) + .GetAllManifestsAsync(_logger, loadout, _manifestPipeline, onlyEnabledMods: true, cancellationToken) .ToArrayAsync(cancellationToken); var apiMods = await _smapiWebApi.GetModDetails( diff --git a/src/Games/NexusMods.Games.StardewValley/Pipelines.cs b/src/Games/NexusMods.Games.StardewValley/Pipelines.cs new file mode 100644 index 0000000000..9bc9f8b2bd --- /dev/null +++ b/src/Games/NexusMods.Games.StardewValley/Pipelines.cs @@ -0,0 +1,58 @@ +using System.Reactive; +using System.Text; +using BitFaster.Caching.Lru; +using Microsoft.Extensions.DependencyInjection; +using NexusMods.Abstractions.IO; +using NexusMods.Abstractions.Resources; +using NexusMods.Abstractions.Resources.Caching; +using NexusMods.Abstractions.Resources.IO; +using NexusMods.Games.StardewValley.Models; +using NexusMods.Hashing.xxHash64; +using SMAPIManifest = StardewModdingAPI.Toolkit.Serialization.Models.Manifest; + +namespace NexusMods.Games.StardewValley; + +internal static class Pipelines +{ + public const string ManifestPipelineKey = nameof(ManifestPipelineKey); + + public static IServiceCollection AddPipelines(this IServiceCollection serviceCollection) + { + return serviceCollection.AddKeyedSingleton>( + serviceKey: ManifestPipelineKey, + implementationFactory: static (serviceProvider, _) => CreateManifestPipeline( + fileStore: serviceProvider.GetRequiredService() + ) + ); + } + + public static IResourceLoader GetManifestPipeline(IServiceProvider serviceProvider) + { + return serviceProvider.GetRequiredKeyedService>(serviceKey: ManifestPipelineKey); + } + + private static IResourceLoader CreateManifestPipeline(IFileStore fileStore) + { + var pipeline = new FileStoreLoader(fileStore) + .ThenDo(Unit.Default, static (_, _, resource, _) => + { + var bytes = resource.Data; + var json = Encoding.UTF8.GetString(bytes); + + var manifest = Interop.SMAPIJsonHelper.Deserialize(json); + ArgumentNullException.ThrowIfNull(manifest); + + return ValueTask.FromResult(resource.WithData(manifest)); + }) + .UseCache( + keyGenerator: static hash => hash, + keyComparer: EqualityComparer.Default, + capacityPartition: new FavorWarmPartition(totalCapacity: 100) + ) + .ChangeIdentifier( + static mod => mod.Manifest.AsLoadoutFile().Hash + ); + + return pipeline; + } +} diff --git a/src/Games/NexusMods.Games.StardewValley/Services.cs b/src/Games/NexusMods.Games.StardewValley/Services.cs index d5cd98b478..0bd3b920d7 100644 --- a/src/Games/NexusMods.Games.StardewValley/Services.cs +++ b/src/Games/NexusMods.Games.StardewValley/Services.cs @@ -16,7 +16,7 @@ public static class Services { public static IServiceCollection AddStardewValley(this IServiceCollection services) { - services + return services .AddGame() .AddSingleton() @@ -46,8 +46,9 @@ public static IServiceCollection AddStardewValley(this IServiceCollection servic // Misc .AddSingleton() - .AddSettings(); + .AddSettings() - return services; + // Pipelines + .AddPipelines(); } }