namespace Unosquare.Swan.Components { using System; using System.Runtime.InteropServices; /// /// 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(int bufferLength) { #if !NET452 if (Runtime.OS != Swan.OperatingSystem.Windows) throw new InvalidOperationException("CircularBuffer component is only available in Windows"); #endif Length = bufferLength; _buffer = Marshal.AllocHGlobal(Length); } #region Properties /// /// Gets the capacity of this buffer. /// /// /// The length. /// public int Length { get; private set; } /// /// Gets the current, 0-based read index. /// /// /// The index of the read. /// public int ReadIndex { get; private set; } /// /// Gets the current, 0-based write index. /// /// /// The index of the write. /// public int 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 int ReadableCount { get; private set; } /// /// Gets the number of bytes that can be written. /// /// /// The writable count. /// public int WritableCount => Length - ReadableCount; /// /// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0). /// /// /// The capacity percent. /// public double CapacityPercent => 1.0 * ReadableCount / 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(int requestedBytes, byte[] target, int targetOffset) { lock (_syncLock) { if (requestedBytes > ReadableCount) { throw new InvalidOperationException( $"Unable to read {requestedBytes} bytes. Only {ReadableCount} bytes are available"); } var readCount = 0; while (readCount < requestedBytes) { var copyLength = Math.Min(Length - ReadIndex, requestedBytes - readCount); var sourcePtr = _buffer + ReadIndex; Marshal.Copy(sourcePtr, target, targetOffset + readCount, copyLength); readCount += copyLength; ReadIndex += copyLength; ReadableCount -= copyLength; if (ReadIndex >= Length) 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, int length, TimeSpan writeTag) { lock (_syncLock) { if (ReadableCount + length > Length) { throw new InvalidOperationException( $"Unable to write to circular buffer. Call the {nameof(Read)} method to make some additional room."); } var writeCount = 0; while (writeCount < length) { var copyLength = Math.Min(Length - WriteIndex, length - writeCount); var sourcePtr = source + writeCount; var targetPtr = _buffer + WriteIndex; CopyMemory(targetPtr, sourcePtr, (uint) copyLength); writeCount += copyLength; WriteIndex += copyLength; ReadableCount += copyLength; if (WriteIndex >= Length) WriteIndex = 0; } WriteTag = writeTag; } } /// /// Resets all states as if this buffer had just been created. /// public void Clear() { lock (_syncLock) { WriteIndex = 0; ReadIndex = 0; WriteTag = TimeSpan.MinValue; ReadableCount = 0; } } /// public void Dispose() { if (_buffer == IntPtr.Zero) return; Marshal.FreeHGlobal(_buffer); _buffer = IntPtr.Zero; 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, uint length); #endregion } }