Skip to content

Commit

Permalink
Merge pull request #2116 from erri120/tech/stardew-valley-pipelines
Browse files Browse the repository at this point in the history
Add manifest pipeline for Stardew Valley
  • Loading branch information
Al12rs authored Oct 3, 2024
2 parents 3e693f1 + a0dbe46 commit ac90b80
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -28,46 +19,21 @@ namespace NexusMods.Games.StardewValley.Emitters;

public class DependencyDiagnosticEmitter : ILoadoutDiagnosticEmitter
{
private readonly ILogger<DependencyDiagnosticEmitter> _logger;
private readonly IFileStore _fileStore;
private readonly ILogger _logger;
private readonly IOSInformation _os;
private readonly ISMAPIWebApi _smapiWebApi;
private readonly IResourceLoader<SMAPIModLoadoutItem.ReadOnly, SMAPIManifest> _manifestPipeline;

public DependencyDiagnosticEmitter(
IServiceProvider serviceProvider,
ILogger<DependencyDiagnosticEmitter> logger,
IFileStore fileStore,
ISMAPIWebApi smapiWebApi,
IOSInformation os)
{
_logger = logger;
_fileStore = fileStore;
_smapiWebApi = smapiWebApi;
_os = os;
}

private static IResourceLoader<SMAPIModLoadoutItem.ReadOnly, SMAPIManifest> 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<SMAPIManifest>(json);
ArgumentNullException.ThrowIfNull(manifest);

return ValueTask.FromResult(resource.WithData(manifest));
})
.UseCache(
keyGenerator: static hash => hash,
keyComparer: EqualityComparer<Hash>.Default,
capacityPartition: new FavorWarmPartition(totalCapacity: 100)
)
.ChangeIdentifier<SMAPIModLoadoutItem.ReadOnly, Hash, SMAPIManifest>(
static mod => mod.Manifest.AsLoadoutFile().Hash
);

return pipeline;
_manifestPipeline = Pipelines.GetManifestPipeline(serviceProvider);
}

