You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
379 lines
12 KiB
C#
379 lines
12 KiB
C#
/*
|
|
----------------------------------------------------------------------------
|
|
"THE BEER-WARE LICENSE" (Revision 42):
|
|
Joao Portela wrote this file. As long as you retain this notice you
|
|
can do whatever you want with this stuff. If we meet some day, and you think
|
|
this stuff is worth it, you can buy me a beer in return.
|
|
Joao Portela
|
|
--------------------------------------------------------------------------
|
|
* https://github.com/joaoportela/CircullarBuffer-CSharp
|
|
*/
|
|
|
|
namespace SRDebugger
|
|
{
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
public interface IReadOnlyList<T> : IEnumerable<T>
|
|
{
|
|
int Count { get; }
|
|
|
|
T this[int index] { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Circular buffer.
|
|
/// When writting 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.
|
|
/// </summary>
|
|
public class CircularBuffer<T> : IEnumerable<T>, IReadOnlyList<T>
|
|
{
|
|
private readonly T[] _buffer;
|
|
|
|
/// <summary>
|
|
/// The _end. Index after the last element in the buffer.
|
|
/// </summary>
|
|
private int _end;
|
|
|
|
/// <summary>
|
|
/// The _size. Buffer size.
|
|
/// </summary>
|
|
private int _count;
|
|
|
|
/// <summary>
|
|
/// The _start. Index of the first element in buffer.
|
|
/// </summary>
|
|
private int _start;
|
|
|
|
public CircularBuffer(int capacity)
|
|
: this(capacity, new T[] {}) {}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="CircularBuffer{T}" /> class.
|
|
/// </summary>
|
|
/// <param name='capacity'>
|
|
/// Buffer capacity. Must be positive.
|
|
/// </param>
|
|
/// <param name='items'>
|
|
/// Items to fill buffer with. Items length must be less than capacity.
|
|
/// Sugestion: use Skip(x).Take(y).ToArray() to build this argument from
|
|
/// any enumerable.
|
|
/// </param>
|
|
public CircularBuffer(int capacity, T[] items)
|
|
{
|
|
if (capacity < 1)
|
|
{
|
|
throw new ArgumentException(
|
|
"Circular buffer cannot have negative or zero capacity.", "capacity");
|
|
}
|
|
if (items == null)
|
|
{
|
|
throw new ArgumentNullException("items");
|
|
}
|
|
if (items.Length > capacity)
|
|
{
|
|
throw new ArgumentException(
|
|
"Too many items to fit circular buffer", "items");
|
|
}
|
|
|
|
_buffer = new T[capacity];
|
|
|
|
Array.Copy(items, _buffer, items.Length);
|
|
_count = items.Length;
|
|
|
|
_start = 0;
|
|
_end = _count == capacity ? 0 : _count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maximum capacity of the buffer. Elements pushed into the buffer after
|
|
/// maximum capacity is reached (IsFull = true), will remove an element.
|
|
/// </summary>
|
|
public int Capacity
|
|
{
|
|
get { return _buffer.Length; }
|
|
}
|
|
|
|
public bool IsFull
|
|
{
|
|
get { return Count == Capacity; }
|
|
}
|
|
|
|
public bool IsEmpty
|
|
{
|
|
get { return Count == 0; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current buffer size (the number of elements that the buffer has).
|
|
/// </summary>
|
|
public int Count
|
|
{
|
|
get { return _count; }
|
|
}
|
|
|
|
public T this[int index]
|
|
{
|
|
get
|
|
{
|
|
if (IsEmpty)
|
|
{
|
|
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
|
|
}
|
|
if (index >= _count)
|
|
{
|
|
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}",
|
|
index, _count));
|
|
}
|
|
var actualIndex = InternalIndex(index);
|
|
return _buffer[actualIndex];
|
|
}
|
|
set
|
|
{
|
|
if (IsEmpty)
|
|
{
|
|
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer is empty", index));
|
|
}
|
|
if (index >= _count)
|
|
{
|
|
throw new IndexOutOfRangeException(string.Format("Cannot access index {0}. Buffer size is {1}",
|
|
index, _count));
|
|
}
|
|
var actualIndex = InternalIndex(index);
|
|
_buffer[actualIndex] = value;
|
|
}
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_count = 0;
|
|
_start = 0;
|
|
_end = 0;
|
|
}
|
|
|
|
#region IEnumerable<T> implementation
|
|
|
|
public IEnumerator<T> GetEnumerator()
|
|
{
|
|
var segments = new ArraySegment<T>[2] {ArrayOne(), ArrayTwo()};
|
|
foreach (var segment in segments)
|
|
{
|
|
for (var i = 0; i < segment.Count; i++)
|
|
{
|
|
yield return segment.Array[segment.Offset + i];
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable implementation
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IList<T> implementation
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Element at the front of the buffer - this[0].
|
|
/// </summary>
|
|
/// <returns>The value of the element of type T at the front of the buffer.</returns>
|
|
public T Front()
|
|
{
|
|
ThrowIfEmpty();
|
|
return _buffer[_start];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Element at the back of the buffer - this[Size - 1].
|
|
/// </summary>
|
|
/// <returns>The value of the element of type T at the back of the buffer.</returns>
|
|
public T Back()
|
|
{
|
|
ThrowIfEmpty();
|
|
return _buffer[(_end != 0 ? _end : _count) - 1];
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="item">Item to push to the back of the buffer</param>
|
|
public void PushBack(T item)
|
|
{
|
|
if (IsFull)
|
|
{
|
|
_buffer[_end] = item;
|
|
Increment(ref _end);
|
|
_start = _end;
|
|
}
|
|
else
|
|
{
|
|
_buffer[_end] = item;
|
|
Increment(ref _end);
|
|
++_count;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="item">Item to push to the front of the buffer</param>
|
|
public void PushFront(T item)
|
|
{
|
|
if (IsFull)
|
|
{
|
|
Decrement(ref _start);
|
|
_end = _start;
|
|
_buffer[_start] = item;
|
|
}
|
|
else
|
|
{
|
|
Decrement(ref _start);
|
|
_buffer[_start] = item;
|
|
++_count;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the element at the back of the buffer. Decreassing the
|
|
/// Buffer size by 1.
|
|
/// </summary>
|
|
public void PopBack()
|
|
{
|
|
ThrowIfEmpty("Cannot take elements from an empty buffer.");
|
|
Decrement(ref _end);
|
|
_buffer[_end] = default(T);
|
|
--_count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the element at the front of the buffer. Decreassing the
|
|
/// Buffer size by 1.
|
|
/// </summary>
|
|
public void PopFront()
|
|
{
|
|
ThrowIfEmpty("Cannot take elements from an empty buffer.");
|
|
_buffer[_start] = default(T);
|
|
Increment(ref _start);
|
|
--_count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the buffer contents to an array, acording to the logical
|
|
/// contents of the buffer (i.e. independent of the internal
|
|
/// order/contents)
|
|
/// </summary>
|
|
/// <returns>A new array with a copy of the buffer contents.</returns>
|
|
public T[] ToArray()
|
|
{
|
|
var newArray = new T[Count];
|
|
var newArrayOffset = 0;
|
|
var segments = new ArraySegment<T>[2] {ArrayOne(), ArrayTwo()};
|
|
foreach (var segment in segments)
|
|
{
|
|
Array.Copy(segment.Array, segment.Offset, newArray, newArrayOffset, segment.Count);
|
|
newArrayOffset += segment.Count;
|
|
}
|
|
return newArray;
|
|
}
|
|
|
|
private void ThrowIfEmpty(string message = "Cannot access an empty buffer.")
|
|
{
|
|
if (IsEmpty)
|
|
{
|
|
throw new InvalidOperationException(message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increments the provided index variable by one, wrapping
|
|
/// around if necessary.
|
|
/// </summary>
|
|
/// <param name="index"></param>
|
|
private void Increment(ref int index)
|
|
{
|
|
if (++index == Capacity)
|
|
{
|
|
index = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrements the provided index variable by one, wrapping
|
|
/// around if necessary.
|
|
/// </summary>
|
|
/// <param name="index"></param>
|
|
private void Decrement(ref int index)
|
|
{
|
|
if (index == 0)
|
|
{
|
|
index = Capacity;
|
|
}
|
|
index--;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the index in the argument to an index in <code>_buffer</code>
|
|
/// </summary>
|
|
/// <returns>
|
|
/// The transformed index.
|
|
/// </returns>
|
|
/// <param name='index'>
|
|
/// External index.
|
|
/// </param>
|
|
private int InternalIndex(int index)
|
|
{
|
|
return _start + (index < (Capacity - _start) ? index : index - Capacity);
|
|
}
|
|
|
|
// doing ArrayOne and ArrayTwo methods returning ArraySegment<T> 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<T> ArrayOne()
|
|
{
|
|
if (_start <= _end)
|
|
{
|
|
return new ArraySegment<T>(_buffer, _start, _end - _start);
|
|
}
|
|
return new ArraySegment<T>(_buffer, _start, _buffer.Length - _start);
|
|
}
|
|
|
|
private ArraySegment<T> ArrayTwo()
|
|
{
|
|
if (_start < _end)
|
|
{
|
|
return new ArraySegment<T>(_buffer, _end, 0);
|
|
}
|
|
return new ArraySegment<T>(_buffer, 0, _end);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|