Files
coplaydev--unity-mcp/MCPForUnity/Editor/Tools/Profiler/Operations/FrameDebuggerOps.cs
T
2026-07-13 12:49:17 +08:00

261 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Reflection;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using UnityEditor;
namespace MCPForUnity.Editor.Tools.Profiler
{
internal static class FrameDebuggerOps
{
private static readonly Type UtilType;
private static readonly PropertyInfo EventCountProp;
private static readonly MethodInfo EnableMethod;
private static readonly MethodInfo GetFrameEventsMethod;
private static readonly MethodInfo GetEventDataMethod;
private static readonly MethodInfo GetEventInfoNameMethod;
private static readonly Type EventDataType;
private static readonly bool Available;
static FrameDebuggerOps()
{
try
{
// Unity 6+: moved to FrameDebuggerInternal sub-namespace
UtilType = Type.GetType("UnityEditorInternal.FrameDebuggerInternal.FrameDebuggerUtility, UnityEditor");
// Unity 20212022: original location
UtilType ??= Type.GetType("UnityEditorInternal.FrameDebuggerUtility, UnityEditor");
if (UtilType == null) return;
EventCountProp = UtilType.GetProperty("count", BindingFlags.Public | BindingFlags.Static)
?? UtilType.GetProperty("eventsCount", BindingFlags.Public | BindingFlags.Static);
EnableMethod = UtilType.GetMethod("SetEnabled", BindingFlags.Public | BindingFlags.Static,
null, new[] { typeof(bool), typeof(int) }, null)
?? UtilType.GetMethod("SetEnabled", BindingFlags.Public | BindingFlags.Static);
GetFrameEventsMethod = UtilType.GetMethod("GetFrameEvents", BindingFlags.Public | BindingFlags.Static);
GetEventInfoNameMethod = UtilType.GetMethod("GetFrameEventInfoName", BindingFlags.Public | BindingFlags.Static);
// Unity 6: GetFrameEventData(int, FrameDebuggerEventData) — 2 params, returns bool
// Older: GetFrameEventData(int) — 1 param, returns event data object
EventDataType = Type.GetType("UnityEditorInternal.FrameDebuggerInternal.FrameDebuggerEventData, UnityEditor")
?? Type.GetType("UnityEditorInternal.FrameDebuggerEventData, UnityEditor");
if (EventDataType != null)
{
GetEventDataMethod = UtilType.GetMethod("GetFrameEventData", BindingFlags.Public | BindingFlags.Static,
null, new[] { typeof(int), EventDataType }, null);
}
GetEventDataMethod ??= UtilType.GetMethod("GetFrameEventData", BindingFlags.Public | BindingFlags.Static);
Available = EventCountProp != null && EnableMethod != null;
}
catch
{
Available = false;
}
}
internal static object Enable(JObject @params)
{
if (!Available)
return new ErrorResponse("FrameDebuggerUtility not found via reflection.");
// Open the Frame Debugger window (required for event capture)
EditorApplication.ExecuteMenuItem("Window/Analysis/Frame Debugger");
// Frame Debugger requires game to be paused before enabling to capture events.
if (EditorApplication.isPlaying && !EditorApplication.isPaused)
{
return new ErrorResponse(
"Game must be paused before enabling Frame Debugger. "
+ "Call manage_editor action=pause first, then retry frame_debugger_enable.");
}
try
{
InvokeSetEnabled(true);
}
catch (Exception ex)
{
return new ErrorResponse($"Failed to enable Frame Debugger: {ex.Message}");
}
int eventCount = GetEventCount();
return new SuccessResponse("Frame Debugger enabled.", new
{
enabled = true,
event_count = eventCount,
});
}
internal static object Disable(JObject @params)
{
if (!Available)
return new ErrorResponse("FrameDebuggerUtility not found via reflection.");
try
{
InvokeSetEnabled(false);
}
catch (Exception ex)
{
return new ErrorResponse($"Failed to disable Frame Debugger: {ex.Message}");
}
return new SuccessResponse("Frame Debugger disabled.", new { enabled = false });
}
internal static object GetEvents(JObject @params)
{
if (!Available)
return new ErrorResponse("FrameDebuggerUtility not found via reflection.");
var p = new ToolParams(@params);
int pageSize = p.GetInt("page_size") ?? 50;
int cursor = p.GetInt("cursor") ?? 0;
int totalEvents = GetEventCount();
if (totalEvents == 0)
{
return new SuccessResponse("Frame Debugger has no events. Is it enabled?", new
{
events = new List<object>(),
total_events = 0,
});
}
// Try GetFrameEvents() for the event descriptor array (has type/name info)
object[] frameEvents = null;
if (GetFrameEventsMethod != null)
{
try
{
var raw = GetFrameEventsMethod.Invoke(null, null);
if (raw is Array arr)
{
frameEvents = new object[arr.Length];
arr.CopyTo(frameEvents, 0);
}
}
catch { /* fall through */ }
}
var events = new List<object>();
int end = Math.Min(cursor + pageSize, totalEvents);
for (int i = cursor; i < end; i++)
{
var entry = new Dictionary<string, object> { ["index"] = i };
// Get event name
if (GetEventInfoNameMethod != null)
{
try { entry["name"] = (string)GetEventInfoNameMethod.Invoke(null, new object[] { i }); }
catch { /* skip */ }
}
// Get fields from FrameDebuggerEvent descriptor
if (frameEvents != null && i < frameEvents.Length)
{
var desc = frameEvents[i];
var descType = desc.GetType();
TryAddField(descType, desc, "type", entry, "event_type");
TryAddField(descType, desc, "gameObjectInstanceID", entry);
}
// Get detailed event data
if (GetEventDataMethod != null)
{
try
{
var paramInfos = GetEventDataMethod.GetParameters();
object eventData;
if (paramInfos.Length == 2 && EventDataType != null)
{
// Unity 6: bool GetFrameEventData(int, FrameDebuggerEventData)
eventData = Activator.CreateInstance(EventDataType);
var args = new object[] { i, eventData };
var ok = GetEventDataMethod.Invoke(null, args);
eventData = (ok is true) ? args[1] : null;
}
else
{
// Older: FrameDebuggerEventData GetFrameEventData(int)
eventData = GetEventDataMethod.Invoke(null, new object[] { i });
}
if (eventData != null)
{
var edType = eventData.GetType();
TryAddField(edType, eventData, "shaderName", entry);
TryAddField(edType, eventData, "passName", entry);
TryAddField(edType, eventData, "rtName", entry);
TryAddField(edType, eventData, "rtWidth", entry);
TryAddField(edType, eventData, "rtHeight", entry);
TryAddField(edType, eventData, "vertexCount", entry);
TryAddField(edType, eventData, "indexCount", entry);
TryAddField(edType, eventData, "instanceCount", entry);
TryAddField(edType, eventData, "meshName", entry);
}
}
catch { /* skip event data for this index */ }
}
events.Add(entry);
}
var result = new Dictionary<string, object>
{
["events"] = events,
["total_events"] = totalEvents,
["page_size"] = pageSize,
["cursor"] = cursor,
};
if (end < totalEvents)
result["next_cursor"] = end;
return new SuccessResponse($"Frame Debugger events {cursor}-{end - 1} of {totalEvents}.", result);
}
private static void InvokeSetEnabled(bool value)
{
int paramCount = EnableMethod.GetParameters().Length;
if (paramCount == 2)
EnableMethod.Invoke(null, new object[] { value, 0 });
else if (paramCount == 1)
EnableMethod.Invoke(null, new object[] { value });
else
throw new InvalidOperationException($"SetEnabled has unexpected {paramCount} parameters.");
}
private static int GetEventCount()
{
try { return (int)EventCountProp.GetValue(null); }
catch { return 0; }
}
private static void TryAddField(Type type, object obj, string fieldName, Dictionary<string, object> dict, string outputKey = null)
{
try
{
var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance)
?? type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
var prop = type.GetProperty(fieldName, BindingFlags.Public | BindingFlags.Instance)
?? type.GetProperty(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
object val = field != null ? field.GetValue(obj)
: prop != null ? prop.GetValue(obj)
: null;
if (val != null)
dict[outputKey ?? fieldName] = val.GetType().IsEnum ? val.ToString() : val;
}
catch { /* skip unavailable fields */ }
}
}
}