diff --git a/src/Automation/BackupManager.cs b/src/Automation/BackupManager.cs index 981f6cb..4187489 100644 --- a/src/Automation/BackupManager.cs +++ b/src/Automation/BackupManager.cs @@ -15,6 +15,8 @@ public class BackupManager : Manager public RunConfiguration RunConfig; ///Time in milliseconds to wait until sending next save query command to ProcessManager's process public int QueryTimeout { get; set; } = 500; + private string _tag = "[ VELLUM:BACKUP ] "; + #region PLUGIN public Version Version { get; } @@ -48,7 +50,7 @@ public BackupManager(ProcessManager p, RunConfiguration runConfig) public void CreateWorldBackup(string worldPath, string destinationPath, bool fullCopy, bool archive) { Processing = true; - + Log(String.Format("{0}Creating initial temporary copy of world directory...", _tag)); CallHook((byte)Hook.BEGIN); #region PRE EXEC diff --git a/src/Automation/RenderManager.cs b/src/Automation/RenderManager.cs index 4cc6f2d..53c6581 100644 --- a/src/Automation/RenderManager.cs +++ b/src/Automation/RenderManager.cs @@ -1,129 +1,145 @@ -using System; -using System.IO; -using System.Diagnostics; -using System.Collections.Generic; -using Vellum.Extension; - -namespace Vellum.Automation -{ - public class RenderManager : Manager - { - private ProcessManager _bds; - private Process _renderer; - public RunConfiguration RunConfig; - - #region PLUGIN - public Version Version { get; } - public enum Hook - { - BEGIN, - ABORT, - NEXT, - END - } - #endregion - - public RenderManager(ProcessManager p, RunConfiguration runConfig) - { - _bds = p; - RunConfig = runConfig; - } - - public void Start(string worldPath) - { - Processing = true; - - // Send tellraw message 1/2 - _bds.SendTellraw("Rendering map..."); - - Log(String.Format("{0}Initializing map rendering...", _tag)); - - CallHook((byte)Hook.BEGIN); - - // Create temporary copy of latest backup to initiate render on - string prfx = "_"; - string tempPathCopy = worldPath.Replace(Path.GetFileName(worldPath), prfx + Path.GetFileName(worldPath)); - BackupManager.CopyDirectory(worldPath, tempPathCopy); - - // Prepare map render output directory - if (!Directory.Exists(RunConfig.Renders.PapyrusOutputPath)) - { - Directory.CreateDirectory(RunConfig.Renders.PapyrusOutputPath); - } - - for (int i = 0; i < RunConfig.Renders.PapyrusTasks.Length; i++) - { - Dictionary placeholderReplacements = new Dictionary() - { - { "$WORLD_PATH", String.Format("\"{0}\"", tempPathCopy) }, - { "$OUTPUT_PATH", String.Format("\"{0}\"", RunConfig.Renders.PapyrusOutputPath) }, - { "${WORLD_PATH}", String.Format("\"{0}\"", tempPathCopy) }, - { "${OUTPUT_PATH}", String.Format("\"{0}\"", RunConfig.Renders.PapyrusOutputPath) } - }; - - string args = RunConfig.Renders.PapyrusGlobalArgs; - - foreach (KeyValuePair kv in placeholderReplacements) - args = args.Replace(kv.Key, kv.Value); - - _renderer = new Process(); - _renderer.StartInfo.FileName = RunConfig.Renders.PapyrusBinPath; - _renderer.StartInfo.WorkingDirectory = Path.GetDirectoryName(RunConfig.Renders.PapyrusBinPath); - _renderer.StartInfo.Arguments = $"{args} {RunConfig.Renders.PapyrusTasks[i]}"; - _renderer.StartInfo.RedirectStandardOutput = RunConfig.HideStdout; - _renderer.StartInfo.RedirectStandardInput = true; - - Log(String.Format("{0}{1}Rendering map {2}/{3}...", _tag, _indent, i + 1, RunConfig.Renders.PapyrusTasks.Length)); - - // To pre-emptively start a process with defined priority you need to set calling process to said priority. - Process parentProcess = Process.GetCurrentProcess(); - ProcessPriorityClass parentPriority = parentProcess.PriorityClass; - if(RunConfig.Renders.LowPriority) - { - parentProcess.PriorityClass = ProcessPriorityClass.Idle; - } - - _renderer.Start(); - - CallHook((byte)Hook.NEXT, new HookEventArgs() { Attachment = i }); - - if(RunConfig.Renders.LowPriority) - { - // Set back parent process to original priority - parentProcess.PriorityClass = parentPriority; - } - - _renderer.WaitForExit(); - } - - Log(String.Format("{0}{1}Cleaning up...", _tag, _indent)); - - Directory.Delete(tempPathCopy, true); - - Log(String.Format("{0}Rendering done!", _tag, _indent)); - - // Send tellraw message 2/2 - _bds.SendTellraw("Done rendering!"); - - CallHook((byte)Hook.END); - - Processing = false; - } - - public bool Abort() - { - bool result = false; - if (_renderer != null) - { - _renderer.Kill(); - result = true; - } else { - result = false; - } - - CallHook((byte)Hook.ABORT, new HookEventArgs() { Attachment = result }); - - return result; - } - } +using System; +using System.IO; +using System.Diagnostics; +using System.Collections.Generic; +using Vellum.Extension; + +namespace Vellum.Automation +{ + public class RenderManager : Manager + { + private ProcessManager _bds; + private Process _renderer; + public RunConfiguration RunConfig; + private string _tag = "[ VELLUM:RENDER ] "; + + #region PLUGIN + public Version Version { get; } + public enum Hook + { + BEGIN, + ABORT, + NEXT, + END + } + #endregion + + public RenderManager(ProcessManager p, RunConfiguration runConfig) + { + _bds = p; + RunConfig = runConfig; + } + + + public void Start(string worldPath, string keyFilter = "(.)") + { + Processing = true; + + // Send tellraw message 1/2 + _bds.SendTellraw("Rendering map..."); + + Log(String.Format("{0}Initializing map rendering...", _tag)); + + CallHook((byte)Hook.BEGIN); + + // Create temporary copy of latest backup to initiate render on + string prfx = "_"; + string tempPathCopy = worldPath.Replace(Path.GetFileName(worldPath), prfx + Path.GetFileName(worldPath)); + BackupManager.CopyDirectory(worldPath, tempPathCopy); + + + // Allow multiple external applications that use the same temporary copy in sequence and iterate through them, skipping over disabled engines. + RenderConfig RenderApp; + foreach(KeyValuePair renderEntry in RunConfig.Renders) + { + RenderApp = renderEntry.Value; + + // Global render settings won't be executed, non-existing apps and disabled items will be skipped, and optionally only filtered and active items will run. + if (renderEntry.Key != "Global" && System.Text.RegularExpressions.Regex.IsMatch(renderEntry.Key,keyFilter) && File.Exists(RenderApp.RenderAppBinPath) && RenderApp.EnableRenders ) + { + + // Prepare map render output directory + if (!Directory.Exists(RenderApp.RenderAppOutputPath)) + { + Directory.CreateDirectory(RenderApp.RenderAppOutputPath); + } + + // Go through this renders task list + for (int i = 0; i < RenderApp.RenderAppTasks.Length; i++) + { + Dictionary placeholderReplacements = new Dictionary() + { + { "$WORLD_PATH", String.Format("\"{0}\"", tempPathCopy) }, + { "$OUTPUT_PATH", String.Format("\"{0}\"", RenderApp.RenderAppOutputPath) }, + { "${WORLD_PATH}", String.Format("\"{0}\"", tempPathCopy) }, + { "${OUTPUT_PATH}", String.Format("\"{0}\"", RenderApp.RenderAppOutputPath) } + }; + + string args = RenderApp.RenderAppGlobalArgs; + + foreach (KeyValuePair kv in placeholderReplacements) + args = args.Replace(kv.Key, kv.Value); + + _renderer = new Process(); + _renderer.StartInfo.FileName = RenderApp.RenderAppBinPath; + _renderer.StartInfo.WorkingDirectory = Path.GetDirectoryName(RenderApp.RenderAppBinPath); + _renderer.StartInfo.Arguments = $"{args} {RenderApp.RenderAppTasks[i]}"; + _renderer.StartInfo.RedirectStandardOutput = RunConfig.HideStdout; + _renderer.StartInfo.RedirectStandardInput = true; + + Log(String.Format("{0}{1}Rendering map {2}/{3}...", _tag, _indent, i + 1, RenderApp.RenderAppTasks.Length)); + + // To pre-emptively start a process with defined priority you need to set calling process to said priority. + Process parentProcess = Process.GetCurrentProcess(); + ProcessPriorityClass parentPriority = parentProcess.PriorityClass; + if(RenderApp.LowPriority) + { + parentProcess.PriorityClass = ProcessPriorityClass.Idle; + } + + _renderer.Start(); + // TODO: needs a try /catch block to handle sub-process failure events (e.g. cleanup/recover) without killing the BDS server, since they don't interact + CallHook((byte)Hook.NEXT, new HookEventArgs() { Attachment = i }); + + if(RenderApp.LowPriority) + { + // Set back parent process to original priority + parentProcess.PriorityClass = parentPriority; + } + + _renderer.WaitForExit(); + } + } + } + + Log(String.Format("{0}{1}Cleaning up...", _tag, _indent)); + + Directory.Delete(tempPathCopy, true); + + Log(String.Format("{0}Rendering done!", _tag, _indent)); + + // Send tellraw message 2/2 + _bds.SendTellraw("Done rendering!"); + + CallHook((byte)Hook.END); + + Processing = false; + } + + public bool Abort() + { + bool result = false; + if (_renderer != null) + { + _renderer.Kill(); + result = true; + } else { + result = false; + } + + CallHook((byte)Hook.ABORT, new HookEventArgs() { Attachment = result }); + + return result; + } + } } \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index 2f73ef5..6982ee5 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -136,13 +136,29 @@ public VellumHost(string[] args) } } #endregion - - if (RunConfig.Renders.EnableRenders && String.IsNullOrWhiteSpace(RunConfig.Renders.PapyrusBinPath)) - { - Console.WriteLine("Disabling renders because no valid path to a Papyrus executable has been specified"); - RunConfig.Renders.EnableRenders = false; - } - + // we report the active render applications + RenderConfig RenderApp; + foreach(KeyValuePair renderEntry in RunConfig.Renders) + { + RenderApp = renderEntry.Value; + if (RenderApp.EnableRenders && !String.IsNullOrWhiteSpace(RenderApp.RenderAppBinPath)) + { + Console.WriteLine("\t{0} is activated as a renderer, with {1} task(s)", RenderApp.RenderAppBinPath,RenderApp.RenderAppTasks.Length); + RenderApp.EnableRenders = true; + } + } + + // Render interval is taken from the "Global" renderapp settings. Currently per-app settings are ignored. Multi-threading would require independent temp copies of the world. + if (RunConfig.Renders["Global"].EnableRenders) + { + System.Timers.Timer renderIntervalTimer = new System.Timers.Timer(RunConfig.Renders["Global"].RenderInterval * 60000); + renderIntervalTimer.AutoReset = true; + renderIntervalTimer.Elapsed += (object sender, ElapsedEventArgs e) => + { + InvokeRender(worldPath, tempWorldPath); + }; + renderIntervalTimer.Start(); + } #region BDS process and input thread ProcessStartInfo serverStartInfo = new ProcessStartInfo() @@ -354,22 +370,9 @@ public VellumHost(string[] args) }); } - // Render interval - if (RunConfig.Renders.EnableRenders) - { - System.Timers.Timer renderIntervalTimer = new System.Timers.Timer(RunConfig.Renders.RenderInterval * 60000); - renderIntervalTimer.AutoReset = true; - renderIntervalTimer.Elapsed += (object sender, ElapsedEventArgs e) => - { - InvokeRender(worldPath, tempWorldPath); - }; - renderIntervalTimer.Start(); - } - if (backupOnStartup) { // Create initial world backup - Console.WriteLine("Creating initial temporary copy of world directory..."); _backupManager.CreateWorldBackup(worldPath, tempWorldPath, true, false); // If "StopBeforeBackup" is set to "true" this will also automatically start the server when it's done } @@ -546,20 +549,25 @@ public VellumHost(string[] args) PreExec = "", PostExec = "", }, - Renders = new RenderConfig() - { - EnableRenders = true, - RenderInterval = 180, - PapyrusBinPath = "", - PapyrusGlobalArgs = "-w $WORLD_PATH -o $OUTPUT_PATH --htmlfile index.html -f png -q 100 --deleteexistingupdatefolder", - PapyrusTasks = new string[] { - "--dim 0", - "--dim 1", - "--dim 2" - }, - PapyrusOutputPath = "", - LowPriority = false - }, + Renders = new Dictionary() + { + { "Global", + new RenderConfig() + { + EnableRenders = true, + RenderInterval = 180, + RenderAppBinPath = "", + RenderAppGlobalArgs = "-w $WORLD_PATH -o $OUTPUT_PATH --htmlfile index.html -f png -q 100 --deleteexistingupdatefolder", + RenderAppTasks = new string[] { + "--dim 0", + "--dim 1", + "--dim 2" + }, + RenderAppOutputPath = "", + LowPriority = false + } + } + }, QuietMode = false, HideStdout = true, BusyCommands = true, @@ -583,7 +591,7 @@ public void InvokeBackup(string worldPath, string tempWorldPath) } else { - if (!RunConfig.QuietMode) { Console.WriteLine("A backup task is still running."); } + if (!RunConfig.QuietMode) { Console.WriteLine("A backup (or render) task is still running."); } } } @@ -596,7 +604,7 @@ public void InvokeRender(string worldPath, string tempWorldPath) } else { - if (!RunConfig.QuietMode) { Console.WriteLine("A render task is still running."); } + if (!RunConfig.QuietMode) { Console.WriteLine("A render (or backup) task is still running."); } } } diff --git a/src/RunConfiguration.cs b/src/RunConfiguration.cs index ecceb50..1d4f47f 100644 --- a/src/RunConfiguration.cs +++ b/src/RunConfiguration.cs @@ -4,7 +4,7 @@ public class RunConfiguration { public string BdsBinPath; public BackupConfig Backups; - public RenderConfig Renders; + public System.Collections.Generic.Dictionary Renders; public bool QuietMode; public bool HideStdout; public bool BusyCommands; @@ -32,11 +32,11 @@ public class BackupConfig public class RenderConfig { public bool EnableRenders; - public string PapyrusBinPath; - public string PapyrusOutputPath; + public string RenderAppBinPath; + public string RenderAppOutputPath; public double RenderInterval; - public string PapyrusGlobalArgs; - public string[] PapyrusTasks; + public string RenderAppGlobalArgs; + public string[] RenderAppTasks; public bool LowPriority; }