using System;
using System.Collections.Generic;
namespace Quasar.Server.Networking
{
///
/// Implements a pool of byte arrays to improve allocation performance when parsing data.
///
/// This type is safe for multi-threaded operations.
public class BufferPool
{
private readonly int _bufferLength;
private int _bufferCount;
private readonly Stack _buffers;
///
/// Informs listeners when a new buffer beyond the initial length has been allocated.
///
public event EventHandler NewBufferAllocated;
///
/// Fires the NewBufferAllocated event.
///
/// The event arguments.
protected virtual void OnNewBufferAllocated(EventArgs e)
{
var handler = NewBufferAllocated;
if (handler != null)
handler(this, e);
}
///
/// Informs listeners that a buffer has been allocated.
///
public event EventHandler BufferRequested;
///
/// Raises the BufferRequested event.
///
/// The event arguments.
protected virtual void OnBufferRequested(EventArgs e)
{
var handler =BufferRequested;
if (handler != null)
handler(this, e);
}
///
/// Informs listeners that a buffer has been returned.
///
public event EventHandler BufferReturned;
///
/// Raises the BufferReturned event.
///
/// The event arguments.
protected virtual void OnBufferReturned(EventArgs e)
{
var handler = BufferReturned;
if (handler != null)
handler(this, e);
}
///
/// Gets the size of the buffers allocated from this pool.
///
public int BufferLength
{
get { return _bufferLength; }
}
///
/// Gets the maximum number of buffers available at any given time from this pool.
///
public int MaxBufferCount
{
get { return _bufferCount; }
}
///
/// Gets the current number of buffers available for use.
///
public int BuffersAvailable => _buffers.Count;
///
/// Gets or sets whether to zero the contents of a buffer when it is returned.
///
public bool ClearOnReturn { get; set; }
///
/// Creates a new buffer pool with the specified name, buffer sizes, and buffer count.
///
/// The size of the preallocated buffers.
/// The number of preallocated buffers that should be available.
/// Thrown if or
/// are zero or negative.
public BufferPool(int baseBufferLength, int baseBufferCount)
{
if (baseBufferLength <= 0)
throw new ArgumentOutOfRangeException("baseBufferLength", baseBufferLength, "Buffer length must be a positive integer value.");
if (baseBufferCount <= 0)
throw new ArgumentOutOfRangeException("baseBufferCount", baseBufferCount, "Buffer count must be a positive integer value.");
_bufferLength = baseBufferLength;
_bufferCount = baseBufferCount;
_buffers = new Stack(baseBufferCount);
for (int i = 0; i < baseBufferCount; i++)
{
_buffers.Push(new byte[baseBufferLength]);
}
}
///
/// Gets a buffer from the available pool if one is available, or else allocates a new one.
///
///
/// Buffers retrieved with this method should be returned to the pool by using the
/// ReturnBuffer method.
///
/// A byte[] from the pool.
public byte[] GetBuffer()
{
lock (_buffers)
{
if (_buffers.Count > 0)
{
byte[] buffer = _buffers.Pop();
return buffer;
}
}
return AllocateNewBuffer();
}
private byte[] AllocateNewBuffer()
{
byte[] newBuffer = new byte[_bufferLength];
_bufferCount++;
OnNewBufferAllocated(EventArgs.Empty);
return newBuffer;
}
///
/// Returns the specified buffer to the pool.
///
/// if the buffer belonged to this pool and was freed; otherwise .
///
/// If the ClearOnFree property is , then the buffer will be zeroed before
/// being restored to the pool.
///
/// The buffer to return to the pool.
/// Thrown if is .
public bool ReturnBuffer(byte[] buffer)
{
if (buffer == null)
throw new ArgumentNullException("buffer");
if (buffer.Length != _bufferLength)
return false;
if (ClearOnReturn)
Array.Clear(buffer, 0, buffer.Length);
lock (_buffers)
{
if (!_buffers.Contains(buffer))
_buffers.Push(buffer);
}
return true;
}
///
/// Increases the number of buffers available in the pool by a given size.
///
/// The number of buffers to preallocate.
/// Thrown if the system is unable to preallocate the requested number of buffers.
/// Thrown if is less than or equal to 0.
///
/// This method does not cause the NewBufferAllocated event to be raised.
///
public void IncreaseBufferCount(int buffersToAdd)
{
if (buffersToAdd <= 0)
throw new ArgumentOutOfRangeException("buffersToAdd", buffersToAdd, "The number of buffers to add must be a nonnegative, nonzero integer.");
List newBuffers = new List(buffersToAdd);
for (int i = 0; i < buffersToAdd; i++)
{
newBuffers.Add(new byte[_bufferLength]);
}
lock (_buffers)
{
_bufferCount += buffersToAdd;
for (int i = 0; i < buffersToAdd; i++)
{
_buffers.Push(newBuffers[i]);
}
}
}
///
/// Removes up to the specified number of buffers from the pool.
///
/// The number of buffers to attempt to remove.
/// The number of buffers actually removed.
///
/// The number of buffers removed may actually be lower than the number requested if the specified number of buffers are not free.
/// For example, if the number of buffers free is 15, and the callee requests the removal of 20 buffers, only 15 will be freed, and so the
/// returned value will be 15.
///
/// Thrown if is less than or equal to 0.
public int DecreaseBufferCount(int buffersToRemove)
{
if (buffersToRemove <= 0)
throw new ArgumentOutOfRangeException("buffersToRemove", buffersToRemove, "The number of buffers to remove must be a nonnegative, nonzero integer.");
int numRemoved = 0;
lock (_buffers)
{
for (int i = 0; i < buffersToRemove && _buffers.Count > 0; i++)
{
_buffers.Pop();
numRemoved++;
_bufferCount--;
}
}
return numRemoved;
}
}
}