using System;
using System.Diagnostics;
namespace T3.Core.Stats;
///
/// Host-agnostic rolling performance metrics (frame duration, UI render duration, managed GC allocations).
/// Used by both the Editor and the Player.
///
/// Frame / UI render values are supplied by the caller; GC allocations are sampled internally
/// via whenever is called.
///
/// Thread-safety: intended to be called from the main render thread only.
///
public static class PerformanceMetrics
{
public const int WindowSize = 400;
public const int BucketCount = 16;
/// Total frames recorded since process start. Grows monotonically.
public static long TotalFrameCount { get; private set; }
/// Frame duration in milliseconds. Bucket anchored at 16.66 ms (60 Hz).
public static readonly RollingMetric FrameDuration =
RollingMetric.CreateLinear(WindowSize, BucketCount, 0, 32f);
/// UI render duration in milliseconds. Linear 0..30 ms.
public static readonly RollingMetric UiRenderDuration =
RollingMetric.CreateLinear(WindowSize, BucketCount, 0f, 32f);
/// Managed allocations per frame, in kilobytes. Log10-bucketed 0.1 kB .. 10 MB.
public static readonly RollingMetric GcAllocationsKb =
RollingMetric.CreateLog10(WindowSize, 10, 0f, 4f);
private static long _lastGcTotalBytes;
/// Wall-clock time in seconds since process start. Monotonic, allocation-free.
public static double Now => Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
///
/// Record a completed frame. Updates and samples GC allocations.
/// Call once per frame, after the frame finishes rendering.
///
public static void RecordFrame(float frameDurationMs)
{
var now = Now;
TotalFrameCount++;
FrameDuration.Update(frameDurationMs, now);
SampleGc(now);
}
///
/// Record the duration of the UI pass within a frame (editor only). Call once per frame.
///
public static void RecordUiRender(float uiRenderMs)
{
UiRenderDuration.Update(uiRenderMs, Now);
}
private static void SampleGc(double now)
{
var total = GC.GetTotalAllocatedBytes(precise:true);
var delta = total - _lastGcTotalBytes;
_lastGcTotalBytes = total;
if (delta < 0)
delta = 0; // tolerate counter wrap / host reset
GcAllocationsKb.Update((float)(delta / 1024.0), now);
}
}