using System.Diagnostics; namespace Nerfed.Runtime; public struct ProfilerScope : IDisposable { public ProfilerScope(string label) { Profiler.BeginSample(label); } public void Dispose() { Profiler.EndSample(); } } public static class Profiler { public struct ProfileRecord { public string label; public long startTime; public long endTime; public int depth; public readonly double ElapsedMilliseconds() { long elapsedTicks = endTime - startTime; return ((double)(elapsedTicks * 1000)) / Stopwatch.Frequency; } } public class FrameData { public uint frame; public readonly List records = new List(); public long startTime; public long endTime; public FrameData(uint frame, long startTime) { this.frame = frame; this.startTime = startTime; } public double ElapsedMilliseconds() { long elapsedTicks = endTime - startTime; return ((double)(elapsedTicks * 1000)) / Stopwatch.Frequency; } } private const int maxFrames = 128; public static readonly BoundedQueue frames = new(maxFrames); public static bool recording = true; private static readonly Stopwatch stopwatch = new Stopwatch(); private static FrameData currentFrame = null; private static uint currentFrameIndex = 0; private static int currentDepth = 0; static Profiler() { stopwatch.Start(); } [Conditional("PROFILING")] public static void BeginFrame() { if (!recording) { return; } currentFrame = new FrameData(currentFrameIndex, stopwatch.ElapsedTicks); currentDepth = 0; currentFrameIndex++; } [Conditional("PROFILING")] public static void EndFrame() { if (!recording) { return; } currentFrame.endTime = stopwatch.ElapsedTicks; frames.Enqueue(currentFrame); } [Conditional("PROFILING")] public static void BeginSample(string label) { if (!recording) { return; } ProfileRecord record = new ProfileRecord { label = label, startTime = stopwatch.ElapsedTicks, depth = currentDepth, }; currentFrame.records.Add(record); //Log.Info($"{record.label} {record.depth} | {record.startTime}"); currentDepth++; // Increase depth for nested scopes } [Conditional("PROFILING")] public static void EndSample() { if (!recording) { return; } currentDepth--; // Decrease depth when exiting a scope // Find the last uncompleted record at the current depth and set the end time for (int i = currentFrame.records.Count - 1; i >= 0; i--) { if (currentFrame.records[i].endTime == 0) { ProfileRecord record = currentFrame.records[i]; record.endTime = stopwatch.ElapsedTicks; currentFrame.records[i] = record; // Assign back to the list //Log.Info($"{record.label} | {record.depth} | {record.endTime}"); break; } } } }