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