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