using System; using System.Runtime.InteropServices; namespace Unosquare.Swan.Components { /// /// A fixed-size buffer that acts as an infinite length one. /// This buffer is backed by unmanaged, very fast memory so ensure you call /// the dispose method when you are done using it. /// Only for Windows. /// /// public sealed class CircularBuffer : IDisposable { private readonly Object _syncLock = new Object(); private IntPtr _buffer = IntPtr.Zero; /// /// Initializes a new instance of the class. /// /// Length of the buffer. public CircularBuffer(Int32 bufferLength) { #if !NET452 if (Runtime.OS != Swan.OperatingSystem.Windows) throw new InvalidOperationException("CircularBuffer component is only available in Windows"); #endif this.Length = bufferLength; this._buffer = Marshal.AllocHGlobal(this.Length); } #region Properties /// /// Gets the capacity of this buffer. /// /// /// The length. /// public Int32 Length { get; private set; } /// /// Gets the current, 0-based read index. /// /// /// The index of the read. /// public Int32 ReadIndex { get; private set; } /// /// Gets the current, 0-based write index. /// /// /// The index of the write. /// public Int32 WriteIndex { get; private set; } /// /// Gets an the object associated with the last write. /// /// /// The write tag. /// public TimeSpan WriteTag { get; private set; } = TimeSpan.MinValue; /// /// Gets the available bytes to read. /// /// /// The readable count. /// public Int32 ReadableCount { get; private set; } /// /// Gets the number of bytes that can be written. /// /// /// The writable count. /// public Int32 WritableCount => this.Length - this.ReadableCount; /// /// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0). /// /// /// The capacity percent. /// public Double CapacityPercent => 1.0 * this.ReadableCount / this.Length; #endregion #region Methods /// /// Reads the specified number of bytes into the target array. /// /// The requested bytes. /// The target. /// The target offset. /// /// Exception that is thrown when a method call is invalid for the object's current state. /// public void Read(Int32 requestedBytes, Byte[] target, Int32 targetOffset) { lock(this._syncLock) { if(requestedBytes > this.ReadableCount) { throw new InvalidOperationException( $"Unable to read {requestedBytes} bytes. Only {this.ReadableCount} bytes are available"); } Int32 readCount = 0; while(readCount < requestedBytes) { Int32 copyLength = Math.Min(this.Length - this.ReadIndex, requestedBytes - readCount); IntPtr sourcePtr = this._buffer + this.ReadIndex; Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength); readCount += copyLength; this.ReadIndex += copyLength; this.ReadableCount -= copyLength; if(this.ReadIndex >= this.Length) { this.ReadIndex = 0; } } } } /// /// Writes data to the backing buffer using the specified pointer and length. /// and associating a write tag for this operation. /// /// The source. /// The length. /// The write tag. /// Unable to write to circular buffer. Call the Read method to make some additional room. public void Write(IntPtr source, Int32 length, TimeSpan writeTag) { lock(this._syncLock) { if(this.ReadableCount + length > this.Length) { throw new InvalidOperationException( $"Unable to write to circular buffer. Call the {nameof(Read)} method to make some additional room."); } Int32 writeCount = 0; while(writeCount < length) { Int32 copyLength = Math.Min(this.Length - this.WriteIndex, length - writeCount); IntPtr sourcePtr = source + writeCount; IntPtr targetPtr = this._buffer + this.WriteIndex; CopyMemory(targetPtr, sourcePtr, (UInt32)copyLength); writeCount += copyLength; this.WriteIndex += copyLength; this.ReadableCount += copyLength; if(this.WriteIndex >= this.Length) { this.WriteIndex = 0; } } this.WriteTag = writeTag; } } /// /// Resets all states as if this buffer had just been created. /// public void Clear() { lock(this._syncLock) { this.WriteIndex = 0; this.ReadIndex = 0; this.WriteTag = TimeSpan.MinValue; this.ReadableCount = 0; } } /// public void Dispose() { if(this._buffer == IntPtr.Zero) { return; } Marshal.FreeHGlobal(this._buffer); this._buffer = IntPtr.Zero; this.Length = 0; } /// /// Fast pointer memory block copy function. /// /// The destination. /// The source. /// The length. [DllImport("kernel32")] public static extern void CopyMemory(IntPtr destination, IntPtr source, UInt32 length); #endregion } }