From 3242b2df703edfc1d340ba814896aeebbc0ce3bd Mon Sep 17 00:00:00 2001
From: av-romann <74187241+av-romann@users.noreply.github.com>
Date: Wed, 9 Mar 2022 16:58:18 +1000
Subject: [PATCH] addition of multi-render app support
the configuration and rendering has been updated to allow for an arbitrary selection of multiple render tools, with independent render control.
there's some small pre-work done to allow selective triggering of render apps via the console, but that part hasn't been implemented yet.
there are also some console output changes to better distinguish which component is doing the reporting.
---
src/Automation/BackupManager.cs | 4 +-
src/Automation/RenderManager.cs | 272 +++++++++++++++++---------------
src/Program.cs | 80 +++++-----
src/RunConfiguration.cs | 10 +-
4 files changed, 196 insertions(+), 170 deletions(-)
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;
}