Game UI ready
This commit is contained in:
305
Packages/com.merry-yellow.code-assist/Editor/UnityEditorShell.cs
Normal file
305
Packages/com.merry-yellow.code-assist/Editor/UnityEditorShell.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
#pragma warning disable IDE0005
|
||||
using Serilog = Meryel.Serilog;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Meryel.UnityCodeAssist.Editor.Shell
|
||||
{
|
||||
public class UnityEditorShell
|
||||
{
|
||||
public static string DefaultShellApp
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_EDITOR_WIN
|
||||
return "cmd.exe";
|
||||
#elif UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
//return "bash";
|
||||
return System.IO.File.Exists("/bin/zsh") ? "/bin/zsh" : "/bin/bash";
|
||||
#else
|
||||
Serilog.Log.Error("invalid platform");
|
||||
return "invalid-platform";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// we are using unity actions for posterity in case we want to inspect those in-editor someday
|
||||
private static readonly List<UnityAction> ActionsQueue;
|
||||
|
||||
static UnityEditorShell()
|
||||
{
|
||||
ActionsQueue = new List<UnityAction>();
|
||||
EditorApplication.update += OnUpdate;
|
||||
}
|
||||
|
||||
// while running the Unity Editor update loop, we'll unqueue any tasks if such exist.
|
||||
// actions can be
|
||||
private static void OnUpdate()
|
||||
{
|
||||
while (ActionsQueue.Count > 0)
|
||||
{
|
||||
lock (ActionsQueue)
|
||||
{
|
||||
var action = ActionsQueue[0];
|
||||
try
|
||||
{
|
||||
action?.Invoke();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Serilog.Log.Error(e, "error invoking shell action");
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActionsQueue.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Enqueue(UnityAction action)
|
||||
{
|
||||
lock (ActionsQueue)
|
||||
{
|
||||
ActionsQueue.Add(action);
|
||||
}
|
||||
}
|
||||
|
||||
public static ShellCommandEditorToken Execute(string cmd)
|
||||
{
|
||||
var shellCommandEditorToken = new ShellCommandEditorToken();
|
||||
System.Threading.ThreadPool.QueueUserWorkItem(delegate (object state)
|
||||
{
|
||||
Process? process = null;
|
||||
|
||||
try
|
||||
{
|
||||
var processStartInfo = CreateProcessStartInfo(cmd);
|
||||
|
||||
// in case the command was already killed from the editor when the thread was queued
|
||||
if (shellCommandEditorToken.IsKillRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
process = Process.Start(processStartInfo);
|
||||
SetupProcessCallbacks(process, processStartInfo, shellCommandEditorToken);
|
||||
ReadProcessOutput(process, shellCommandEditorToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Serilog.Log.Error(e, "error starting shell");
|
||||
process?.Close();
|
||||
|
||||
Enqueue(() =>
|
||||
{
|
||||
shellCommandEditorToken.FeedLog(UnityShellLogType.Error, e.ToString());
|
||||
shellCommandEditorToken.MarkAsDone(-1);
|
||||
});
|
||||
}
|
||||
});
|
||||
return shellCommandEditorToken;
|
||||
}
|
||||
|
||||
private static ProcessStartInfo CreateProcessStartInfo(string cmd)
|
||||
{
|
||||
var processStartInfo = new ProcessStartInfo(DefaultShellApp);
|
||||
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
|
||||
processStartInfo.Arguments = "-c";
|
||||
#elif UNITY_EDITOR_WIN
|
||||
processStartInfo.Arguments = "/c";
|
||||
#endif
|
||||
|
||||
processStartInfo.Arguments += (" \"" + cmd + " \"");
|
||||
processStartInfo.CreateNoWindow = true;
|
||||
processStartInfo.ErrorDialog = true;
|
||||
processStartInfo.UseShellExecute = false;
|
||||
//processStartInfo.WorkingDirectory = options.WorkingDirectory == null ? "./" : options.WorkingDirectory;
|
||||
processStartInfo.RedirectStandardOutput = true;
|
||||
processStartInfo.RedirectStandardError = true;
|
||||
processStartInfo.RedirectStandardInput = true;
|
||||
processStartInfo.StandardOutputEncoding = Encoding.UTF8;
|
||||
processStartInfo.StandardErrorEncoding = Encoding.UTF8;
|
||||
return processStartInfo;
|
||||
}
|
||||
|
||||
private static void SetupProcessCallbacks(Process process, ProcessStartInfo processStartInfo, ShellCommandEditorToken shellCommandEditorToken)
|
||||
{
|
||||
shellCommandEditorToken.BindProcess(process);
|
||||
|
||||
process.ErrorDataReceived += delegate (object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
Serilog.Log.Error("error on shell.ErrorDataReceived: {data}", e.Data);
|
||||
};
|
||||
process.OutputDataReceived += delegate (object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
Serilog.Log.Debug("shell.OutputDataReceived: {data}", e.Data);
|
||||
};
|
||||
process.Exited += delegate (object sender, System.EventArgs e)
|
||||
{
|
||||
Serilog.Log.Debug("shell.Exited: {data}", e.ToString());
|
||||
};
|
||||
}
|
||||
|
||||
private static void ReadProcessOutput(Process process, ShellCommandEditorToken shellCommandEditorToken)
|
||||
{
|
||||
do
|
||||
{
|
||||
var line = process.StandardOutput.ReadLine();
|
||||
if (line == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
line = line.Replace("\\", "/");
|
||||
Enqueue(delegate () { shellCommandEditorToken.FeedLog(UnityShellLogType.Log, line); });
|
||||
} while (true);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var error = process.StandardError.ReadLine();
|
||||
if (string.IsNullOrEmpty(error))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Enqueue(delegate () { shellCommandEditorToken.FeedLog(UnityShellLogType.Error, error); });
|
||||
}
|
||||
|
||||
process.WaitForExit();
|
||||
var exitCode = process.ExitCode;
|
||||
process.Close();
|
||||
Enqueue(() => { shellCommandEditorToken.MarkAsDone(exitCode); });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ShellCommandEditorToken
|
||||
{
|
||||
public event UnityAction<UnityShellLogType, string>? OnLog;
|
||||
public event UnityAction<int>? OnExit;
|
||||
|
||||
private Process? _process;
|
||||
|
||||
internal void BindProcess(Process process)
|
||||
{
|
||||
_process = process;
|
||||
}
|
||||
|
||||
internal void FeedLog(UnityShellLogType unityShellLogType, string log)
|
||||
{
|
||||
OnLog?.Invoke(unityShellLogType, log);
|
||||
|
||||
if (unityShellLogType == UnityShellLogType.Error)
|
||||
{
|
||||
HasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsKillRequested { get; private set; }
|
||||
|
||||
public void Kill()
|
||||
{
|
||||
if (IsKillRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsKillRequested = true;
|
||||
if (_process != null)
|
||||
{
|
||||
_process.Kill();
|
||||
_process = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
MarkAsDone(137);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasError { get; private set; }
|
||||
|
||||
public int ExitCode { get; private set; }
|
||||
|
||||
public bool IsDone { get; private set; }
|
||||
|
||||
internal void MarkAsDone(int exitCode)
|
||||
{
|
||||
ExitCode = exitCode;
|
||||
IsDone = true;
|
||||
OnExit?.Invoke(exitCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is intended for compiler use. Don't call it in your code.
|
||||
/// </summary>
|
||||
public ShellCommandAwaiter GetAwaiter()
|
||||
{
|
||||
return new ShellCommandAwaiter(this);
|
||||
}
|
||||
}
|
||||
|
||||
public struct ShellCommandAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
|
||||
{
|
||||
private readonly ShellCommandEditorToken _shellCommandEditorToken;
|
||||
|
||||
public ShellCommandAwaiter(ShellCommandEditorToken shellCommandEditorToken)
|
||||
{
|
||||
_shellCommandEditorToken = shellCommandEditorToken;
|
||||
}
|
||||
|
||||
public int GetResult()
|
||||
{
|
||||
return _shellCommandEditorToken.ExitCode;
|
||||
}
|
||||
|
||||
public bool IsCompleted => _shellCommandEditorToken.IsDone;
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
if (IsCompleted)
|
||||
{
|
||||
continuation();
|
||||
}
|
||||
else
|
||||
{
|
||||
_shellCommandEditorToken.OnExit += (_) => { continuation(); };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum UnityShellLogType
|
||||
{
|
||||
Log,
|
||||
Error
|
||||
}
|
||||
|
||||
public class ShellCommandYieldable : CustomYieldInstruction
|
||||
{
|
||||
private readonly ShellCommandEditorToken _shellCommandEditorToken;
|
||||
|
||||
public ShellCommandYieldable(ShellCommandEditorToken shellCommandEditorToken)
|
||||
{
|
||||
_shellCommandEditorToken = shellCommandEditorToken;
|
||||
}
|
||||
|
||||
public override bool keepWaiting => !_shellCommandEditorToken.IsDone;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user