using System;
namespace AcDream.App.Diagnostics;
///
/// MP0 (2026-07-05) — fixed-capacity ring buffer of long samples
/// (microseconds or bytes) with percentile/max over the current window.
/// Pure and allocation-free after construction:
/// sorts into a preallocated scratch array, so the 5-second report path
/// allocates nothing. Not thread-safe — owned by the window loop thread.
/// Spec: docs/superpowers/specs/2026-07-05-modern-pipeline-design.md §5.
///
public sealed class FrameStatsBuffer
{
private readonly long[] _samples;
private readonly long[] _scratch;
private int _cursor;
private int _count;
public FrameStatsBuffer(int capacity)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
_samples = new long[capacity];
_scratch = new long[capacity];
}
public int Count => _count;
public void Push(long value)
{
_samples[_cursor] = value;
_cursor = (_cursor + 1) % _samples.Length;
if (_count < _samples.Length) _count++;
}
public void Reset()
{
_cursor = 0;
_count = 0;
}
///
/// Nearest-rank percentile over the current window: element at
/// ceil(q·n) in the ascending sort (1-based), 0 when empty.
///
public long Percentile(double q)
{
if (_count == 0) return 0;
Array.Copy(_samples, _scratch, _count);
Array.Sort(_scratch, 0, _count);
int rank = (int)Math.Ceiling(q * _count); // 1-based nearest rank
if (rank < 1) rank = 1;
if (rank > _count) rank = _count;
return _scratch[rank - 1];
}
public long Max()
{
long max = 0;
for (int i = 0; i < _count; i++)
if (_samples[i] > max) max = _samples[i];
return max;
}
}