using System; using System.Collections; using System.Collections.Generic; using System.Threading; namespace FinlyticTechnicals.Timeframe; /// /// Thread-safe, high-performance circular ring buffer with zero allocations on updates. /// Holds a fixed capacity of items (e.g. 500 candles). /// /// The item type (e.g. CandleDto) public class CircularRingBuffer : IReadOnlyList { private readonly T[] _buffer; private readonly int _capacity; private int _start; private int _count; private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion); public CircularRingBuffer(int capacity = 500) { if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be positive."); _capacity = capacity; _buffer = new T[capacity]; _start = 0; _count = 0; } public int Capacity => _capacity; public int Count { get { _lock.EnterReadLock(); try { return _count; } finally { _lock.ExitReadLock(); } } } /// /// Adds an item to the buffer. If capacity is reached, the oldest element is overwritten in O(1). /// public void Add(T item) { _lock.EnterWriteLock(); try { if (_count < _capacity) { int nextIndex = (_start + _count) % _capacity; _buffer[nextIndex] = item; _count++; } else { _buffer[_start] = item; _start = (_start + 1) % _capacity; } } finally { _lock.ExitWriteLock(); } } /// /// Updates the last (most recent) item in place. /// public void UpdateLast(T item) { _lock.EnterWriteLock(); try { if (_count == 0) { _buffer[_start] = item; _count = 1; } else { int lastIndex = (_start + _count - 1) % _capacity; _buffer[lastIndex] = item; } } finally { _lock.ExitWriteLock(); } } /// /// Gets the most recent item or default if empty. /// public T? GetLast() { _lock.EnterReadLock(); try { if (_count == 0) return default; int lastIndex = (_start + _count - 1) % _capacity; return _buffer[lastIndex]; } finally { _lock.ExitReadLock(); } } /// /// Indexer accessing items from oldest (0) to newest (Count - 1). /// public T this[int index] { get { _lock.EnterReadLock(); try { if (index < 0 || index >= _count) throw new ArgumentOutOfRangeException(nameof(index), "Index out of range."); int actualIndex = (_start + index) % _capacity; return _buffer[actualIndex]; } finally { _lock.ExitReadLock(); } } } /// /// Returns an ordered immutable array snapshot of all elements. /// public T[] ToArray() { _lock.EnterReadLock(); try { if (_count == 0) return Array.Empty(); var result = new T[_count]; for (int i = 0; i < _count; i++) { int actualIndex = (_start + i) % _capacity; result[i] = _buffer[actualIndex]; } return result; } finally { _lock.ExitReadLock(); } } /// /// Populates the buffer in bulk with historical data (oldest to newest). /// public void LoadBulk(IEnumerable items) { _lock.EnterWriteLock(); try { _start = 0; _count = 0; foreach (var item in items) { if (_count < _capacity) { _buffer[_count] = item; _count++; } else { _buffer[_start] = item; _start = (_start + 1) % _capacity; } } } finally { _lock.ExitWriteLock(); } } public Enumerator GetEnumerator() => new(this); IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); public struct Enumerator : IEnumerator { private readonly CircularRingBuffer _buffer; private int _index; private T? _current; internal Enumerator(CircularRingBuffer buffer) { _buffer = buffer; _index = 0; _current = default; } public readonly T Current => _current!; readonly object? IEnumerator.Current => Current; public bool MoveNext() { if (_index < _buffer.Count) { _current = _buffer[_index]; _index++; return true; } _current = default; return false; } public void Reset() { _index = 0; _current = default; } public readonly void Dispose() { } } }