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; } }