PageRenderTime 52ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Collections/CircularBuffer.cs

#
C# | 187 lines | 117 code | 14 blank | 56 comment | 4 complexity | 62b134d77e61f6824e13b2cbd417bbcf MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using Delta.Utilities.Helpers;
  3. using NUnit.Framework;
  4. namespace Delta.Utilities.Collections
  5. {
  6. /// <summary>
  7. /// CircularShortBuffer, used for recording/playback of sound samples.
  8. /// We can simply add new sample data and read existing sample data
  9. /// at the same time! The size of the circular buffer should fit
  10. /// the requirements (data should have been read when cycling around).
  11. /// There will be no warning if we overwrite data without reading.
  12. /// </summary>
  13. /// <typeparam name="T">Data type</typeparam>
  14. public class CircularBuffer<T>
  15. {
  16. #region Length (Public)
  17. /// <summary>
  18. /// Get how much bytes can be read, can even be more than buffer.Length
  19. /// </summary>
  20. /// <typeparam name="T">T</typeparam>
  21. public int Length
  22. {
  23. get
  24. {
  25. return writePos - readPos;
  26. }
  27. }
  28. #endregion
  29. #region Private
  30. #region buffer (Private)
  31. /// <summary>
  32. /// Buffer
  33. /// </summary>
  34. /// <typeparam name="T">T</typeparam>
  35. private readonly T[] buffer;
  36. #endregion
  37. #region writePos (Private)
  38. /// <summary>
  39. /// We only increase writePos and readPos, they will
  40. /// obviously go way out of range of the buffer size,
  41. /// we need ALWAYS to modulate it by the size of the buffer to access data!
  42. /// We do this because only this way we can see how much data
  43. /// was written and how much was read!
  44. /// </summary>
  45. /// <typeparam name="T">T</typeparam>
  46. private int writePos;
  47. #endregion
  48. #region readPos (Private)
  49. /// <summary>
  50. /// Read pos
  51. /// </summary>
  52. /// <returns>0</returns>
  53. /// <typeparam name="T">T</typeparam>
  54. private int readPos;
  55. #endregion
  56. #endregion
  57. #region Constructors
  58. /// <summary>
  59. /// Circular short buffer
  60. /// </summary>
  61. /// <param name="bufferSize">Buffer size</param>
  62. public CircularBuffer(int bufferSize)
  63. {
  64. buffer = new T[bufferSize];
  65. writePos = 0;
  66. readPos = 0;
  67. }
  68. #endregion
  69. #region Flush (Public)
  70. /// <summary>
  71. /// Flush is very simply, we just reset both writePos and readPos to 0.
  72. /// </summary>
  73. public void Flush()
  74. {
  75. writePos = 0;
  76. readPos = 0;
  77. }
  78. #endregion
  79. #region Write (Public)
  80. /// <summary>
  81. /// Write array of new data to write into the circular buffer.
  82. /// </summary>
  83. /// <param name="newData">New data to write into the buffer</param>
  84. public void Write(T[] newData)
  85. {
  86. if (newData.Length >
  87. buffer.Length)
  88. {
  89. throw new InvalidOperationException(
  90. "newData length can't be bigger than circular buffer: " +
  91. "newData.Length=" + newData.Length +
  92. ", buffer.Length=" + buffer.Length);
  93. }
  94. int localWritePos = writePos % buffer.Length;
  95. // Does whole newData block fit till end of array?
  96. if (localWritePos + newData.Length <=
  97. buffer.Length)
  98. {
  99. Array.Copy(newData, 0, buffer, localWritePos, newData.Length);
  100. }
  101. else
  102. {
  103. // Does not fit, split into 2 parts and add seperatly
  104. int lengthTillEnd = buffer.Length - localWritePos;
  105. Array.Copy(newData, 0, buffer, localWritePos, lengthTillEnd);
  106. Array.Copy(newData, lengthTillEnd, buffer, 0,
  107. newData.Length - lengthTillEnd);
  108. }
  109. // Advance write pos
  110. writePos += newData.Length;
  111. }
  112. #endregion
  113. #region Read (Public)
  114. /// <summary>
  115. /// Read data into an array of the same type.
  116. /// </summary>
  117. /// <param name="readData">Data to read into</param>
  118. public void Read(T[] readData)
  119. {
  120. if (readData.Length >
  121. buffer.Length)
  122. {
  123. throw new InvalidOperationException(
  124. "readData length can't be bigger than circular buffer: " +
  125. "readData.Length" + readData.Length +
  126. ", buffer.Length=" + buffer.Length);
  127. }
  128. int localReadPos = readPos % buffer.Length;
  129. // Can read one simple block?
  130. if (localReadPos + readData.Length <=
  131. buffer.Length)
  132. {
  133. Array.Copy(buffer, localReadPos, readData, 0, readData.Length);
  134. }
  135. else
  136. {
  137. // Does not fit, split into 2 parts and read seperatly
  138. int lengthTillEnd = buffer.Length - localReadPos;
  139. Array.Copy(buffer, localReadPos, readData, 0, lengthTillEnd);
  140. Array.Copy(buffer, 0, readData, lengthTillEnd,
  141. readData.Length - lengthTillEnd);
  142. }
  143. // Advance read pos
  144. readPos += readData.Length;
  145. }
  146. #endregion
  147. }
  148. /// <summary>
  149. /// CircularBuffer tests helper, must be decleared outside of generic class.
  150. /// </summary>
  151. internal class CircularBufferTests
  152. {
  153. #region TestAddAndReadData (Static)
  154. /// <summary>
  155. /// Test add and read data
  156. /// </summary>
  157. [Test]
  158. public static void TestAddAndReadData()
  159. {
  160. CircularBuffer<int> testCircularBuffer = new CircularBuffer<int>(100);
  161. testCircularBuffer.Write(new[]
  162. {
  163. 1, 2, 3, 4
  164. });
  165. int[] readBuffer = new int[2];
  166. testCircularBuffer.Read(readBuffer);
  167. Assert.Equal("1, 2", readBuffer.Write());
  168. testCircularBuffer.Read(readBuffer);
  169. Assert.Equal("3, 4", readBuffer.Write());
  170. }
  171. #endregion
  172. }
  173. }