From b503f7458abd09684bc99f85960fb4266f609cc6 Mon Sep 17 00:00:00 2001 From: num0005 Date: Sun, 30 Jun 2024 21:25:13 +0100 Subject: [PATCH] get lightmap logging working for halo 2. --- Launcher/ToolkitInterface/H2Toolkit.cs | 102 +++++++++++++---------- Launcher/ToolkitInterface/HRToolkit.cs | 2 +- Launcher/ToolkitInterface/ToolkitBase.cs | 81 ++++++++++++++++-- Launcher/Utility/PRTInstaller.cs | 2 +- Launcher/Utility/Process.Windows.cs | 68 ++++++++++++--- Launcher/Utility/Process.cs | 18 ++-- 6 files changed, 196 insertions(+), 77 deletions(-) diff --git a/Launcher/ToolkitInterface/H2Toolkit.cs b/Launcher/ToolkitInterface/H2Toolkit.cs index c320e1f..22c235b 100644 --- a/Launcher/ToolkitInterface/H2Toolkit.cs +++ b/Launcher/ToolkitInterface/H2Toolkit.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Threading; using System.Threading.Tasks; using static ToolkitLauncher.ToolkitProfiles; @@ -55,63 +56,73 @@ private static string GetLightmapQuality(LightmapArgs lightmapArgs) public override async Task BuildLightmap(string scenario, string bsp, LightmapArgs args, ICancellableProgress? progress) { - string quality = GetLightmapQuality(args); - - if (args.instanceCount > 1 && (Profile.IsMCC || Profile.CommunityTools)) // multi instance? + LogFolder = $"lightmaps_{Path.GetFileNameWithoutExtension(scenario)}"; + try { - if (progress is not null) - progress.MaxValue += 1 + args.instanceCount; + string quality = GetLightmapQuality(args); - async Task RunInstance(int index) + if (args.instanceCount > 1 && (Profile.IsMCC || Profile.CommunityTools)) // multi instance? { - if (index == 0 && !Profile.IsH2Codez()) // not needed for H2Codez + if (progress is not null) + progress.MaxValue += 1 + args.instanceCount; + + async Task RunInstance(int index) { + if (index == 0 && !Profile.IsH2Codez()) // not needed for H2Codez + { + if (progress is not null) + progress.Status = "Delaying launch of zeroth instance"; + await Task.Delay(1000 * 70, progress.GetCancellationToken()); + } + Utility.Process.Result result = await RunLightmapWorker( + scenario, + bsp, + quality, + args.instanceCount, + index, + args.NoAssert, + progress.GetCancellationToken(), + args.outputSetting + ); + if (result is not null && result.HasErrorOccured) + progress.Cancel($"Tool worker {index} has failed - exit code {result.ReturnCode}"); if (progress is not null) - progress.Status = "Delaying launch of zeroth instance"; - await Task.Delay(1000 * 70, progress.GetCancellationToken()); + progress.Report(1); + } + + var instances = new List(); + for (int i = args.instanceCount - 1; i >= 0; i--) + { + instances.Add(RunInstance(i)); } - Utility.Process.Result result = await RunLightmapWorker( - scenario, - bsp, - quality, - args.instanceCount, - index, - args.NoAssert, - progress.GetCancellationToken(), - args.outputSetting - ); - if (result is not null && result.HasErrorOccured) - progress.Cancel($"Tool worker {index} has failed - exit code {result.ReturnCode}"); + if (progress is not null) + progress.Status = $"Running {args.instanceCount} instances"; + await Task.WhenAll(instances); + if (progress is not null) + progress.Status = "Merging output"; + + if (progress.IsCancelled) + return; + + await RunMergeLightmap(scenario, bsp, args.instanceCount, args.NoAssert); if (progress is not null) progress.Report(1); } - - var instances = new List(); - for (int i = args.instanceCount - 1; i >= 0; i--) + else { - instances.Add(RunInstance(i)); + Debug.Assert(args.instanceCount == 1); // should be one, otherwise we got bad args + if (progress is not null) + { + progress.DisableCancellation(); + progress.MaxValue += 1; + } + await RunTool((args.NoAssert && Profile.IsMCC) ? ToolType.ToolFast : ToolType.Tool, new() { "lightmaps", scenario, bsp, quality }); + if (progress is not null) + progress.Report(1); } - if (progress is not null) - progress.Status = $"Running {args.instanceCount} instances"; - await Task.WhenAll(instances); - if (progress is not null) - progress.Status = "Merging output"; - - await RunMergeLightmap(scenario, bsp, args.instanceCount, args.NoAssert); - if (progress is not null) - progress.Report(1); - } - else + } finally { - Debug.Assert(args.instanceCount == 1); // should be one, otherwise we got bad args - if (progress is not null) - { - progress.DisableCancellation(); - progress.MaxValue += 1; - } - await RunTool((args.NoAssert && Profile.IsMCC) ? ToolType.ToolFast : ToolType.Tool, new() { "lightmaps", scenario, bsp, quality }); - if (progress is not null) - progress.Report(1); + LogFolder = null; } } @@ -143,6 +154,7 @@ private async Task RunMergeLightmap(string scenario, string bsp, int workerCount private async Task RunLightmapWorker(string scenario, string bsp, string quality, int workerCount, int index, bool useFast, CancellationToken cancelationToken, OutputMode output) { + this.LogFileSuffix = $"_{index}"; if (Profile.IsMCC) { bool wereWeExperts = Profile.ElevatedToExpert; diff --git a/Launcher/ToolkitInterface/HRToolkit.cs b/Launcher/ToolkitInterface/HRToolkit.cs index efa1dd7..b11a9c3 100644 --- a/Launcher/ToolkitInterface/HRToolkit.cs +++ b/Launcher/ToolkitInterface/HRToolkit.cs @@ -126,7 +126,7 @@ async Task RunStage(string stage) /// public override async Task ImportModel(string path, ModelCompile importType, bool phantomFix, bool h2SelectionLogic, bool renderPRT, bool FPAnim, string characterFPPath, string weaponFPPath, bool accurateRender, bool verboseAnim, bool uncompressedAnim, bool skyRender, bool PDARender, bool resetCompression, bool autoFBX, bool genShaders) { - List args = new List(); + List args = new(); if (importType.HasFlag(ModelCompile.render)) { // Generate shaders if requested diff --git a/Launcher/ToolkitInterface/ToolkitBase.cs b/Launcher/ToolkitInterface/ToolkitBase.cs index 085cc4c..34c83fa 100644 --- a/Launcher/ToolkitInterface/ToolkitBase.cs +++ b/Launcher/ToolkitInterface/ToolkitBase.cs @@ -340,14 +340,14 @@ protected virtual string ToLocalPath(string path) /// public async Task RunCustomToolCommand(string command) { - await Utility.Process.StartProcessWithShell(BaseDirectory, GetToolExecutable(ToolType.Tool), Utility.Process.EscapeArgList(GetArgsToPrepend()) + " " + command); + await Utility.Process.StartProcessWithShell(BaseDirectory, GetToolExecutable(ToolType.Tool), Utility.Process.EscapeArgList(GetArgsToPrepend(noWindow: false)) + " " + command); } /// /// Get the args to prepend to every invokaing of a game tool /// /// List with all the args to add - private List GetArgsToPrepend() + private List GetArgsToPrepend(bool noWindow) { List args = new(); @@ -383,7 +383,7 @@ private List GetArgsToPrepend() args.Add("-expert_mode"); } - if (Profile.Batch) + if (Profile.Batch || noWindow) { args.Add("-batch"); } @@ -457,9 +457,64 @@ static protected OutputMode GetMoreSilentMode(OutputMode mode) } } + readonly private AsyncLocal _log_folder = new(); + readonly private AsyncLocal _log_file_suffix = new(); + + public string? LogFolder + { + get + { + return _log_folder.Value; + } + set + { + _log_folder.Value = value; + } + } + + public string? LogFileSuffix + { + get + { + return _log_file_suffix.Value; + } + set + { + _log_file_suffix.Value = value; + } + } + public Action? ToolFailure { get; set; } + private string GetLogFileName(List? args) + { + string report_folder = Path.Combine(BaseDirectory, "reports", "Osoyoos"); + string? log_subfolder = LogFolder; + string? log_suffix = LogFileSuffix; + + if (log_subfolder != null) + report_folder = Path.Combine(report_folder, log_subfolder); + + string filename; + + if (args is not null && args.Count > 0) + { + filename = "tool_" + args[0]; + } + else + { + filename = "tool"; + } + + if (log_suffix is not null) + { + filename += log_suffix; + } + + return Path.Combine(report_folder, filename) + ".log"; + } + /// /// Run a tool from the toolkit with arguments /// @@ -471,7 +526,7 @@ static protected OutputMode GetMoreSilentMode(OutputMode mode) /// Results of running the tool if possible public async Task RunTool(ToolType tool, List? args = null, OutputMode? outputMode = null, bool lowPriority = false, CancellationToken cancellationToken = default) { - Utility.Process.Result? result = await RunToolInternal(tool, args, outputMode, cancellationToken, lowPriority); + Utility.Process.Result? result = await RunToolInternal(tool, args, outputMode, lowPriority, cancellationToken); if (result is not null && result.ReturnCode != 0 && ToolFailure is not null) ToolFailure(result); return result; @@ -480,10 +535,13 @@ static protected OutputMode GetMoreSilentMode(OutputMode mode) /// /// Implementation of RunTool /// - private async Task RunToolInternal(ToolType tool, List? args, OutputMode? outputMode, CancellationToken cancellationToken, bool lowPriority) + private async Task RunToolInternal(ToolType tool, List? args, OutputMode? outputMode, bool lowPriority, CancellationToken cancellationToken) { + bool has_window = outputMode != OutputMode.slient && outputMode != OutputMode.logToDisk; + bool enabled_log = outputMode == OutputMode.logToDisk; + // always include the prepend args - List full_args = GetArgsToPrepend(); + List full_args = GetArgsToPrepend(has_window); if (args is not null) full_args.AddRange(args); @@ -491,10 +549,17 @@ static protected OutputMode GetMoreSilentMode(OutputMode mode) if (outputMode is null) outputMode = GetDefaultOutputMode(tool, args); + string? log_path = null; + + if (enabled_log) + { + log_path = GetLogFileName(args); + } + if (outputMode == OutputMode.keepOpen) - return await Utility.Process.StartProcessWithShell(BaseDirectory, tool_path, full_args, cancellationToken, lowPriority); + return await Utility.Process.StartProcessWithShell(BaseDirectory, tool_path, full_args, lowPriority, cancellationToken); else - return await Utility.Process.StartProcess(BaseDirectory, tool_path, full_args, cancellationToken, lowPriority); + return await Utility.Process.StartProcess(BaseDirectory, executable: tool_path, args: full_args, lowPriority: lowPriority, logFileName: log_path, noWindow: !has_window, cancellationToken: cancellationToken); } /// diff --git a/Launcher/Utility/PRTInstaller.cs b/Launcher/Utility/PRTInstaller.cs index 61e6167..6103a9e 100644 --- a/Launcher/Utility/PRTInstaller.cs +++ b/Launcher/Utility/PRTInstaller.cs @@ -127,7 +127,7 @@ private static async Task DoUpdate(string prt_install_path, GitHubReleases await File.WriteAllBytesAsync(redist_executable_path, redist_package, progress.GetCancellationToken()); progress.Status = "Installing redist package!"; - await Process.StartProcess(temp_folder, redist_executable_path, new(), progress.GetCancellationToken(), admin:true); + await Process.StartProcess(temp_folder, redist_executable_path, new(), admin: true, cancellationToken: progress.GetCancellationToken()); progress.Complete = true; progress.Status = IsRedistInstalled() ? "Installed redist package!" : "Failed to install redist package!"; diff --git a/Launcher/Utility/Process.Windows.cs b/Launcher/Utility/Process.Windows.cs index 3047a1a..7c5451f 100644 --- a/Launcher/Utility/Process.Windows.cs +++ b/Launcher/Utility/Process.Windows.cs @@ -3,8 +3,9 @@ using System.Diagnostics; using System.IO; using System.Management; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; +using ToolkitLauncher.ToolkitInterface; namespace ToolkitLauncher.Utility { @@ -33,15 +34,28 @@ private static ProcessPriorityClass LowerPriority(ProcessPriorityClass old) return ProcessPriorityClass.Idle; } } - static public async Task StartProcess(string directory, string executable, List args, CancellationToken cancellationToken, bool lowPriority, bool admin) + static public async Task StartProcess(string directory, string executable, List args, bool lowPriority, bool admin, bool noWindow, string? logFileName, CancellationToken cancellationToken) { try { string executable_path = Path.Combine(directory, executable); ProcessStartInfo info = new(executable_path); info.WorkingDirectory = directory; - // info.RedirectStandardError = true; - // info.RedirectStandardOutput = true; + info.CreateNoWindow = noWindow; + + + bool loggingToDisk = false; + if (!String.IsNullOrWhiteSpace(logFileName)) + { + info.RedirectStandardError = true; + info.RedirectStandardOutput = true; + + string log_folder = Path.GetDirectoryName(logFileName); + Directory.CreateDirectory(log_folder); + loggingToDisk = true; + Debug.Print($"log folder for process {log_folder}"); + } + foreach (string arg in args) info.ArgumentList.Add(arg); @@ -62,14 +76,18 @@ static public async Task StartProcess(string directory, string executabl Debug.Print(ex.ToString()); } } - - //proc.StandardOutput. - //Task standardOut = proc.StandardOutput.ReadToEndAsync(); - //Task standardError = proc.StandardError.ReadToEndAsync(); - //await Task.WhenAll(standardOut, standardError, proc.WaitForExitAsync()); + Task standardOut = null; + Task standardError = null; + try { + if (loggingToDisk) + { + standardOut = proc.StandardOutput.ReadToEndAsync(); + standardError = proc.StandardError.ReadToEndAsync(); + } + await proc.WaitForExitAsync(cancellationToken); } catch (OperationCanceledException) { }; @@ -84,9 +102,33 @@ static public async Task StartProcess(string directory, string executabl Debug.Print(ex.ToString()); } } + + Result results = null; + + if (loggingToDisk) + { + await Task.WhenAll(standardOut, standardError); + + using (StreamWriter file = new(logFileName, append: false)) + { + await file.WriteAsync("=== Error log == \r\n\r\n"); + await file.WriteAsync(standardError.Result); + await file.WriteAsync("\r\n\r\n"); + await file.WriteAsync("=== Output log == \r\n\r\n"); + await file.WriteAsync(standardOut.Result); + } + + results = new Result(standardOut.Result, standardError.Result, proc.ExitCode); + } + else + { + results = new Result("", "", proc.ExitCode); + } + cancellationToken.ThrowIfCancellationRequested(); - //return new Result(standardOut.Result, standardError.Result, proc.ExitCode); - return new Result("", "", proc.ExitCode); + + return results; + } catch (System.ComponentModel.Win32Exception ex) { @@ -97,7 +139,7 @@ static public async Task StartProcess(string directory, string executabl if (ErrorCode == 2) { // todo(num0005) refactor this and move the exception into the Process - throw new ToolkitInterface.ToolkitBase.MissingFile(executable); + throw new ToolkitBase.MissingFile(executable); } else if (ErrorCode == 1223) { @@ -111,7 +153,7 @@ static public async Task StartProcess(string directory, string executabl } } - static public async Task StartProcessWithShell(string directory, string executable, string args, CancellationToken cancellationToken, bool lowPriority) + static public async Task StartProcessWithShell(string directory, string executable, string args, bool lowPriority, CancellationToken cancellationToken) { // build command line string commnad_line = "/c \"" + escape_arg(executable) + " " + args + " & pause\""; diff --git a/Launcher/Utility/Process.cs b/Launcher/Utility/Process.cs index 9324434..82df7ba 100644 --- a/Launcher/Utility/Process.cs +++ b/Launcher/Utility/Process.cs @@ -33,11 +33,11 @@ public bool Success /// Cancellation token for canceling the process before it exists /// Lower priority if possible /// A task that will complete when the executable exits - static public Task StartProcess(string directory, string executable, List args, CancellationToken cancellationToken = default, bool lowPriority = false, bool admin = false) + static public Task StartProcess(string directory, string executable, List args, bool lowPriority = false, bool admin = false, bool noWindow = false, string? logFileName = null, CancellationToken cancellationToken = default) { - Debug.Print($"starting(): directory: {directory}, executable:{executable}, args:{args}, admin: {admin}, low priority {lowPriority}"); + Debug.Print($"starting(): directory: {directory}, executable:{executable}, args:{args}, admin: {admin}, low priority {lowPriority}, noWindow {noWindow} log {logFileName}"); if (OperatingSystem.IsWindows()) - return Windows.StartProcess(directory, executable, args, cancellationToken, lowPriority, admin); + return Windows.StartProcess(directory, executable, args, lowPriority, admin, noWindow, logFileName, cancellationToken); throw new PlatformNotSupportedException(); } @@ -47,14 +47,14 @@ static public Task StartProcess(string directory, string executable, Lis /// Path containing the executable /// unescaped name /// escaped arguments string - /// Cancellation token for canceling the process before it exists /// Lower priority if possible + /// Cancellation token for canceling the process before it exists /// A task that will complete when the executable exits - static public Task StartProcessWithShell(string directory, string executable, string args, CancellationToken cancellationToken = default, bool lowPriority = false) + static public Task StartProcessWithShell(string directory, string executable, string args, bool lowPriority = false, CancellationToken cancellationToken = default) { Debug.Print($"starting_with_shell(): directory: {directory}, executable:{executable}, args:{args}"); if (OperatingSystem.IsWindows()) - return Windows.StartProcessWithShell(directory, executable, args, cancellationToken, lowPriority); + return Windows.StartProcessWithShell(directory, executable, args, lowPriority, cancellationToken); throw new PlatformNotSupportedException(); } @@ -64,12 +64,12 @@ static public Task StartProcess(string directory, string executable, Lis /// Path containing the executable /// unescaped name /// unescaped arguments - /// Cancellation token for canceling the process before it exists /// Lower priority if possible + /// Cancellation token for canceling the process before it exists /// A task that will complete when the executable exits - static public async Task StartProcessWithShell(string directory, string executable, List args, CancellationToken cancellationToken = default, bool lowPriority = false) + static public async Task StartProcessWithShell(string directory, string executable, List args, bool lowPriority = false, CancellationToken cancellationToken = default) { - return await StartProcessWithShell(directory, executable, EscapeArgList(args), cancellationToken, lowPriority); + return await StartProcessWithShell(directory, executable, EscapeArgList(args), lowPriority, cancellationToken); } ///