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(); } }