using System; using System.Diagnostics; using S3.Modules.Profiler; using UnityEngine.LowLevel; namespace S3.Mcp.Tools; /// /// Temporarily inserts timestamp boundaries around every Unity PlayerLoop /// subsystem. This measures native engine phases that Harmony cannot patch. /// internal static class PlayerLoopProfiler { static PlayerLoopSystem _original; static long[] _depthStarts = Array.Empty(); public static bool Active { get; private set; } public static int Boundaries { get; private set; } public static string Start() { Stop(); try { _original = PlayerLoop.GetCurrentPlayerLoop(); PlayerLoopSystem instrumented = _original; Boundaries = 0; int maxDepth = MeasureDepth(instrumented, 0); _depthStarts = new long[Math.Max(2, maxDepth + 2)]; Instrument(ref instrumented, 0, "PlayerLoop"); PlayerLoop.SetPlayerLoop(instrumented); Active = true; return $"boundaries={Boundaries} depth={maxDepth}"; } catch (Exception ex) { Stop(); return "failed:" + ex.GetType().Name; } } public static void Stop() { if (Active) { try { PlayerLoop.SetPlayerLoop(_original); } catch { } } Active = false; Boundaries = 0; _depthStarts = Array.Empty(); } static int MeasureDepth(PlayerLoopSystem system, int depth) { int max = depth; PlayerLoopSystem[]? children = system.subSystemList; if (children == null) return max; for (int i = 0; i < children.Length; i++) max = Math.Max(max, MeasureDepth(children[i], depth + 1)); return max; } static void Instrument( ref PlayerLoopSystem system, int depth, string parentPath) { PlayerLoopSystem[]? children = system.subSystemList; if (children == null || children.Length == 0) return; var expanded = new PlayerLoopSystem[children.Length * 2 + 1]; expanded[0] = Boundary(depth, null); for (int i = 0; i < children.Length; i++) { PlayerLoopSystem child = children[i]; string childName = DisplayName(child.type, i); string path = parentPath + "." + childName; Instrument(ref child, depth + 1, path); expanded[i * 2 + 1] = child; expanded[i * 2 + 2] = Boundary(depth, "loop." + path); Boundaries++; } system.subSystemList = expanded; } static PlayerLoopSystem Boundary(int depth, string? completedId) { return new PlayerLoopSystem { type = typeof(PlayerLoopProfiler), updateDelegate = () => Mark(depth, completedId), }; } static void Mark(int depth, string? completedId) { long now = Stopwatch.GetTimestamp(); if (completedId != null && SparseHitchSampler.Active && depth < _depthStarts.Length) { long start = _depthStarts[depth]; if (start != 0 && now >= start) SparseHitchSampler.Record(completedId, now - start); } if (depth < _depthStarts.Length) _depthStarts[depth] = now; } static string DisplayName(Type? type, int index) { if (type == null) return "unnamed" + index; return type.FullName ?? type.Name; } }