2019-12-03 18:44:25 +01:00
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 ;
2019-02-17 14:08:57 +01:00
/// <summary>
2019-12-03 18:44:25 +01:00
/// Initializes a new instance of the <see cref="CircularBuffer"/> class.
2019-02-17 14:08:57 +01:00
/// </summary>
2019-12-03 18:44:25 +01:00
/// <param name="bufferLength">Length of the buffer.</param>
public CircularBuffer ( Int32 bufferLength ) {
2019-02-17 14:08:57 +01:00
#if ! NET452
if ( Runtime . OS ! = Swan . OperatingSystem . Windows )
throw new InvalidOperationException ( "CircularBuffer component is only available in Windows" ) ;
#endif
2019-12-03 18:44:25 +01:00
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
}
2019-02-17 14:08:57 +01:00
}