using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEngine; namespace MCPForUnity.Editor.Tools.Profiler { internal static class MemorySnapshotOps { private static readonly Type MemoryProfilerType = Type.GetType("Unity.Profiling.Memory.MemoryProfiler, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.Profiling.Memory.Experimental.MemoryProfiler, UnityEngine.CoreModule") ?? Type.GetType("Unity.MemoryProfiler.MemoryProfiler, Unity.MemoryProfiler.Editor"); private static bool HasPackage => MemoryProfilerType != null; internal static async Task TakeSnapshotAsync(JObject @params) { if (!HasPackage) return PackageMissingError(); var p = new ToolParams(@params); string snapshotPath = p.Get("snapshot_path"); if (string.IsNullOrEmpty(snapshotPath)) { string dir = Path.Combine(Application.temporaryCachePath, "MemoryCaptures"); Directory.CreateDirectory(dir); snapshotPath = Path.Combine(dir, $"snapshot_{DateTime.Now:yyyyMMdd_HHmmss}.snap"); } var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); try { var debugScreenCaptureType = Type.GetType( "Unity.Profiling.DebugScreenCapture, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.Profiling.Experimental.DebugScreenCapture, UnityEngine.CoreModule") ?? Type.GetType("Unity.Profiling.Memory.Experimental.DebugScreenCapture, Unity.MemoryProfiler.Editor"); var captureFlagsType = Type.GetType( "Unity.Profiling.Memory.CaptureFlags, UnityEngine.CoreModule") ?? Type.GetType("UnityEngine.Profiling.Memory.Experimental.CaptureFlags, UnityEngine.CoreModule"); System.Reflection.MethodInfo takeMethod = null; if (debugScreenCaptureType != null && captureFlagsType != null) { var screenshotCallbackType = typeof(Action<,,>).MakeGenericType( typeof(string), typeof(bool), debugScreenCaptureType); takeMethod = MemoryProfilerType.GetMethod("TakeSnapshot", new[] { typeof(string), typeof(Action), screenshotCallbackType, captureFlagsType }); } if (takeMethod == null && captureFlagsType != null) { takeMethod = MemoryProfilerType.GetMethod("TakeSnapshot", new[] { typeof(string), typeof(Action), captureFlagsType }); } if (takeMethod == null && debugScreenCaptureType != null) { var screenshotCallbackType = typeof(Action<,,>).MakeGenericType( typeof(string), typeof(bool), debugScreenCaptureType); takeMethod = MemoryProfilerType.GetMethod("TakeSnapshot", new[] { typeof(string), typeof(Action), screenshotCallbackType, typeof(uint) }); } if (takeMethod == null) { takeMethod = MemoryProfilerType.GetMethod("TakeSnapshot", new[] { typeof(string), typeof(Action) }); } if (takeMethod == null) return new ErrorResponse("Could not find TakeSnapshot method on MemoryProfiler. API may have changed."); Action callback = (path, result) => { if (result) { var fi = new FileInfo(path); tcs.TrySetResult(new SuccessResponse("Memory snapshot captured.", new { path, size_bytes = fi.Exists ? fi.Length : 0, size_mb = fi.Exists ? Math.Round(fi.Length / (1024.0 * 1024.0), 2) : 0, })); } else { tcs.TrySetResult(new ErrorResponse($"Snapshot capture failed for path: {path}")); } }; var takeMethodParams = takeMethod.GetParameters(); int paramCount = takeMethodParams.Length; if (paramCount == 4 && takeMethodParams[3].ParameterType == captureFlagsType) takeMethod.Invoke(null, new object[] { snapshotPath, callback, null, Enum.ToObject(captureFlagsType, 0) }); else if (paramCount == 3 && takeMethodParams[2].ParameterType == captureFlagsType) takeMethod.Invoke(null, new object[] { snapshotPath, callback, Enum.ToObject(captureFlagsType, 0) }); else if (paramCount == 4 && takeMethodParams[3].ParameterType == typeof(uint)) takeMethod.Invoke(null, new object[] { snapshotPath, callback, null, 0u }); else if (paramCount == 2) takeMethod.Invoke(null, new object[] { snapshotPath, callback }); else return new ErrorResponse($"TakeSnapshot has unexpected {paramCount} parameters. API may have changed."); } catch (Exception ex) { return new ErrorResponse($"Failed to take snapshot: {ex.Message}"); } var timeout = Task.Delay(TimeSpan.FromSeconds(30)); var completed = await Task.WhenAny(tcs.Task, timeout); if (completed == timeout) return new ErrorResponse("Snapshot timed out after 30 seconds."); return await tcs.Task; } internal static object ListSnapshots(JObject @params) { var p = new ToolParams(@params); string searchPath = p.Get("search_path"); var dirs = new List(); if (!string.IsNullOrEmpty(searchPath)) { dirs.Add(searchPath); } else { dirs.Add(Path.Combine(Application.temporaryCachePath, "MemoryCaptures")); dirs.Add(Path.Combine(Application.dataPath, "..", "MemoryCaptures")); } var snapshots = new List(); foreach (string dir in dirs) { if (!Directory.Exists(dir)) continue; foreach (string file in Directory.GetFiles(dir, "*.snap")) { var fi = new FileInfo(file); snapshots.Add(new { path = fi.FullName, size_bytes = fi.Length, size_mb = Math.Round(fi.Length / (1024.0 * 1024.0), 2), created = fi.CreationTimeUtc.ToString("o"), }); } } return new SuccessResponse($"Found {snapshots.Count} snapshot(s).", new { snapshots, searched_dirs = dirs, }); } internal static object CompareSnapshots(JObject @params) { var p = new ToolParams(@params); var pathAResult = p.GetRequired("snapshot_a"); if (!pathAResult.IsSuccess) return new ErrorResponse(pathAResult.ErrorMessage); var pathBResult = p.GetRequired("snapshot_b"); if (!pathBResult.IsSuccess) return new ErrorResponse(pathBResult.ErrorMessage); string pathA = pathAResult.Value; string pathB = pathBResult.Value; if (!File.Exists(pathA)) return new ErrorResponse($"Snapshot file not found: {pathA}"); if (!File.Exists(pathB)) return new ErrorResponse($"Snapshot file not found: {pathB}"); var fiA = new FileInfo(pathA); var fiB = new FileInfo(pathB); return new SuccessResponse("Snapshot comparison (file-level metadata).", new { snapshot_a = new { path = fiA.FullName, size_bytes = fiA.Length, size_mb = Math.Round(fiA.Length / (1024.0 * 1024.0), 2), created = fiA.CreationTimeUtc.ToString("o"), }, snapshot_b = new { path = fiB.FullName, size_bytes = fiB.Length, size_mb = Math.Round(fiB.Length / (1024.0 * 1024.0), 2), created = fiB.CreationTimeUtc.ToString("o"), }, delta = new { size_delta_bytes = fiB.Length - fiA.Length, size_delta_mb = Math.Round((fiB.Length - fiA.Length) / (1024.0 * 1024.0), 2), time_delta_seconds = (fiB.CreationTimeUtc - fiA.CreationTimeUtc).TotalSeconds, }, note = "For detailed object-level comparison, open both snapshots in the Memory Profiler window.", }); } private static ErrorResponse PackageMissingError() { return new ErrorResponse( "Package com.unity.memoryprofiler is required. " + "Install via Package Manager or: manage_packages action=add_package package_id=com.unity.memoryprofiler"); } } }