using System;
using System.Collections.Generic;
namespace xServer.Core.Networking
{
///
/// Implements a pool of byte arrays to improve allocation performance when parsing data.
///
/// This type is safe for multithreaded operations.
public class PooledBufferManager
{
private readonly int _bufferLength;
private int _bufferCount;
private readonly Stack _buffers;
#region events
///
/// 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)
{
if (NewBufferAllocated != null)
NewBufferAllocated(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)
{
if (BufferRequested != null)
BufferRequested(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)
{
if (BufferReturned != null)
BufferReturned(this, e);
}
#endregion
#region properties
///
/// 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
{
get { return _buffers.Count; }
}
///
/// Gets or sets whether to zero the contents of a buffer when it is returned.
///
public bool ClearOnReturn { get; set; }
#endregion
#region constructor
///
/// 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 PooledBufferManager(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]);
}
}
#endregion
#region methods
///
/// 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()
{
if (_buffers.Count > 0)
{
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)
{
for (int i = 0; i < _bufferLength; i++)
{
buffer[i] = 0;
}
}
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++;
}
}
return numRemoved;
}
#endregion
}
}