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

156 lines
6.2 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MCPForUnity.Editor.Helpers;
using Newtonsoft.Json.Linq;
using Unity.Profiling;
using Unity.Profiling.LowLevel.Unsafe;
using UnityEditor;
namespace MCPForUnity.Editor.Tools.Profiler
{
internal static class CounterOps
{
internal static async Task<object> GetCountersAsync(JObject @params)
{
var p = new ToolParams(@params);
var categoryResult = p.GetRequired("category");
if (!categoryResult.IsSuccess)
return new ErrorResponse(categoryResult.ErrorMessage);
string categoryName = categoryResult.Value;
var resolved = ResolveCategory(categoryName, out string categoryError);
if (resolved == null)
return new ErrorResponse(categoryError);
ProfilerCategory category = resolved.Value;
// Get counter names: explicit list or discover all in category
var counterNames = GetRequestedCounters(p, category);
if (counterNames.Count == 0)
return new SuccessResponse($"No counters found in category '{categoryName}'.", new
{
category = categoryName,
counters = new Dictionary<string, object>()
});
// Start recorders
var recorders = new List<ProfilerRecorder>();
foreach (string name in counterNames)
{
recorders.Add(ProfilerRecorder.StartNew(category, name));
}
var data = new Dictionary<string, object>();
try
{
// Wait 1 frame for recorders to accumulate data
await WaitOneFrameAsync();
// Read values — use GetSample(0) for last completed frame data;
// CurrentValue is always 0 for per-frame render counters.
for (int i = 0; i < recorders.Count; i++)
{
var recorder = recorders[i];
string name = counterNames[i];
long value = 0;
if (recorder.Valid && recorder.Count > 0)
value = recorder.GetSample(0).Value;
else if (recorder.Valid)
value = recorder.CurrentValue;
data[name] = value;
data[name + "_valid"] = recorder.Valid;
data[name + "_unit"] = recorder.Valid ? recorder.UnitType.ToString() : "Unknown";
}
}
finally
{
foreach (var recorder in recorders)
recorder.Dispose();
}
return new SuccessResponse($"Captured {counterNames.Count} counter(s) from '{categoryName}'.", new
{
category = categoryName,
counters = data,
});
}
private static List<string> GetRequestedCounters(ToolParams p, ProfilerCategory category)
{
var explicitCounters = p.GetStringArray("counters");
if (explicitCounters != null && explicitCounters.Length > 0)
return explicitCounters.ToList();
var allHandles = new List<ProfilerRecorderHandle>();
ProfilerRecorderHandle.GetAvailable(allHandles);
return allHandles
.Select(h => ProfilerRecorderHandle.GetDescription(h))
.Where(d => string.Equals(d.Category.Name, category.Name, StringComparison.OrdinalIgnoreCase))
.Select(d => d.Name)
.OrderBy(n => n)
.ToList();
}
private static Task WaitOneFrameAsync()
{
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
int remaining = 2;
void Tick()
{
if (--remaining > 0) return;
EditorApplication.update -= Tick;
tcs.TrySetResult(true);
}
EditorApplication.update += Tick;
try { EditorApplication.QueuePlayerLoopUpdate(); } catch { /* throttled editor */ }
return tcs.Task;
}
private static readonly string[] ValidCategories = new[]
{
"Render", "Scripts", "Memory", "Physics",
#if UNITY_2022_2_OR_NEWER
"Physics2D",
#endif
"Animation",
"Audio", "Lighting", "Network", "Gui", "UI", "Ai", "Video",
"Loading", "Input", "Vr", "Internal", "Particles", "FileIO", "VirtualTexturing"
};
internal static ProfilerCategory? ResolveCategory(string name, out string error)
{
error = null;
switch (name.ToLowerInvariant())
{
case "render": return ProfilerCategory.Render;
case "scripts": return ProfilerCategory.Scripts;
case "memory": return ProfilerCategory.Memory;
case "physics": return ProfilerCategory.Physics;
#if UNITY_2022_2_OR_NEWER
case "physics2d": return ProfilerCategory.Physics2D;
#endif
case "animation": return ProfilerCategory.Animation;
case "audio": return ProfilerCategory.Audio;
case "lighting": return ProfilerCategory.Lighting;
case "network": return ProfilerCategory.Network;
case "gui": case "ui": return ProfilerCategory.Gui;
case "ai": return ProfilerCategory.Ai;
case "video": return ProfilerCategory.Video;
case "loading": return ProfilerCategory.Loading;
case "input": return ProfilerCategory.Input;
case "vr": return ProfilerCategory.Vr;
case "internal": return ProfilerCategory.Internal;
case "particles": return ProfilerCategory.Particles;
case "fileio": return ProfilerCategory.FileIO;
case "virtualtexturing": return ProfilerCategory.VirtualTexturing;
default:
error = $"Unknown category '{name}'. Valid: {string.Join(", ", ValidCategories)}";
return null;
}
}
}
}