RaspberryIO/Unosquare.Swan/Components/CircularBuffer.cs
2019-12-03 18:44:25 +01:00

202 lines
6.2 KiB
C#

using System;
using System.Runtime.InteropServices;
namespace Unosquare.Swan.Components {
/// <summary>
/// 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.
/// </summary>
/// <seealso cref="System.IDisposable" />
public sealed class CircularBuffer : IDisposable {
private readonly Object _syncLock = new Object();
private IntPtr _buffer = IntPtr.Zero;
/// <summary>
/// Initializes a new instance of the <see cref="CircularBuffer"/> class.
/// </summary>
/// <param name="bufferLength">Length of the buffer.</param>
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
/// <summary>
/// Gets the capacity of this buffer.
/// </summary>
/// <value>
/// The length.
/// </value>
public Int32 Length {
get; private set;
}
/// <summary>
/// Gets the current, 0-based read index.
/// </summary>
/// <value>
/// The index of the read.
/// </value>
public Int32 ReadIndex {
get; private set;
}
/// <summary>
/// Gets the current, 0-based write index.
/// </summary>
/// <value>
/// The index of the write.
/// </value>
public Int32 WriteIndex {
get; private set;
}
/// <summary>
/// Gets an the object associated with the last write.
/// </summary>
/// <value>
/// The write tag.
/// </value>
public TimeSpan WriteTag { get; private set; } = TimeSpan.MinValue;
/// <summary>
/// Gets the available bytes to read.
/// </summary>
/// <value>
/// The readable count.
/// </value>
public Int32 ReadableCount {
get; private set;
}
/// <summary>
/// Gets the number of bytes that can be written.
/// </summary>
/// <value>
/// The writable count.
/// </value>
public Int32 WritableCount => this.Length - this.ReadableCount;
/// <summary>
/// Gets percentage of used bytes (readbale/available, from 0.0 to 1.0).
/// </summary>
/// <value>
/// The capacity percent.
/// </value>
public Double CapacityPercent => 1.0 * this.ReadableCount / this.Length;
#endregion
#region Methods
/// <summary>
/// Reads the specified number of bytes into the target array.
/// </summary>
/// <param name="requestedBytes">The requested bytes.</param>
/// <param name="target">The target.</param>
/// <param name="targetOffset">The target offset.</param>
/// <exception cref="System.InvalidOperationException">
/// Exception that is thrown when a method call is invalid for the object's current state.
/// </exception>
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;
}
}
}
}
/// <summary>
/// Writes data to the backing buffer using the specified pointer and length.
/// and associating a write tag for this operation.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="length">The length.</param>
/// <param name="writeTag">The write tag.</param>
/// <exception cref="System.InvalidOperationException">Unable to write to circular buffer. Call the Read method to make some additional room.</exception>
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;
}
}
/// <summary>
/// Resets all states as if this buffer had just been created.
/// </summary>
public void Clear() {
lock(this._syncLock) {
this.WriteIndex = 0;
this.ReadIndex = 0;
this.WriteTag = TimeSpan.MinValue;
this.ReadableCount = 0;
}
}
/// <inheritdoc />
public void Dispose() {
if(this._buffer == IntPtr.Zero) {
return;
}
Marshal.FreeHGlobal(this._buffer);
this._buffer = IntPtr.Zero;
this.Length = 0;
}
/// <summary>
/// Fast pointer memory block copy function.
/// </summary>
/// <param name="destination">The destination.</param>
/// <param name="source">The source.</param>
/// <param name="length">The length.</param>
[DllImport("kernel32")]
public static extern void CopyMemory(IntPtr destination, IntPtr source, UInt32 length);
#endregion
}
}