using Pcie8586Probe.Models; namespace Pcie8586Probe.Acquisition; public sealed class RingHistoryBuffer { private readonly object _gate = new(); private readonly Queue _blocks = new(); private readonly long _maxSamplesPerChannel; private long _samplesPerChannel; public RingHistoryBuffer(double sampleRateHz, double seconds, int maxBytes = 500 * 1024 * 1024) { if (sampleRateHz <= 0) throw new ArgumentOutOfRangeException(nameof(sampleRateHz)); if (seconds <= 0) throw new ArgumentOutOfRangeException(nameof(seconds)); var requested = (long)Math.Ceiling(sampleRateHz * seconds); var memoryBound = Math.Max(1, maxBytes / (8L * 8L)); _maxSamplesPerChannel = Math.Max(1, Math.Min(requested, memoryBound)); } public long SamplesPerChannel { get { lock (_gate) { return _samplesPerChannel; } } } public void Add(SampleBlock block) { if (block.SamplesPerChannel == 0) { return; } lock (_gate) { _blocks.Enqueue(block); _samplesPerChannel += block.SamplesPerChannel; while (_samplesPerChannel > _maxSamplesPerChannel && _blocks.Count > 0) { var removed = _blocks.Dequeue(); _samplesPerChannel -= removed.SamplesPerChannel; } } } public SampleBlock? Snapshot() { lock (_gate) { if (_blocks.Count == 0) { return null; } return SampleBlockAssembler.Concat(_blocks); } } public SampleBlock? Slice(long startInclusive, long endExclusive) { if (startInclusive < 0 || endExclusive <= startInclusive) { return null; } var snapshot = Snapshot(); if (snapshot is null || startInclusive >= snapshot.SamplesPerChannel) { return null; } var end = Math.Min(endExclusive, snapshot.SamplesPerChannel); return SampleBlockAssembler.Slice(snapshot, (int)startInclusive, (int)(end - startInclusive)); } }