using System;
using System.Reflection;
using System.Diagnostics;
namespace Utility
{
///
/// A collection that maintains a set of class instances to allow for recycling
/// instances and minimizing the effects of garbage collection.
///
/// The type of object to store in the Pool. Pools can only hold class types.
public class Pool where T : class
{
// the amount to enlarge the items array if New is called and there are no free items
private const int ResizeAmount = 20;
// the actual items of the pool
private T[] items;
// used for checking if a given object is still valid
private readonly Predicate validate;
// used for allocating instances of the object
private readonly Allocate allocate;
// a constructor the default allocate method can use to create instances
private readonly ConstructorInfo constructor;
///
/// Gets or sets a delegate used for initializing objects before returning them from the New method.
///
public Action Initialize { get; set; }
///
/// Gets or sets a delegate that is run when an object is moved from being valid to invalid
/// in the CleanUp method.
///
public Action Deinitialize { get; set; }
///
/// Gets the number of valid objects in the pool.
///
public int ValidCount { get { return items.Length - InvalidCount; } }
///
/// Gets the number of invalid objects in the pool.
///
public int InvalidCount { get; private set; }
///
/// Returns a valid object at the given index. The index must fall in the range of [0, ValidCount].
///
/// The index of the valid object to get
/// A valid object found at the index
public T this[int index]
{
get
{
index += InvalidCount;
if (index < InvalidCount || index >= items.Length)
throw new IndexOutOfRangeException("The index must be less than or equal to ValidCount");
return items[index];
}
}
///
/// Creates a new pool.
///
/// A predicate used to determine if a given object is still valid.
public Pool(Predicate validateFunc) : this(0, validateFunc) { }
///
/// Creates a new pool with a specific starting size.
///
/// The initial size of the pool.
/// A predicate used to determine if a given object is still valid.
public Pool(int initialSize, Predicate validateFunc) : this(initialSize, validateFunc, null) { }
///
/// Creates a new pool with a specific starting size.
///
/// The initial size of the pool.
/// A predicate used to determine if a given object is still valid.
/// A function used to allocate an instance for the pool.
public Pool(int initialSize, Predicate validateFunc, Allocate allocateFunc)
{
// validate some parameters
if (initialSize < 0)
throw new ArgumentException("initialSize must be non-negative");
if (validateFunc == null)
throw new ArgumentNullException("validateFunc");
if (initialSize == 0)
initialSize = 10;
items = new T[initialSize];
validate = validateFunc;
InvalidCount = items.Length;
// default to using a parameterless constructor if no allocateFunc was given
allocate = allocateFunc ?? ConstructorAllocate;
// if we are using the ConstructorAllocate method, make sure we have a valid parameterless constructor
if (allocate == ConstructorAllocate)
{
// we want to find any parameterless constructor, public or not
constructor = typeof(T).GetConstructor(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
null,
new Type[] { },
null);
if (constructor == null)
throw new InvalidOperationException(typeof(T) + " does not have a parameterless constructor.");
}
}
///
/// Cleans up the pool by checking each valid object to ensure it is still actually valid.
///
public void CleanUp()
{
for (int i = InvalidCount; i < items.Length; i++)
{
T obj = items[i];
// if it's still valid, keep going
if (validate(obj))
continue;
// otherwise if we're not at the start of the invalid objects, we have to move
// the object to the invalid object section of the array
if (i != InvalidCount)
{
items[i] = items[InvalidCount];
items[InvalidCount] = obj;
}
// clean the object if desired
if (Deinitialize != null)
Deinitialize(obj);
InvalidCount++;
}
}
///
/// Returns a new object from the Pool.
///
/// The next object in the pool if available, null if all instances are valid.
public T New()
{
// if we're out of invalid instances, resize to fit some more
if (InvalidCount == 0)
{
#if DEBUG
Trace.WriteLine("Resizing pool. Old size: " + items.Length + ". New size: " + (items.Length + ResizeAmount));
#endif
// create a new array with some more slots and copy over the existing items
T[] newItems = new T[items.Length + ResizeAmount];
for (int i = items.Length - 1; i >= 0; i--)
newItems[i + ResizeAmount] = items[i];
items = newItems;
// move the invalid count based on our resize amount
InvalidCount += ResizeAmount;
}
// decrement the counter
InvalidCount--;
// get the next item in the list
T obj = items[InvalidCount];
// if the item is null, we need to allocate a new instance
if (obj == null)
{
obj = allocate();
if (obj == null)
throw new InvalidOperationException("The pool's allocate method returned a null object reference.");
items[InvalidCount] = obj;
}
// initialize the object if a delegate was provided
if (Initialize != null)
Initialize(obj);
return obj;
}
// a default Allocate delegate for use when no custom allocate delegate is provided
private T ConstructorAllocate()
{
return constructor.Invoke(null) as T;
}
///
/// A delegate that returns a new object instance for the Pool.
///
/// A new object instance.
public delegate T Allocate();
}
}