PageRenderTime 163ms CodeModel.GetById 80ms app.highlight 7ms RepoModel.GetById 74ms app.codeStats 0ms

/Utilities/Collections/CircularBuffer.cs

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