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