All-Purpose Object Pooling System: Criticisms, Suggestions?

I’ve written a generic object pooling class that should be able to pool instances of any class that inherits from UnityEngine.Object without making any changes to that class. I’ve also written some classes that simplify pooling for commonly used object types like GameObjects and MonoBehaviours, and an interface so that they can be used interchangeably after construction and new classes can be written easily. I’d like to put it on the Unify Community Wiki, but first I want to make sure that the code is as useful, concise and intuitive as it can be. Suggestions welcome.

sample use of GameObjectPool:

IPool<GameObject> goPool =  new GameObjectPool(prefab, 40);
GameObject poolie = goPool.Get();
goPool.Return(poolie);

sample use of GenericPool with anonymous methods:

IPool<CustomType> customPool = new GenericPool<CustomType>
	(
		delegate() {return new CustomType();},
		80,
		delegate(CustomType customType) {customType.value = 0;},
		delegate(CustomType customType) {customType.activated = false;},
		delegate(CustomType customType) {customType.activated = true;},
	);

sample use of GenericPool with an object to copy and method references:

IPool<CustomType> customPool = new GenericPool<CustomType> (customInstance, 80, Reset, Disable, Enable);

the code itself:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Linq;

public interface IPool<T> {
	T Get();
	void Return (T objectToPool);
	void CopyTo (T[] array);
	
	int Count{get;}
}

public class GenericPool<T> : UnityEngine.Object, IPool<T> where T : UnityEngine.Object {
	protected HashSet<T> pool;
	
	protected T original; //if provided, all pool objects will be copies of this object
	protected Func<T> Constructor; //creates the pool objects from a given method.
	protected Action<T> Recycler; //called on each object when it is returned to the pool. it should reset all changed values.
	
	protected Action<T> Disabler; 
	//called on each object when it is returned to the pool. it should "hide" the object from anything that might try to
	//interact with it, or keep the object from doing anything.
	
	protected Action<T> Activator;
	//called on each object when it is gotten from the pool. it should undo whatever the disabler did so that the object
	//is able to act freely again.
	
	public int Count {get { return pool.Count; }} //gets the current total number of objects in pool.
	
	//create a pool containing copies of a given object.
	public GenericPool (T original, int initialSize, Action<T> Recycler, Action<T> Disabler, Action<T> Activator) {
		this.original = original;
		this.Recycler = Recycler;
		this.Disabler = Disabler;
		this.Activator = Activator;
		
		for (int i = 0; i < initialSize; i++)
		{
			pool.Add(MakeNew());
		}
	}
	
	//create a pool of objects built by a passed-in function.
	public GenericPool (Func<T> Constructor, int initialSize, Action<T> Recycler, Action<T> Disabler, Action<T> Activator) {
		this.Constructor = Constructor;
		this.Recycler = Recycler;
		this.Disabler = Disabler;
		this.Activator = Activator;
		
		for (int i = 0; i < initialSize; i++)
		{
			pool.Add(MakeNew());
		}
	}
	
	protected GenericPool () {
		
	}
	
	//creates a new instance of whatever object we're pooling
	protected virtual T MakeNew () {
		T copy;
		if (original != null) //if the pool was provided an original objec to copy, copy it
		{
			copy = Instantiate(original) as T;
			Disabler(copy);
		}
		else if (Constructor != null) //if a constructor was supplied instead, call it
		{
			copy = Constructor();
			Disabler(copy);
		}
		else
		{
			copy = null;
			Debug.Log("couldn't create object for pool because there was no original to copy and no constructor provided.");
		}
		return copy;
	}
	
	T IPool<T>.Get () {
		T unPooledObject;
		
		if (Count == 0)
		{
			unPooledObject = MakeNew();
		}
		else
		{
			unPooledObject = pool.First();
			pool.Remove(unPooledObject);
		}
		if (Activator != null)
		{
			Activator(unPooledObject);
		}
		return unPooledObject;
	}
	
	void IPool<T>.Return (T objectToPool) {
		if (Recycler != null)
		{
			Recycler(objectToPool);
		}
		if (Disabler != null)
		{
			Disabler(objectToPool);
		}
		pool.Add(objectToPool);
	}
	
