using FishNet.Documenting; using FishNet.Managing; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Utility { /// /// Writes values to a collection of a set size, overwriting old values as needed. /// public class RingBuffer { #region Types. /// /// Custom enumerator to prevent garbage collection. /// [APIExclude] public struct Enumerator : IEnumerator { #region Public. /// /// Current entry in the enumerator. /// public T Current { get; private set; } /// /// Actual index of the last enumerated value. /// public int ActualIndex { get { int total = (_startIndex + (_read - 1)); int capacity = _rollingCollection.Capacity; if (total >= capacity) total -= capacity; return total; } } /// /// Simulated index of the last enumerated value. /// public int SimulatedIndex => (_read - 1); #endregion #region Private. /// /// RollingCollection to use. /// private RingBuffer _rollingCollection; /// /// Collection to iterate. /// private readonly T[] _collection; /// /// Number of entries read during the enumeration. /// private int _read; /// /// Start index of enumerations. /// private int _startIndex; #endregion public Enumerator(RingBuffer c) { _read = 0; _startIndex = 0; _rollingCollection = c; _collection = c.Collection; Current = default; } public bool MoveNext() { int written = _rollingCollection.Count; if (_read >= written) { ResetRead(); return false; } int index = (_startIndex + _read); int capacity = _rollingCollection.Capacity; if (index >= capacity) index -= capacity; Current = _collection[index]; _read++; return true; } /// /// Sets a new start index to begin reading at. /// public void SetStartIndex(int index) { _startIndex = index; ResetRead(); } /// /// Sets a new start index to begin reading at. /// public void AddStartIndex(int value) { _startIndex += value; int cap = _rollingCollection.Capacity; if (_startIndex > cap) _startIndex -= cap; else if (_startIndex < 0) _startIndex += cap; ResetRead(); } /// /// Resets number of entries read during the enumeration. /// public void ResetRead() { _read = 0; } /// /// Resets read count. /// public void Reset() { _startIndex = 0; ResetRead(); } object IEnumerator.Current => Current; public void Dispose() { } } #endregion #region Public. /// /// Current write index of the collection. /// public int WriteIndex { get; private set; } /// /// Number of entries currently written. /// public int Count => _written; /// /// Maximum size of the collection. /// public int Capacity => Collection.Length; /// /// Collection being used. /// public T[] Collection = new T[0]; /// /// True if initialized. /// public bool Initialized { get; private set; } #endregion #region Private. /// /// Number of entries written. This will never go beyond the capacity but will be less until capacity is filled. /// private int _written; /// /// Enumerator for the collection. /// private Enumerator _enumerator; #endregion /// /// Initializes the collection at length. /// /// Size to initialize the collection as. This cannot be changed after initialized. public void Initialize(int capacity) { if (capacity <= 0) { NetworkManager.StaticLogError($"Collection length must be larger than 0."); return; } Collection = new T[capacity]; _enumerator = new Enumerator(this); Initialized = true; } /// /// Clears the collection to default values and resets indexing. /// public void Clear() { for (int i = 0; i < Collection.Length; i++) Collection[i] = default; Reset(); } /// /// Resets the collection without clearing. /// public void Reset() { _written = 0; WriteIndex = 0; _enumerator.Reset(); } /// /// Adds an entry to the collection, returning a replaced entry. /// /// Data to add. /// Replaced entry. Value will be default if no entry was replaced. [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Add(T data) { if (!IsInitializedWithError()) return default; int capacity = Capacity; T current = Collection[WriteIndex]; Collection[WriteIndex] = data; WriteIndex++; _written++; //If index would exceed next iteration reset it. if (WriteIndex >= capacity) WriteIndex = 0; /* If written has exceeded capacity * then the start index needs to be moved * to adjust for overwritten values. */ if (_written > capacity) { _written = capacity; _enumerator.SetStartIndex(WriteIndex); } return current; } /// /// Returns value in actual index as it relates to simulated index. /// /// Simulated index to return. A value of 0 would return the first simulated index in the collection. /// public T this[int simulatedIndex] { get { int offset = (Capacity - _written) + simulatedIndex + WriteIndex; if (offset >= Capacity) offset -= Capacity; return Collection[offset]; } set { int offset = (Capacity - _written) + simulatedIndex + WriteIndex; if (offset >= Capacity) offset -= Capacity; Collection[offset] = value; } } /// /// Returns Enumerator for the collection. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { if (!IsInitializedWithError()) return default; _enumerator.ResetRead(); return _enumerator; } /// /// Removes values from the simulated start of the collection. /// /// True to remove from the start, false to remove from the end. /// Number of entries to remove. public void RemoveRange(bool fromStart, int length) { if (length == 0) return; if (length < 0) { NetworkManager.StaticLogError($"Negative values cannot be removed."); return; } //Full reset if value is at or more than written. if (length >= _written) { Reset(); return; } _written -= length; if (fromStart) { _enumerator.AddStartIndex(length); } else { WriteIndex -= length; if (WriteIndex < 0) WriteIndex += Capacity; } } /// /// Returns if initialized and errors if not. /// /// private bool IsInitializedWithError() { if (!Initialized) { NetworkManager.StaticLogError($"RingBuffer has not yet been initialized."); return false; } return true; } } }