Static performance sampler for playtests. Records frame, controller, simulation and world step times into fixed ring buffers and returns average, P95 and max statistics from Snapshot.
using System;
using System.Diagnostics;
namespace Socer;
// Opt-in measurements for playtests. The hot path only checks Enabled when idle.
public static class PerformanceDiagnostics
{
const int Capacity=600;
static readonly double[] frameMs=new double[Capacity];
static readonly double[] controllerMs=new double[Capacity];
static readonly double[] simulationMs=new double[Capacity];
static readonly double[] worldMs=new double[Capacity];
static int frames,worldFrames;
public static bool Enabled {get;private set;}
public static void Start()
{
Array.Clear(frameMs);Array.Clear(controllerMs);Array.Clear(simulationMs);Array.Clear(worldMs);
frames=worldFrames=0;Enabled=true;
}
public static void Stop()=>Enabled=false;
public static long Begin()=>Enabled?Stopwatch.GetTimestamp():0;
public static double Milliseconds(long started)=>started==0?0:
(Stopwatch.GetTimestamp()-started)*1000.0/Stopwatch.Frequency;
public static void RecordFrame(float delta,long started,double simulation)
{
// Long frames are precisely the regressions this sampler must report.
// Filtering >500 ms hid a continuous model recompile loop at 1 FPS.
if(!Enabled || !float.IsFinite(delta) || delta<=0)return;
int index=frames++%Capacity;
frameMs[index]=delta*1000;
controllerMs[index]=Milliseconds(started);
simulationMs[index]=simulation;
}
public static void RecordWorld(long started)
{
if(!Enabled)return;
worldMs[worldFrames++%Capacity]=Milliseconds(started);
}
static object Describe(double[] values,int count)
{
count=Math.Min(count,Capacity);
if(count==0)return new{Average=0d,P95=0d,Maximum=0d};
var sample=new double[count];Array.Copy(values,sample,count);Array.Sort(sample);
double sum=0;foreach(double value in sample)sum+=value;
return new{Average=Math.Round(sum/count,2),P95=Math.Round(sample[(int)Math.Ceiling(count*.95)-1],2),
Maximum=Math.Round(sample[^1],2)};
}
public static object Snapshot()=>new
{
Enabled,Frames=Math.Min(frames,Capacity),WorldFrames=Math.Min(worldFrames,Capacity),
Frame=Describe(frameMs,frames),Controller=Describe(controllerMs,frames),
Simulation=Describe(simulationMs,frames),World=Describe(worldMs,worldFrames)
};
}