diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cc99e80 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +[*.cs] + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = none + +# IDE0305: Simplify collection initialization +dotnet_style_prefer_collection_expression = never + +# IDE0290: Use primary constructor +csharp_style_prefer_primary_constructors = false diff --git a/ALttPRandomizer.sln b/ALttPRandomizer.sln index a9626d6..73a0899 100644 --- a/ALttPRandomizer.sln +++ b/ALttPRandomizer.sln @@ -1,10 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.12.35521.163 d17.12 +VisualStudioVersion = 17.12.35521.163 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ALttPRandomizer", "ALttPRandomizer\ALttPRandomizer.csproj", "{8F6F2C3C-ACE4-4944-9722-21E71222195A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/ALttPRandomizer/GenerationFailedException.cs b/ALttPRandomizer/GenerationFailedException.cs index aab5281..19b5daf 100644 --- a/ALttPRandomizer/GenerationFailedException.cs +++ b/ALttPRandomizer/GenerationFailedException.cs @@ -2,6 +2,6 @@ using System; public class GenerationFailedException : Exception { - public GenerationFailedException(string message) : base(message) { } + public GenerationFailedException(string message, params string[] args) : base(string.Format(message, args)) { } } } diff --git a/ALttPRandomizer/Model/SeedSettings.cs b/ALttPRandomizer/Model/SeedSettings.cs index 9558bf8..60d8e08 100644 --- a/ALttPRandomizer/Model/SeedSettings.cs +++ b/ALttPRandomizer/Model/SeedSettings.cs @@ -173,11 +173,10 @@ public Hints Hints { get; set; } = Hints.Off; } - public enum RandomizerInstance - { - [RandomizerName(BaseRandomizer.Name)] Base, - [RandomizerName(Apr2025Randomizer.Name)] Apr2025, - [RandomizerName(BaseRandomizer.BetaName)] Beta, + public enum RandomizerInstance { + [GeneratorSettings("base", "OR_", "--bps", "--spoiler=json")] Base, + [GeneratorSettings("beta", "OR_","--bps", "--spoiler=json")] Beta, + [GeneratorSettings("apr2025", "ER_", requireFlips: true, "--json_spoiler")] Apr2025, } public enum RaceMode { diff --git a/ALttPRandomizer/Program.cs b/ALttPRandomizer/Program.cs index d6e05b2..f09d1a4 100644 --- a/ALttPRandomizer/Program.cs +++ b/ALttPRandomizer/Program.cs @@ -69,13 +69,10 @@ builder.Services.AddSingleton(sp => sp); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddKeyedScoped(BaseRandomizer.Name); - builder.Services.AddKeyedScoped(BaseRandomizer.BetaName); - builder.Services.AddKeyedScoped(Apr2025Randomizer.Name); builder.Services.AddScoped(); - builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/ALttPRandomizer/Randomizers/Apr2025Randomizer.cs b/ALttPRandomizer/Randomizers/Apr2025Randomizer.cs deleted file mode 100644 index 30b2196..0000000 --- a/ALttPRandomizer/Randomizers/Apr2025Randomizer.cs +++ /dev/null @@ -1,157 +0,0 @@ -namespace ALttPRandomizer.Randomizers { - using ALttPRandomizer; - using ALttPRandomizer.Azure; - using ALttPRandomizer.Model; - using ALttPRandomizer.Options; - using ALttPRandomizer.Settings; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - using System; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Text.Json; - using System.Threading.Tasks; - - public class Apr2025Randomizer : IRandomizer { - public const string Name = "apr2025"; - public const RandomizerInstance Instance = RandomizerInstance.Apr2025; - - public Apr2025Randomizer( - AzureStorage azureStorage, - CommonSettingsProcessor settingsProcessor, - IOptionsMonitor optionsMonitor, - ILogger logger) { - AzureStorage = azureStorage; - SettingsProcessor = settingsProcessor; - OptionsMonitor = optionsMonitor; - Logger = logger; - } - - private CommonSettingsProcessor SettingsProcessor { get; } - private AzureStorage AzureStorage { get; } - private IOptionsMonitor OptionsMonitor { get; } - private ILogger Logger { get; } - private ServiceOptions Configuration => OptionsMonitor.CurrentValue; - - public void Validate(SeedSettings settings) { - this.SettingsProcessor.ValidateSettings(Instance, settings); - } - - public async Task Randomize(string id, SeedSettings settings, bool uploadSettings = true) { - Logger.LogDebug("Recieved request for id {id} to randomize settings {@settings}", id, settings); - - var generatorSettings = this.Configuration.Generators[Name]; - var start = new ProcessStartInfo(generatorSettings.RandomizerCommand[0], generatorSettings.RandomizerCommand.Skip(1)) { - WorkingDirectory = generatorSettings.WorkingDirectory, - RedirectStandardOutput = true, - RedirectStandardError = true, - }; - - var args = start.ArgumentList; - args.Add("--rom"); - args.Add(Configuration.Baserom); - - args.Add("--outputpath"); - args.Add(Path.GetTempPath()); - - args.Add("--outputname"); - args.Add(id); - - foreach (var arg in SettingsProcessor.GetSettings(Instance, settings)) { - args.Add(arg); - } - - Logger.LogInformation("Randomizing {id} with command: {command} {args}", id, start.FileName, string.Join(" ", args.Select(arg => arg.Contains(" ") ? $"\"{arg}\"" : arg))); - - var generating = string.Format("{0}/generating", id); - await AzureStorage.UploadFile(generating, BinaryData.Empty); - - var process = Process.Start(start) ?? throw new GenerationFailedException("Process failed to start."); - process.EnableRaisingEvents = true; - - process.OutputDataReceived += (_, args) => Logger.LogInformation("Randomizer STDOUT: {output}", args.Data); - process.ErrorDataReceived += (_, args) => Logger.LogInformation("Randomizer STDERR: {output}", args.Data); - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - process.Exited += async (sender, args) => { - var exitcode = process.ExitCode; - - if (exitcode != 0) { - await GenerationFailed(id, exitcode); - } else { - await GenerationSucceeded(id, settings); - } - }; - - if (uploadSettings) { - var settingsJson = JsonSerializer.SerializeToDocument(settings, JsonOptions.Default); - var settingsOut = string.Format("{0}/settings.json", id); - await AzureStorage.UploadFile(settingsOut, new BinaryData(settingsJson)); - } - } - - private async Task GenerationSucceeded(string id, SeedSettings settings) { - var rom = Path.Join(Path.GetTempPath(), string.Format("ER_{0}.sfc", id)); - - var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("ER_{0}_Spoiler.json", id)); - var spoilerOut = string.Format("{0}/spoiler.json", id); - var uploadSpoiler = AzureStorage.UploadFileAndDelete(spoilerOut, spoilerIn); - - var metaIn = Path.Join(Path.GetTempPath(), string.Format("ER_{0}_Meta.json", id)); - var metaOut = string.Format("{0}/meta.json", id); - var uploadMeta = AzureStorage.UploadFileAndDelete(metaOut, metaIn); - - var bpsIn = Path.Join(Path.GetTempPath(), string.Format("ER_{0}.bps", id)); - - var flips = new ProcessStartInfo() { - FileName = Configuration.FlipsPath, - RedirectStandardOutput = true, - RedirectStandardError = true, - }; - var args = flips.ArgumentList; - args.Add("--create"); - args.Add(Configuration.Baserom); - args.Add(rom); - args.Add(bpsIn); - - var process = Process.Start(flips) ?? throw new GenerationFailedException("Process failed to start."); - process.EnableRaisingEvents = true; - - process.OutputDataReceived += (_, args) => Logger.LogInformation("flips STDOUT: {output}", args.Data); - process.ErrorDataReceived += (_, args) => Logger.LogInformation("flips STDERR: {output}", args.Data); - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - await process.WaitForExitAsync(); - - if (process.ExitCode != 0) { - await this.GenerationFailed(id, process.ExitCode); - return; - } - - var bpsOut = string.Format("{0}/patch.bps", id); - var uploadPatch = AzureStorage.UploadFileAndDelete(bpsOut, bpsIn); - - var generating = string.Format("{0}/generating", id); - var deleteGenerating = AzureStorage.DeleteFile(generating); - - await Task.WhenAll(uploadPatch, uploadSpoiler, uploadMeta, deleteGenerating); - - Logger.LogDebug("Deleting file {filepath}", rom); - File.Delete(rom); - - Logger.LogInformation("Finished uploading seed id {id}", id); - } - - private async Task GenerationFailed(string id, int exitcode) { - var generating = string.Format("{0}/generating", id); - var deleteGenerating = AzureStorage.DeleteFile(generating); - - await Task.WhenAll(deleteGenerating); - } - } -} diff --git a/ALttPRandomizer/Randomizers/BaseRandomizer.cs b/ALttPRandomizer/Randomizers/BaseRandomizer.cs index 10598ae..914db11 100644 --- a/ALttPRandomizer/Randomizers/BaseRandomizer.cs +++ b/ALttPRandomizer/Randomizers/BaseRandomizer.cs @@ -3,6 +3,7 @@ using ALttPRandomizer.Azure; using ALttPRandomizer.Model; using ALttPRandomizer.Options; + using ALttPRandomizer.Service; using ALttPRandomizer.Settings; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -15,21 +16,20 @@ using System.Threading.Tasks; public class BaseRandomizer : IRandomizer { - public const string Name = "base"; - public const string BetaName = "beta"; - public const int MULTI_TRIES = 100; public const int SINGLE_TRIES = 5; public BaseRandomizer( AzureStorage azureStorage, CommonSettingsProcessor settingsProcessor, + ProcessService processService, IdGenerator idGenerator, ShutdownHandler shutdownHandler, IOptionsMonitor optionsMonitor, ILogger logger) { this.AzureStorage = azureStorage; this.SettingsProcessor = settingsProcessor; + this.ProcessService = processService; this.IdGenerator = idGenerator; this.ShutdownHandler = shutdownHandler; this.OptionsMonitor = optionsMonitor; @@ -38,6 +38,7 @@ private CommonSettingsProcessor SettingsProcessor { get; } private AzureStorage AzureStorage { get; } + private ProcessService ProcessService { get; } private IdGenerator IdGenerator { get; } private IOptionsMonitor OptionsMonitor { get; } private ILogger Logger { get; } @@ -58,11 +59,11 @@ } private List GetArgs(SeedSettings settings) { - var args = new List() { - "--reduce_flashing", - "--quickswap", - "--shuffletavern", - }; + var args = new List(); + + if (settings.Randomizer != RandomizerInstance.Apr2025) { + args.Add("--shuffletavern"); + } if (settings.DoorShuffle == DoorShuffle.Vanilla) { settings.DoorTypeMode = DoorTypeMode.Original; @@ -80,50 +81,25 @@ return args; } - private async Task StartProcess(string generatorName, string id, IEnumerable settings, Func completed) { - var generatorSettings = this.Configuration.Generators[generatorName]; + private async Task StartProcess(GeneratorSettingsAttribute generatorSettings, string id, IEnumerable settings, Func completed) { + var generatorConfigSettings = this.Configuration.Generators[generatorSettings.Name]; - var start = new ProcessStartInfo(generatorSettings.RandomizerCommand[0], generatorSettings.RandomizerCommand.Skip(1)) { - WorkingDirectory = generatorSettings.WorkingDirectory, - RedirectStandardOutput = true, - RedirectStandardError = true, - }; - - var args = start.ArgumentList; - args.Add("--rom"); - args.Add(Configuration.Baserom); - - args.Add("--outputpath"); - args.Add(Path.GetTempPath()); - - args.Add("--outputname"); - args.Add(id); - - foreach (var arg in settings) { - args.Add(arg); - } - - Logger.LogInformation("Randomizing {id} with command: {command} {args}", id, start.FileName, string.Join(" ", args.Select(arg => arg.Contains(" ") ? $"\"{arg}\"" : arg))); + string[] args = [ + .. generatorConfigSettings.RandomizerCommand, + .. generatorSettings.Args, + "--rom", + Configuration.Baserom, + "--outputpath", + Path.GetTempPath(), + "--outputname", + id, + .. settings, + ]; var generating = string.Format("{0}/generating", id); await AzureStorage.UploadFile(generating, BinaryData.Empty); - var process = Process.Start(start) ?? throw new GenerationFailedException("Process failed to start."); - process.EnableRaisingEvents = true; - - process.OutputDataReceived += (_, args) => { - if (args.Data != null) { - Logger.LogInformation("Randomizer {id} STDOUT: {output}", id, args.Data); - } - }; - process.ErrorDataReceived += (_, args) => { - if (args.Data != null) { - Logger.LogInformation("Randomizer {id} STDERR: {output}", id, args.Data); - } - }; - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); + var process = this.ProcessService.StartProcess($"Generation {id}", generatorConfigSettings.WorkingDirectory, args); this.ShutdownHandler.AddId(id); @@ -141,13 +117,19 @@ public async Task Randomize(string id, SeedSettings settings, bool uploadSettings = true) { Logger.LogDebug("Recieved request for id {id} to randomize settings {@settings}", id, settings); - var args = this.GetArgs(settings).Append(string.Format("--tries={0}", SINGLE_TRIES)); + var args = this.GetArgs(settings); - await StartProcess(this.SettingsProcessor.GetRandomizerName(settings.Randomizer), id, args, async exitcode => { + if (settings.Randomizer != RandomizerInstance.Apr2025) { + args.Append(string.Format("--tries={0}", SINGLE_TRIES)); + } + + var generatorSettings = this.SettingsProcessor.GetGeneratorSettings(settings.Randomizer); + + await StartProcess(generatorSettings, id, args, async exitcode => { if (exitcode != 0) { await GenerationFailed(id, exitcode); } else { - await SingleSucceeded(id); + await SingleSucceeded(generatorSettings, id); } }); @@ -159,7 +141,8 @@ } public async Task RandomizeMultiworld(string id, IList settings, bool uploadSettings = true) { - var randomizerName = this.SettingsProcessor.GetRandomizerName(settings[0].Randomizer); + var generatorSettings = this.SettingsProcessor.GetGeneratorSettings(settings[0].Randomizer); + Logger.LogDebug("Recieved request for id {id} to randomize multiworld settings {@settings}", id, settings); var names = settings.Select(s => s.PlayerName.Replace(' ', '_')).ToList(); @@ -169,11 +152,11 @@ .Append(string.Format("--multi={0}", settings.Count)) .Append(string.Format("--tries={0}", MULTI_TRIES)); - await StartProcess(randomizerName, id, args, async exitcode => { + await StartProcess(generatorSettings, id, args, async exitcode => { if (exitcode != 0) { await GenerationFailed(id, exitcode); } else { - await MultiSucceeded(id, settings, names); + await MultiSucceeded(generatorSettings, id, settings, names); } }); @@ -184,91 +167,115 @@ } } - private async Task SingleSucceeded(string id) { - try { - var basename = string.Format("OR_{0}", id); - await this.UploadFiles(id, basename, 1, null); + private async Task GeneratePatch(string id, string basename) { + var tempPath = Path.GetTempPath(); + var bps = Path.Join(tempPath, string.Format("{0}.bps", basename)); + var sfc = Path.Join(tempPath, string.Format("{0}.sfc", basename)); - var metaIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_Meta.json", id)); + var process = this.ProcessService.StartProcess($"Generation {id}", tempPath, this.Configuration.FlipsPath, "--create", this.Configuration.Baserom, sfc, bps); + + await process.WaitForExitAsync(); + + return process.ExitCode; + } + + private async Task SingleSucceeded(GeneratorSettingsAttribute generatorSettings, string id) { + try { + var basename = $"{generatorSettings.Prefix}{id}"; + if (generatorSettings.RequireFlips) { + var exitCode = await GeneratePatch(id, basename); + + if (exitCode != 0) { + await this.GenerationFailed(id, exitCode); + return; + } + } + + await this.UploadFiles(id, basename); + + var metaIn = Path.Join(Path.GetTempPath(), string.Format("{0}_Meta.json", basename)); Logger.LogDebug("Deleting file {filepath}", metaIn); File.Delete(metaIn); - var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_Spoiler.json", id)); + var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("{0}_Spoiler.json", basename)); Logger.LogDebug("Deleting file {filepath}", spoilerIn); File.Delete(spoilerIn); Logger.LogInformation("Finished uploading seed id {id}", id); } finally { - var generating = string.Format("{0}/generating", id); - await AzureStorage.DeleteFile(generating); + await AzureStorage.DeleteFile($"{id}/generating"); } } - private async Task UploadFiles(string id, string basename, int playerNum, string? parentId) { + private async Task UploadFiles(string id, string basename, int playerNum = 1, string playerSuffix = "") { var tasks = new List(); - var rom = Path.Join(Path.GetTempPath(), string.Format("{0}.sfc", basename)); + var rom = Path.Join(Path.GetTempPath(), string.Format("{0}{1}.sfc", basename, playerSuffix)); Logger.LogDebug("Deleting file {filepath}", rom); File.Delete(rom); - var bpsIn = Path.Join(Path.GetTempPath(), string.Format("{0}.bps", basename)); - var bpsOut = string.Format("{0}/patch.bps", id); - tasks.Add(this.AzureStorage.UploadFileAndDelete(bpsOut, bpsIn)); + var bpsIn = Path.Join(Path.GetTempPath(), string.Format("{0}{1}.bps", basename, playerSuffix)); + tasks.Add(this.AzureStorage.UploadFileAndDelete($"{id}/patch.bps", bpsIn)); - var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_Spoiler.json", parentId ?? id)); - var spoilerOut = string.Format("{0}/spoiler.json", id); - tasks.Add(this.AzureStorage.UploadFileFromSource(spoilerOut, spoilerIn)); + var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("{0}_Spoiler.json", basename)); + tasks.Add(this.AzureStorage.UploadFileFromSource($"{id}/spoiler.json", spoilerIn)); - var metaIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_Meta.json", parentId ?? id)); - var metaOut = string.Format("{0}/meta.json", id); + var metaIn = Path.Join(Path.GetTempPath(), string.Format("{0}_Meta.json", basename)); var meta = ProcessMetadata(metaIn, playerNum); - tasks.Add(this.AzureStorage.UploadFile(metaOut, new BinaryData(meta))); - - if (parentId != null) { - var parentOut = string.Format("{0}/parent", id); - tasks.Add(this.AzureStorage.UploadFile(parentOut, new BinaryData(parentId))); - } + tasks.Add(this.AzureStorage.UploadFile($"{id}/meta.json", new BinaryData(meta))); await Task.WhenAll(tasks); } - private async Task MultiSucceeded(string id, IList settings, IList names) { + private async Task MultiSucceeded(GeneratorSettingsAttribute generatorSettings, string id, IList settings, List names) { var tasks = new List(); var subIds = new List(); var worlds = new List(); try { + var basename = $"{generatorSettings.Prefix}{id}"; + for (var i = 0; i < settings.Count; i++) { - var basename = string.Format("OR_{0}_P{1}_{2}", id, i + 1, names[i]); + var playerSuffix = $"_P{i + 1}_{names[i]}"; var randomId = this.IdGenerator.GenerateId(); subIds.Add(randomId); - tasks.Add(this.UploadFiles(randomId, basename, i + 1, id)); + + Task flipsTask; + if (generatorSettings.RequireFlips) { + flipsTask = this.GeneratePatch(id, $"{basename}{playerSuffix}"); + } else { + flipsTask = Task.FromResult(0); + } + + tasks.Add(flipsTask.ContinueWith(exitCode => { + if (exitCode.Result != 0) { + this.Logger.LogWarning("Generation {id} - flips failed with exit code {exitCode}", id, exitCode); + } + + return this.UploadFiles(randomId, basename, i + 1, playerSuffix); + })); + + tasks.Add(this.AzureStorage.UploadFile($"{randomId}/parent", new BinaryData(id))); worlds.Add(new { Name = settings[i].PlayerName, Id = randomId }); var settingsJson = JsonSerializer.SerializeToDocument(settings[i], JsonOptions.Default); - var settingsOut = string.Format("{0}/settings.json", randomId); - tasks.Add(this.AzureStorage.UploadFile(settingsOut, new BinaryData(settingsJson))); + tasks.Add(this.AzureStorage.UploadFile($"{randomId}/settings.json", new BinaryData(settingsJson))); } var worldsJson = JsonSerializer.SerializeToDocument(worlds, JsonOptions.Default); - var worldsOut = string.Format("{0}/worlds.json", id); - - tasks.Add(this.AzureStorage.UploadFile(worldsOut, new BinaryData(worldsJson))); + tasks.Add(this.AzureStorage.UploadFile($"{id}/worlds.json", new BinaryData(worldsJson))); await Task.WhenAll(tasks); - var metaIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_Meta.json", id)); - var metaOut = string.Format("{0}/meta.json", id); - var uploadMeta = AzureStorage.UploadFileAndDelete(metaOut, metaIn); + var metaIn = Path.Join(Path.GetTempPath(), string.Format("{0}_Meta.json", basename)); + var uploadMeta = AzureStorage.UploadFileAndDelete($"{id}/meta.json", metaIn); - var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_Spoiler.json", id)); - var spoilerOut = string.Format("{0}/spoiler.json", id); - var uploadSpoiler = AzureStorage.UploadFileAndDelete(spoilerOut, spoilerIn); + var spoilerIn = Path.Join(Path.GetTempPath(), string.Format("{0}_Spoiler.json", basename)); + var uploadSpoiler = AzureStorage.UploadFileAndDelete($"{id}/spoiler.json", spoilerIn); - var multidataIn = Path.Join(Path.GetTempPath(), string.Format("OR_{0}_multidata", id)); - var multidataOut = string.Format("{0}/multidata", id); - var uploadMultidata = AzureStorage.UploadFileAndDelete(multidataOut, multidataIn); + var multidataIn = Path.Join(Path.GetTempPath(), string.Format("{0}_multidata", basename)); + var uploadMultidata = AzureStorage.UploadFileAndDelete($"{id}/multidata", multidataIn); await Task.WhenAll(uploadMeta, uploadSpoiler, uploadMultidata); diff --git a/ALttPRandomizer/Service/ProcessService.cs b/ALttPRandomizer/Service/ProcessService.cs new file mode 100644 index 0000000..0e51fe9 --- /dev/null +++ b/ALttPRandomizer/Service/ProcessService.cs @@ -0,0 +1,41 @@ +namespace ALttPRandomizer.Service { + using System.Diagnostics; + using System.Linq; + using Microsoft.Extensions.Logging; + + public class ProcessService { + public ProcessService(ILogger logger) { + this.Logger = logger; + } + + public ILogger Logger { get; } + + public Process StartProcess(string logPrefix, string workingDirectory, params string[] args) { + var start = new ProcessStartInfo(args[0], args.Skip(1)) { + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + }; + + this.Logger.LogInformation("{prefix} - executing command: {command}", logPrefix, string.Join(" ", args.Select(arg => arg.Contains(' ') ? $"\"{arg}\"" : arg))); + + var process = Process.Start(start) ?? throw new GenerationFailedException("{0} - Process failed to start.", logPrefix); + process.EnableRaisingEvents = true; + + process.OutputDataReceived += (_, args) => { + if (args.Data != null) { + Logger.LogInformation("{prefix} - STDOUT: {output}", logPrefix, args.Data); + } + }; + process.ErrorDataReceived += (_, args) => { + if (args.Data != null) { + Logger.LogInformation("{prefix} STDERR: {output}", logPrefix, args.Data); + } + }; + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + return process; + } + } +} diff --git a/ALttPRandomizer/Service/RandomizeService.cs b/ALttPRandomizer/Service/RandomizeService.cs index 3c845b7..774c246 100644 --- a/ALttPRandomizer/Service/RandomizeService.cs +++ b/ALttPRandomizer/Service/RandomizeService.cs @@ -2,24 +2,18 @@ using ALttPRandomizer.Azure; using ALttPRandomizer.Model; using ALttPRandomizer.Randomizers; - using ALttPRandomizer.Settings; - using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; - using System; using System.Collections.Generic; - using System.Reflection; using System.Text.Json; using System.Threading.Tasks; public class RandomizeService { public RandomizeService( IdGenerator idGenerator, - IServiceProvider serviceProvider, BaseRandomizer baseRandomizer, AzureStorage azureStorage, ILogger logger) { this.IdGenerator = idGenerator; - this.ServiceProvider = serviceProvider; this.BaseRandomizer = baseRandomizer; this.AzureStorage = azureStorage; this.Logger = logger; @@ -29,25 +23,15 @@ private IdGenerator IdGenerator { get; } private BaseRandomizer BaseRandomizer { get; } - private IServiceProvider ServiceProvider { get; } private AzureStorage AzureStorage { get; } public async Task RandomizeSeed(SeedSettings settings, string? seedId = null) { var id = seedId ?? this.IdGenerator.GenerateId(); this.Logger.LogInformation("Generating seed {seedId} with settings {@settings}", id, settings); - var fi = typeof(RandomizerInstance).GetField(settings.Randomizer.ToString(), BindingFlags.Static | BindingFlags.Public); + this.BaseRandomizer.Validate(settings); - var randomizerKey = fi?.GetCustomAttribute()?.Name; - - if (randomizerKey == null) { - throw new InvalidSettingsException("Invalid randomizer: {0}", settings.Randomizer); - } - - var randomizer = this.ServiceProvider.GetRequiredKeyedService(randomizerKey); - randomizer.Validate(settings); - - await randomizer.Randomize(id, settings, seedId == null); + await this.BaseRandomizer.Randomize(id, settings, seedId == null); return id; } diff --git a/ALttPRandomizer/Settings/Attributes.cs b/ALttPRandomizer/Settings/Attributes.cs index f258c4b..881aff9 100644 --- a/ALttPRandomizer/Settings/Attributes.cs +++ b/ALttPRandomizer/Settings/Attributes.cs @@ -3,12 +3,25 @@ using System; using System.Linq; - internal class RandomizerNameAttribute : Attribute { - public RandomizerNameAttribute(string name) { + [AttributeUsage(AttributeTargets.Field)] + internal class GeneratorSettingsAttribute : Attribute { + public GeneratorSettingsAttribute(string name, string prefix, params string[] args) { this.Name = name; + this.Prefix = prefix; + this.Args = args; + } + + public GeneratorSettingsAttribute(string name, string prefix, bool requireFlips, params string[] args) { + this.Name = name; + this.Prefix = prefix; + this.Args = args; + this.RequireFlips = requireFlips; } public string Name { get; } + public string Prefix { get; } + public string[] Args { get; } + public bool RequireFlips { get; } = false; } internal abstract class RandomizerSpecificAttribute : Attribute { diff --git a/ALttPRandomizer/Settings/CommonSettingsProcessor.cs b/ALttPRandomizer/Settings/CommonSettingsProcessor.cs index bc6887e..cbf348f 100644 --- a/ALttPRandomizer/Settings/CommonSettingsProcessor.cs +++ b/ALttPRandomizer/Settings/CommonSettingsProcessor.cs @@ -72,17 +72,17 @@ } } - public string GetRandomizerName(RandomizerInstance randomizer) + internal GeneratorSettingsAttribute GetGeneratorSettings(RandomizerInstance randomizer) { var fi = typeof(RandomizerInstance).GetField(randomizer.ToString(), BindingFlags.Static | BindingFlags.Public); - var randomizerKey = fi?.GetCustomAttribute()?.Name; + var settings = fi?.GetCustomAttribute(); - if (randomizerKey == null) { - throw new InvalidSettingsException("Invalid randomizer: {0}", randomizerKey); + if (settings == null) { + throw new InvalidSettingsException("Invalid randomizer: {0}", randomizer); } - return randomizerKey; + return settings; } } diff --git a/ALttPRandomizer/appsettings.Docker.json b/ALttPRandomizer/appsettings.Docker.json index d48cd73..897b29f 100644 --- a/ALttPRandomizer/appsettings.Docker.json +++ b/ALttPRandomizer/appsettings.Docker.json @@ -14,15 +14,15 @@ "generators": { "base": { "workingDirectory": "/randomizer", - "randomizerCommand": [ "uv", "run", "DungeonRandomizer.py", "--bps", "--spoiler=json" ], + "randomizerCommand": [ "uv", "run", "DungeonRandomizer.py" ], }, "apr2025": { "workingDirectory": "/apr2025_randomizer", - "randomizerCommand": [ "uv", "run", "EntranceRandomizer.py", "--json_spoiler" ], + "randomizerCommand": [ "uv", "run", "EntranceRandomizer.py" ], }, "beta": { "workingDirectory": "/beta_randomizer", - "randomizerCommand": [ "uv", "run", "DungeonRandomizer.py", "--bps", "--spoiler=json" ], + "randomizerCommand": [ "uv", "run", "DungeonRandomizer.py" ], }, } },