public async IAsyncEnumerable<Diagnostic> Diagnose(Loadout.ReadOnly loadout, [EnumeratorCancellation] CancellationToken cancellationToken)
Expand All @@ -82,7 +48,7 @@ public async IAsyncEnumerable<Diagnostic> 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
Expand Down
41 changes: 14 additions & 27 deletions src/Games/NexusMods.Games.StardewValley/Emitters/Helpers.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -28,8 +27,8 @@ public static bool TryGetSMAPI(Loadout.ReadOnly loadout, out SMAPILoadoutItem.Re

public static async IAsyncEnumerable<ValueTuple<SMAPIModLoadoutItem.ReadOnly, Manifest>> GetAllManifestsAsync(
ILogger logger,
IFileStore fileStore,
Loadout.ReadOnly loadout,
IResourceLoader<SMAPIModLoadoutItem.ReadOnly, Manifest> pipeline,
bool onlyEnabledMods,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
Expand All @@ -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<Manifest> resource;

private static async ValueTask<Manifest?> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,14 +33,16 @@ namespace NexusMods.Games.StardewValley.Emitters;
/// </summary>
public class SMAPIModDatabaseCompatibilityDiagnosticEmitter : ILoadoutDiagnosticEmitter
{
private readonly ILogger<SMAPIModDatabaseCompatibilityDiagnosticEmitter> _logger;
private readonly ILogger _logger;
private readonly IFileStore _fileStore;
private readonly IOSInformation _os;
private readonly ISMAPIWebApi _smapiWebApi;
private readonly IResourceLoader<SMAPIModLoadoutItem.ReadOnly, SMAPIManifest> _manifestPipeline;

private static readonly NamedLink DefaultWikiLink = new("SMAPI Wiki", new Uri("https://smapi.io/mods"));

public SMAPIModDatabaseCompatibilityDiagnosticEmitter(
IServiceProvider serviceProvider,
ILogger<SMAPIModDatabaseCompatibilityDiagnosticEmitter> logger,
IFileStore fileStore,
IOSInformation os,
Expand All @@ -49,6 +52,7 @@ public SMAPIModDatabaseCompatibilityDiagnosticEmitter(
_fileStore = fileStore;
_os = os;
_smapiWebApi = smapiWebApi;
_manifestPipeline = Pipelines.GetManifestPipeline(serviceProvider);
}

public async IAsyncEnumerable<Diagnostic> Diagnose(Loadout.ReadOnly loadout, [EnumeratorCancellation] CancellationToken cancellationToken)
Expand All @@ -66,7 +70,7 @@ public async IAsyncEnumerable<Diagnostic> 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)>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,34 @@
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;

[UsedImplicitly]
public class VersionDiagnosticEmitter : ILoadoutDiagnosticEmitter
{
private readonly ILogger _logger;
private readonly IFileStore _fileStore;
private readonly IOSInformation _os;
private readonly ISMAPIWebApi _smapiWebApi;
private readonly IResourceLoader<SMAPIModLoadoutItem.ReadOnly, SMAPIManifest> _manifestPipeline;

public VersionDiagnosticEmitter(
IServiceProvider serviceProvider,
ILogger<VersionDiagnosticEmitter> logger,
IFileStore fileStore,
IOSInformation os,
ISMAPIWebApi smapiWebApi)
{
_logger = logger;
_fileStore = fileStore;
_os = os;
_smapiWebApi = smapiWebApi;
_manifestPipeline = Pipelines.GetManifestPipeline(serviceProvider);
}

public async IAsyncEnumerable<Diagnostic> Diagnose(Loadout.ReadOnly loadout, [EnumeratorCancellation] CancellationToken cancellationToken)
Expand All @@ -45,7 +47,7 @@ public async IAsyncEnumerable<Diagnostic> 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(
Expand Down
58 changes: 58 additions & 0 deletions src/Games/NexusMods.Games.StardewValley/Pipelines.cs
Original file line number Diff line number Diff line change
@@ -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<IResourceLoader<SMAPIModLoadoutItem.ReadOnly, SMAPIManifest>>(
serviceKey: ManifestPipelineKey,
implementationFactory: static (serviceProvider, _) => CreateManifestPipeline(
fileStore: serviceProvider.GetRequiredService<IFileStore>()
)
);
}

public static IResourceLoader<SMAPIModLoadoutItem.ReadOnly, SMAPIManifest> GetManifestPipeline(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredKeyedService<IResourceLoader<SMAPIModLoadoutItem.ReadOnly, SMAPIManifest>>(serviceKey: ManifestPipelineKey);
}

private static IResourceLoader<SMAPIModLoadoutItem.ReadOnly, SMAPIManifest> 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<SMAPIManifest>(json);
ArgumentNullException.ThrowIfNull(manifest);

return ValueTask.FromResult(resource.WithData(manifest));
})
.UseCache(
keyGenerator: static hash => hash,
keyComparer: EqualityComparer<Hash>.Default,
capacityPartition: new FavorWarmPartition(totalCapacity: 100)
)
.ChangeIdentifier<SMAPIModLoadoutItem.ReadOnly, Hash, SMAPIManifest>(
static mod => mod.Manifest.AsLoadoutFile().Hash
);

return pipeline;
}
}
7 changes: 4 additions & 3 deletions src/Games/NexusMods.Games.StardewValley/Services.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static class Services
{
public static IServiceCollection AddStardewValley(this IServiceCollection services)
{
services
return services
.AddGame<StardewValley>()
.AddSingleton<ITool, SmapiRunGameTool>()

Expand Down Expand Up @@ -46,8 +46,9 @@ public static IServiceCollection AddStardewValley(this IServiceCollection servic

// Misc
.AddSingleton<ISMAPIWebApi, SMAPIWebApi>()
.AddSettings<StardewValleySettings>();
.AddSettings<StardewValleySettings>()

return services;
// Pipelines
.AddPipelines();
}
}

0 comments on commit ac90b80

Please sign in to comment.