271 lines
No EOL
8.4 KiB
C#
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();
|
|
}
|
|
}
|
|
} |