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