using System;
using System.Collections;
using System.Collections.Generic;
namespace HurricaneVR.Framework.Shared.Utilities
{
///
///
/// Circular buffer.
///
/// When writing to a full buffer:
/// PushBack -> removes this[0] / Front()
/// PushFront -> removes this[Size-1] / Back()
///
/// this implementation is inspired by
/// http://www.boost.org/doc/libs/1_53_0/libs/circular_buffer/doc/circular_buffer.html
/// because I liked their interface.
///
public class CircularBuffer : IEnumerable
{
private readonly T[] _buffer;
///
/// The _start. Index of the first element in buffer.
///
private int _start;
///
/// The _end. Index after the last element in the buffer.
///
private int _end;
///
/// The _size. Buffer size.
///
private int _size;
public CircularBuffer(int capacity)
: this(capacity, new T[] { })
{
}
///
/// Initializes a new instance of the class.
///
///
///
/// Buffer capacity. Must be positive.
///
///
/// Items to fill buffer with. Items length must be less than capacity.
/// Suggestion: use Skip(x).Take(y).ToArray() to build this argument from
/// any enumerable.
///
public CircularBuffer(int capacity, T[] items)
{
if (capacity < 1)
{
throw new ArgumentException(
"Circular buffer cannot have negative or zero capacity.", nameof(capacity));
}
if (items == null)
{
throw new ArgumentNullException(nameof(items));
}
if (items.Length > capacity)
{
throw new ArgumentException(
"Too many items to fit circular buffer", nameof(items));
}
_buffer = new T[capacity];
Array.Copy(items, _buffer, items.Length);
_size = items.Length;
_start = 0;
_end = _size == capacity ? 0 : _size;
}
///
/// Maximum capacity of the buffer. Elements pushed into the buffer after
/// maximum capacity is reached (IsFull = true), will remove an element.
///
public int Capacity { get { return _buffer.Length; } }
public bool IsFull
{
get
{
return Size == Capacity;
}
}
public bool IsEmpty
{
get
{
return Size == 0;
}
}
///
/// Current buffer size (the number of elements that the buffer has).
///
public int Size { get { return _size; } }
///
/// Element at the front of the buffer - this[0].
///
/// The value of the element of type T at the front of the buffer.
public T Front()
{
ThrowIfEmpty();
return _buffer[_start];
}
///
/// Element at the back of the buffer - this[Size - 1].
///
/// The value of the element of type T at the back of the buffer.
public T Back()
{
ThrowIfEmpty();
return _buffer[(_end != 0 ? _end : Capacity) - 1];
}
public T this[int index]
{
get
{
if (IsEmpty)
{
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
}
if (index >= _size)
{
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _size));
}
int actualIndex = InternalIndex(index);
return _buffer[actualIndex];
}
set
{
if (IsEmpty)
{
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
}
if (index >= _size)
{
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}", index, _size));
}
int actualIndex = InternalIndex(index);
_buffer[actualIndex] = value;
}
}
///
/// Pushes a new element to the back of the buffer. Back()/this[Size-1]
/// will now return this element.
///
/// When the buffer is full, the element at Front()/this[0] will be
/// popped to allow for this new element to fit.
///
/// Item to push to the back of the buffer
public void Dequeue(T item)
{
if (IsFull)
{
_buffer[_end] = item;
Increment(ref _end);
_start = _end;
}
else
{
_buffer[_end] = item;
Increment(ref _end);
++_size;
}
}
///
/// Pushes a new element to the front of the buffer. Front()/this[0]
/// will now return this element.
///
/// When the buffer is full, the element at Back()/this[Size-1] will be
/// popped to allow for this new element to fit.
///
/// Item to push to the front of the buffer
public void Enqueue(T item)
{
if (IsFull)
{
Decrement(ref _start);
_end = _start;
_buffer[_start] = item;
}
else
{
Decrement(ref _start);
_buffer[_start] = item;
++_size;
}
}
///
/// Removes the element at the back of the buffer. Decreasing the
/// Buffer size by 1.
///
public void PopBack()
{
ThrowIfEmpty("Cannot take elements from an empty buffer.");
Decrement(ref _end);
_buffer[_end] = default(T);
--_size;
}
///
/// Removes the element at the front of the buffer. Decreasing the
/// Buffer size by 1.
///
public void PopFront()
{
ThrowIfEmpty("Cannot take elements from an empty buffer.");
_buffer[_start] = default(T);
Increment(ref _start);
--_size;
}
///
/// Copies the buffer contents to an array, according to the logical
/// contents of the buffer (i.e. independent of the internal
/// order/contents)
///
/// A new array with a copy of the buffer contents.
public T[] ToArray()
{
T[] newArray = new T[Size];
int newArrayOffset = 0;
var segments = new ArraySegment[2] { ArrayOne(), ArrayTwo() };
foreach (ArraySegment segment in segments)
{
Array.Copy(segment.Array, segment.Offset, newArray, newArrayOffset, segment.Count);
newArrayOffset += segment.Count;
}
return newArray;
}
#region IEnumerable implementation
public IEnumerator GetEnumerator()
{
var segments = new ArraySegment[2] { ArrayOne(), ArrayTwo() };
foreach (ArraySegment segment in segments)
{
for (int i = 0; i < segment.Count; i++)
{
yield return segment.Array[segment.Offset + i];
}
}
}
#endregion
#region IEnumerable implementation
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator)GetEnumerator();
}
#endregion
private void ThrowIfEmpty(string message = "Cannot access an empty buffer.")
{
if (IsEmpty)
{
throw new InvalidOperationException(message);
}
}
///
/// Increments the provided index variable by one, wrapping
/// around if necessary.
///
///
private void Increment(ref int index)
{
if (++index == Capacity)
{
index = 0;
}
}
///
/// Decrements the provided index variable by one, wrapping
/// around if necessary.
///
///
private void Decrement(ref int index)
{
if (index == 0)
{
index = Capacity;
}
index--;
}
///
/// Converts the index in the argument to an index in _buffer
///
///
/// The transformed index.
///
///
/// External index.
///
private int InternalIndex(int index)
{
return _start + (index < (Capacity - _start) ? index : index - Capacity);
}
// doing ArrayOne and ArrayTwo methods returning ArraySegment as seen here:
// http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1957cccdcb0c4ef7d80a34a990065818d
// http://www.boost.org/doc/libs/1_37_0/libs/circular_buffer/doc/circular_buffer.html#classboost_1_1circular__buffer_1f5081a54afbc2dfc1a7fb20329df7d5b
// should help a lot with the code.
#region Array items easy access.
// The array is composed by at most two non-contiguous segments,
// the next two methods allow easy access to those.
private ArraySegment ArrayOne()
{
if (IsEmpty)
{
return new ArraySegment(new T[0]);
}
else if (_start < _end)
{
return new ArraySegment(_buffer, _start, _end - _start);
}
else
{
return new ArraySegment(_buffer, _start, _buffer.Length - _start);
}
}
private ArraySegment ArrayTwo()
{
if (IsEmpty)
{
return new ArraySegment(new T[0]);
}
else if (_start < _end)
{
return new ArraySegment(_buffer, _end, 0);
}
else
{
return new ArraySegment(_buffer, 0, _end);
}
}
#endregion
}
}