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