using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace Interpolation
{
///
/// A collection that maintains a finite 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 : IEnumerable where T : class
{
// the actual items of the pool
private readonly T[] items;
// we keep track of a list so we can provide an enumerator that only includes active objects
private readonly List enumerableItemList = new List();
// 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 maximum number of items capable of fitting in the pool.
///
public int MaximumSize { get { return items.Length; } }
///
/// Gets the current number of allocated instances in the pool.
///
///
/// This value includes invalid objects and is only exposed for debugging and profiling
/// purposes by providing a method for games to know how many objects are actually
/// being allocated by the pool.
///
public int AllocationSize { get; private 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 >= MaximumSize)
throw new IndexOutOfRangeException("The index must be less than or equal to ValidCount");
return items[index];
}
}
///
/// Creates a new pool of allocated instances.
///
///
/// This will instantiate the entire array of objects for the pool. If you wish
/// to perform lazy instantiation, use one of the constructors that takes in an
/// initialAllocation parameter to set how many instances to create in the
/// constructor.
///
/// The maximum size of the pool.
/// A predicate used to determine if a given object is still valid.
public Pool(int size, Predicate validateFunc) : this(size, size, validateFunc) { }
///
/// Creates a new pool with a specific number of pre-allocated instances.
///
/// The maximum size of the pool.
/// The number of items to instantiate at construction time.
/// A predicate used to determine if a given object is still valid.
public Pool(int size, int initialAllocation, Predicate validateFunc) : this(size, initialAllocation, validateFunc, null) { }
///
/// Creates a new pool with a specific number of pre-allocated instances.
///
/// The maximum size of the pool.
/// The number of items to instantiate at construction time.
/// 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 size, int initialAllocation, Predicate validateFunc, Allocate allocateFunc)
{
// validate some parameters
if (size < 1)
throw new ArgumentException("size must be greater than zero");
if (initialAllocation < 0 || initialAllocation > size)
throw new ArgumentException("initialAllocation must be non-negative and cannot be larger than size");
if (validateFunc == null)
throw new ArgumentNullException("validateFunc");
items = new T[size];
validate = validateFunc;
InvalidCount = size;
// 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("No allocateFunc was provided and " + typeof(T) + " does not have a parameterless constructor.");
}
// allocate the initial set of instances
AllocationSize = initialAllocation;
for (int i = 0; i < initialAllocation; i++)
{
T obj = allocate();
if (obj == null)
throw new InvalidOperationException("The pool's allocate method returned a null object reference.");
items[i] = obj;
}
}
///
/// 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;
}
// remove the item from the enumerable list
enumerableItemList.Remove(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 (InvalidCount > 0)
{
// 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;
AllocationSize++;
}
// initialize the object if a delegate was provided
if (Initialize != null)
Initialize(obj);
// add it to our enumerable list
enumerableItemList.Add(obj);
return obj;
}
return null;
}
///
/// Returns an enumerator that iterates through the collection.
///
///
/// A that can be used to iterate through the collection.
///
/// 1
public IEnumerator GetEnumerator()
{
return enumerableItemList.GetEnumerator();
}
///
/// Returns an enumerator that iterates through a collection.
///
///
/// An object that can be used to iterate through the collection.
///
/// 2
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// 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();
}
}