123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- /*
- ----------------------------------------------------------------------------
- "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
- }
- }
|