StationObscurum/Assets/FishNet/Runtime/Utility/Performance/RingBuffer.cs

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