	void IPool<T>.CopyTo (T[] array) {
		pool.CopyTo(array);
	}
}
 
public class GameObjectPool : GenericPool<GameObject> {

	public GameObjectPool (GameObject original, int initialSize, Action<GameObject> Recycler = null) {
		pool = new HashSet<GameObject>();
		this.original = original;
		this.Recycler = Recycler;
		this.Disabler = Disable;
		this.Activator = Activate;
		
		for (int i = 0; i < initialSize; i++)
		{
			pool.Add(MakeNew());
		}
	}
	
	private void Disable (GameObject go) {
		go.SetActiveRecursively(false);
		go.hideFlags = HideFlags.HideInHierarchy;
	}
	
	private void Activate (GameObject go) {
		go.hideFlags = 0;
		go.SetActiveRecursively(true);
	}
}

public class ComponentPool<T> : GenericPool<T> where T : UnityEngine.Component {
	protected GameObject attatchedObject;
	
	public ComponentPool (GameObject attatchedObject, int initialSize, Func<T> Constructor = null, Action<T> Recycler = null) {
		pool = new HashSet<T>();
		this.attatchedObject = attatchedObject;
		this.Constructor = Constructor;
		this.Recycler = Recycler;
		
		for (int i = 0; i < initialSize; i++)
		{
			pool.Add(MakeNew());
		}
	}
	
	protected ComponentPool () {
		
	}
	
	protected override T MakeNew () {
		T copy;
		
		if (Constructor != null)
		{
			copy = Constructor();
		}
		else
		{
			copy = attatchedObject.AddComponent<T>();
		}
		return copy;
	}
}

public class MonoBehaviourPool<T> : ComponentPool<T> where T : UnityEngine.MonoBehaviour {
	
	public MonoBehaviourPool (GameObject attatchedObject, int initialSize, Func<T> Constructor = null, Action<T> Recycler = null) {
		pool = new HashSet<T>();
		this.attatchedObject = attatchedObject;
		this.Constructor = Constructor;
		this.Recycler = Recycler;
		this.Disabler = Disable;
		this.Activator = Activate;
		
		for (int i = 0; i < initialSize; i++)
		{
			T copy = MakeNew();
			copy.enabled = false;
			pool.Add(copy);
		}
	}
	
	private void Activate (T behaviour) {
		behaviour.enabled = true;
	}
	
	private void Disable (T behaviour) {
		behaviour.CancelInvoke();
		behaviour.StopAllCoroutines();
		behaviour.enabled = false;
	}
}

Teatime, thanks for your work.
Maybe a singletone with a hastable, list, or an array, could make a similar work.
In may case, i use what i say… plus a couple of method to register and unregister the object which can be, virtually, whatever (unless you use GENERIC… which is a cool feature but for other scope).
We will see what the people says!

Thanks again.

i thought about making it inherently a singleton, but that seemed too inflexible. creating a static dictionary<string, IPool> in another class should provide the same easy-access functionality, and this allows for strictly local pools as well.

Can you post a simple demo scene with for example pooling some cubes and spheres?

Thanks,

Peter.

I can’t figure out how to create a global objectpool. In my case it will hold bullets that can be fetched from all other scripts. If I make IPool goPool = new GameObjectPool(prefab, 40); public I will get the following error:

PoolObjects.cs(10,34): error CS0052: Inconsistent accessibility: field type IPool<UnityEngine.GameObject>' is less accessible than field PoolObjects.goPool’

Please help :wink:

Peter.

wow, sorry, just add the word “public” to the beginning of the line “interface IPool {” or c+p the edited version in the first post and that error should go away. somehow it was there on my copy but not in here.

That fixed the problem ;-). I implemented already a few pools and the game starts to run smoothly.

What happens if I don’t return objects back to the pool and just destroy them? Does the pool generate on the fly new copies to get the maximum precache again? I have some objects that are somewhat difficult to recycle like particle effects.

Peter.

if you get an object from the pool when it is empty, it will automatically generate and send you a new object. the pool keeps no reference of currently unpooled objects so it makes no difference to the pool if you just destroy them.