156 lines
6.2 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|