JD2022-TU1/main/tools/apps/Packager/AssetCooker/Worker.cs

271 lines
No EOL
8.4 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using JD.Core.ProjectManaging;
namespace Packager.AssetCooker
{
public class MessageArgs : EventArgs
{
public string Text { get; private set; }
public MessageArgs(string text)
{
Text = text;
}
}
public class CookMessageArgs : MessageArgs
{
public bool ErrorDescribed { get; private set; }
public CookMessageArgs(string text, bool errorDescribed)
: base(text)
{
ErrorDescribed = errorDescribed;
}
}
public class CookingWorkerPipes
{
internal AnonymousPipeServerStream PipeIn;
internal AnonymousPipeServerStream PipeOut;
internal StreamWriter Writer;
public CookingWorkerPipes()
{
CreatePipes();
}
public void CreatePipes()
{
PipeOut = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable);
PipeIn = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable);
Writer = new StreamWriter(PipeOut) {AutoFlush = true};
}
public void DestroyPipes()
{
if (Writer != null)
{
Writer.Dispose();
Writer = null;
}
if (PipeIn != null)
{
PipeIn.Dispose();
PipeIn = null;
}
if (PipeOut != null)
{
PipeOut.Dispose();
PipeOut = null;
}
}
}
/// <summary>
/// Launches the cookingWorker.exe and executes all the tasks one by one
/// </summary>
public class Worker: IDisposable
{
private readonly cookerWrapper _cooker;
private readonly bool _cookLogicToBinary;
private readonly int _forceMipMap;
private readonly Model _model;
private readonly AutoResetEvent _operationDoneEvent;
private readonly string _platform;
private readonly int _rebaseEngineVersion;
private string _fileBeingProcessed;
public Thread Thread { get; private set; }
public MessageLogger Logger { get; }
public Worker(cookerWrapper cooker, string platform, bool cookLogicToBinary, Model model, int forceMipMap, int rebaseEngineVersion, int id)
{
_platform = platform;
_cookLogicToBinary = cookLogicToBinary;
_cooker = cooker;
_model = model;
_forceMipMap = forceMipMap;
_rebaseEngineVersion = rebaseEngineVersion;
Thread = new Thread(DoWork);
_operationDoneEvent = new AutoResetEvent(false);
Logger = new MessageLogger($"{nameof(Worker)}{id}");
}
public void DoWork()
{
var cookingPipes = new CookingWorkerPipes();
var process = LaunchCookingWorkerProcess(cookingPipes);
var t = _model.Queue.Pop();
while (t != null)
{
// restart the process if needed
if (process.HasExited)
process = RestartCookingWorkerProcess(cookingPipes);
if (Path.IsPathRooted(t.FileToProcess))
{
var message = "Should not use an absolute path " + t.FileToProcess;
Logger.LogError(message);
throw new Exception(message);
}
_fileBeingProcessed = t.FileToProcess;
t.State = Task.TaskState.Cooking;
var watchCook = Stopwatch.StartNew();
var result = CookFile(process, _fileBeingProcessed, cookingPipes);
t.Duration = watchCook.Elapsed;
if (result != 0)
{
t.State = Task.TaskState.Failed;
CleanupAfterError();
}
else
{
t.State = Task.TaskState.Done;
}
// to the next task
t = _model.Queue.Pop();
}
cookingPipes.DestroyPipes();
}
private Process RestartCookingWorkerProcess(CookingWorkerPipes cookingPipes)
{
cookingPipes.DestroyPipes();
cookingPipes.CreatePipes();
return LaunchCookingWorkerProcess(cookingPipes);
}
private Process LaunchCookingWorkerProcess(CookingWorkerPipes cookingPipes)
{
var process = new Process
{
StartInfo =
{
FileName = EnginePath.FindExecutable("CookingWorker.exe"),
Arguments =
$"{cookingPipes.PipeOut.GetClientHandleAsString()} {cookingPipes.PipeIn.GetClientHandleAsString()} \"{_cooker.getDataRoot()}\" \"{_platform}\"",
UseShellExecute = false,
CreateNoWindow = true,
StandardErrorEncoding = Encoding.GetEncoding("iso-8859-1"),
StandardOutputEncoding = Encoding.GetEncoding("iso-8859-1"),
RedirectStandardError = true,
RedirectStandardOutput = true,
}
};
process.StartInfo.Arguments += $" cooklogictobinary={_cookLogicToBinary}";
process.StartInfo.Arguments += $" forceMipMap={_forceMipMap}";
process.StartInfo.Arguments += $" rebaseEngineVersion={_rebaseEngineVersion}";
process.ErrorDataReceived += ProcessErrorHandler;
Logger.LogInfo($"Starting process {process.StartInfo.FileName} {process.StartInfo.Arguments}");
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
return process;
}
private int CookFile(Process process, string filepath, CookingWorkerPipes cookingPipes)
{
cookingPipes.Writer.WriteLine(filepath);
var result = new byte[] {0xFF};
cookingPipes.PipeIn.BeginRead(result, 0, 1, delegate(IAsyncResult ar)
{
try
{
_operationDoneEvent.Set();
cookingPipes.PipeIn.EndRead(ar);
}
catch (Exception e)
{
// The worker has probably already terminated, ignore the exception.
Logger.LogException(e);
}
}, _operationDoneEvent);
while (!_operationDoneEvent.WaitOne(500))
{
if (process.HasExited)
{
return -1;
}
}
return result[0];
}
private void CleanupAfterError()
{
// Delete the cooked file
var filenameCooked = "";
_cooker.getCookedNamePlatform(_fileBeingProcessed, _platform, ref filenameCooked);
var filenameToDelete = Path.Combine(_cooker.getDataRoot(), filenameCooked);
try
{
if (File.Exists(filenameToDelete))
{
File.Delete(filenameToDelete);
Logger.LogInfo($"Deleted failed cooked file at \"{filenameToDelete}\"");
}
}
catch (Exception ex)
{
Logger.LogException(ex);
}
var fileExists = File.Exists(Path.Combine(_cooker.getDataRoot(), _fileBeingProcessed)) ? "" : "(File doesn't exist)";
var absPath = _fileBeingProcessed.Contains(':') ? "(Absolute path!)" : "";
Logger.LogError($"Failed to cook the file \"{_fileBeingProcessed}\" on {_platform} {fileExists}{absPath}");
}
private void ProcessErrorHandler(object sender, DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data))
{
return;
}
if (e.Data.Contains(" WARN "))
{
Logger.LogWarning($"Warning received from Cooking Worker: \"{e.Data}\"");
}
else
{
Logger.LogError($"Error received from Cooking Worker: \"{e.Data}\"");
}
}
public void Dispose()
{
_cooker?.Dispose();
_operationDoneEvent?.Dispose();
}
}
}