Files
Fishing2/Packages/cn.tuanjie.codely.bridge/Editor/Tools/ExecuteCSharpScript.cs
2026-03-09 17:50:20 +08:00

205 lines
6.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Codely.Newtonsoft.Json.Linq;
using UnityEngine;
using UnityTcp.Editor.Helpers;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
namespace UnityTcp.Editor.Tools
{
/// <summary>
/// Executes C# scripts using Microsoft.CodeAnalysis.CSharp.Scripting (Roslyn).
/// Captures and returns logs generated during script execution.
/// Ensures execution happens on the main thread.
/// </summary>
public static class ExecuteCSharpScript
{
private static List<string> _capturedLogs = new List<string>();
private static bool _isCapturingLogs = false;
/// <summary>
/// Main handler for executing C# scripts.
/// </summary>
public static object HandleCommand(JObject @params)
{
string script = @params["script"]?.ToString();
if (string.IsNullOrEmpty(script))
{
return Response.Error("'script' parameter is required.");
}
bool captureLogs = @params["capture_logs"]?.ToObject<bool>() ?? true;
string[] imports = @params["imports"]?.ToObject<string[]>() ?? new string[]
{
"System",
"System.Linq",
"System.Collections.Generic",
"UnityEngine",
"UnityEditor",
"UnityEditor.SceneManagement",
"UnityEngine.SceneManagement"
};
try
{
Debug.Log($"[ExecuteCSharpScript] Executing C# script (length: {script.Length} chars)");
StartLogCapture(captureLogs);
object result;
try
{
result = ExecuteScriptInternal(script, imports);
}
finally
{
// Always stop log capture, even on error
}
var logs = captureLogs ? StopLogCapture() : new List<string>();
return Response.Success(
"C# script executed successfully.",
new
{
result = result?.ToString(),
logs = logs,
log_count = logs.Count
}
);
}
catch (Exception e)
{
var logs = captureLogs ? StopLogCapture() : new List<string>();
Debug.LogError($"[ExecuteCSharpScript] Failed to execute script: {e}");
return Response.Error(
$"C# script execution failed: {e.Message}",
new
{
logs = logs,
exception = e.ToString()
}
);
}
}
/// <summary>
/// Internal method to execute the script using Roslyn.
/// </summary>
private static object ExecuteScriptInternal(string script, string[] imports)
{
try
{
// Collect assembly references
var references = new List<System.Reflection.Assembly>
{
typeof(UnityEngine.Debug).Assembly,
typeof(UnityEditor.EditorApplication).Assembly
};
// Add Assembly-CSharp if it exists (runtime user code)
var assemblyCSharp = System.AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "Assembly-CSharp");
if (assemblyCSharp != null)
{
references.Add(assemblyCSharp);
}
// Add Assembly-CSharp-Editor if it exists (editor user code)
var assemblyCSharpEditor = System.AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "Assembly-CSharp-Editor");
if (assemblyCSharpEditor != null)
{
references.Add(assemblyCSharpEditor);
}
// Add Unity.InputSystem if it exists (Input System package)
var inputSystemAssembly = System.AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == "Unity.InputSystem");
if (inputSystemAssembly != null)
{
references.Add(inputSystemAssembly);
}
// Create script options with imports
var options = ScriptOptions.Default
.WithReferences(references)
.WithImports(imports);
// Execute the script synchronously
var scriptTask = CSharpScript.EvaluateAsync(script, options);
// Wait for the task to complete
scriptTask.Wait();
return scriptTask.Result;
}
catch (AggregateException ae)
{
// Unwrap AggregateException to get the actual exception
if (ae.InnerException != null)
{
throw ae.InnerException;
}
throw;
}
}
/// <summary>
/// Starts capturing Unity logs.
/// </summary>
private static void StartLogCapture(bool enabled)
{
if (!enabled)
{
_isCapturingLogs = false;
return;
}
_capturedLogs.Clear();
_isCapturingLogs = true;
Application.logMessageReceived += OnLogMessageReceived;
}
/// <summary>
/// Stops capturing logs and returns the captured log list.
/// </summary>
private static List<string> StopLogCapture()
{
Application.logMessageReceived -= OnLogMessageReceived;
_isCapturingLogs = false;
var logs = new List<string>(_capturedLogs);
_capturedLogs.Clear();
return logs;
}
/// <summary>
/// Log message callback handler.
/// </summary>
private static void OnLogMessageReceived(string logString, string stackTrace, LogType type)
{
if (!_isCapturingLogs)
return;
var logEntry = new StringBuilder();
logEntry.Append($"[{type}] {logString}");
// Include stack trace for errors and exceptions
if ((type == LogType.Error || type == LogType.Exception) && !string.IsNullOrEmpty(stackTrace))
{
logEntry.Append($"\n{stackTrace}");
}
_capturedLogs.Add(logEntry.ToString());
}
}
}