3
\$\begingroup\$

I've made 2 object pooling classes -

MonoObjectPooler : MonoBehaviour - can hold a single bucket of pooled objects of 1 type.

PolyObjectPooler : MonoBehaviour - can hold multiple buckets of pooled objects of different types.

The object to pool information is store in a class PooledObject

/// <summary>
/// Information holder for a pooled object
/// </summary>
[Serializable]
public class PooledObject
{
 [Tooltip(@"Name is used to differ the objects from one another")]
 public string Name;
 [Tooltip(@"What object should be created ?")]
 public GameObject Object;
 [Range(1, 10000)] [Tooltip(@"How much objects should be created ?")]
 public int Amount;
 [Tooltip(@"Can new objects be created in case there are none left ?")]
 public bool CanGrow;
 [Tooltip(@"False - objects must be created manually using Populate method
True - objects will be created automatically on awake")]
 public bool CreateOnAwake;
}

Which is later used in the 2 object pooling classes.

MonoObjectPooler : MonoBehaviour

/// <summary>
/// Object pooler class which can hold a single type of objects.
/// </summary>
public class MonoObjectPooler : MonoBehaviour
{
 /// <summary>
 /// Object to be pooled.
 /// </summary>
 public PooledObject PooledObject;
 /// <summary>
 /// List to store all the objects that will be pooled.
 /// </summary>
 private readonly List<GameObject> pooledObjects = new List<GameObject>();
 private void Awake()
 {
 //Create the pooled object if the CreateOnAwake variable is set to true.
 if (PooledObject.CreateOnAwake)
 {
 Populate();
 }
 }
 /// <summary>
 /// Populates the list of pooled objects with PooledObjects.
 /// </summary>
 public void Populate()
 {
 //Clear the previous items in the list.
 pooledObjects.Clear();
 //Load the items again
 for (int i = 0; i < PooledObject.Amount; i++)
 {
 GameObject obj = Instantiate(PooledObject.Object);
 obj.SetActive(false);
 pooledObjects.Add(obj);
 }
 }
 /// <summary>
 /// Returns a PooledObject.
 /// </summary>
 public GameObject GetPooledObject()
 {
 for (int i = 0; i < pooledObjects.Count; i++)
 {
 if (!pooledObjects[i].activeInHierarchy)
 {
 //we have an available object
 return pooledObjects[i];
 }
 }
 if (PooledObject.CanGrow)
 {
 //we ran out of objects but we can create more
 GameObject obj = Instantiate(PooledObject.Object);
 pooledObjects.Add(obj);
 return obj;
 }
 //We ran out of objects and we cant create more
 return null;
 }
}

PolyObjectPooler : MonoBehaviour

/// <summary>
/// Object pooler class which can hold many different types of objects at one place.
/// </summary>
public class PolyObjectPooler : MonoBehaviour
{
 /// <summary>
 /// Array to store all the objects that will be pooled.
 /// </summary>
 public PooledObject[] ObjectsToPool;
 /// <summary>
 /// Dictionary which holds all the created objects i.e the one you get objects from.
 /// </summary>
 private readonly Dictionary<string, List<GameObject>> pooledObjects =
 new Dictionary<string, List<GameObject>>();
 /// <summary>
 /// Dictionary to hold information about each separate pooled object.
 /// </summary>
 private readonly Dictionary<string, PooledObject> pooledObjectsContainer =
 new Dictionary<string, PooledObject>();
 private void Awake()
 {
 //Load all the required info into the dictionary.
 foreach (PooledObject objectToPool in ObjectsToPool)
 {
 pooledObjects.Add(objectToPool.Name, new List<GameObject>());
 pooledObjectsContainer.Add(objectToPool.Name, objectToPool);
 //Create any pooled object with the CreateOnAwake variable set to true.
 if (objectToPool.CreateOnAwake)
 {
 Populate(objectToPool.Name);
 }
 }
 }
 /// <summary>
 /// Creates objects by a specified pooled object name.
 /// </summary>
 /// <param name="objectName"> The name of the pooled object. </param>
 public void Populate(string objectName)
 {
 //Clear the previous items in the list.
 pooledObjects[objectName].Clear();
 //Load the items again
 for (int i = 0; i < pooledObjectsContainer[objectName].Amount; i++)
 {
 GameObject obj = Instantiate(pooledObjectsContainer[objectName].Object);
 obj.SetActive(false);
 pooledObjects[objectName].Add(obj);
 }
 }
 /// <summary>
 /// Returns a GameObject by a specified pooled object name.
 /// </summary>
 /// <param name="objectName">The name of the pooled object.</param>
 public GameObject GetPooledObject(string objectName)
 {
 for (int i = 0; i < pooledObjects[objectName].Count; i++)
 {
 if (!pooledObjects[objectName][i].activeInHierarchy)
 {
 //we have an available object
 return pooledObjects[objectName][i];
 }
 }
 if (pooledObjectsContainer[objectName].CanGrow)
 {
 //we ran out of objects but we can create more
 GameObject obj = Instantiate(pooledObjectsContainer[objectName].Object);
 pooledObjects[objectName].Add(obj);
 return obj;
 }
 //We ran out of objects and we cant create more
 return null;
 }
}

The code is relatively short and I putted a lot of comments for each method so it should be easy to understand and follow the logic, any performance and code style improvements are welcome.

asked Dec 25, 2016 at 16:57
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

I am not too familiar with Unity, so it's hard for me to comment on design. But I do have some experience with pools, so I want to point out, that depending on pool size this linear O(n) search:

for (int i = 0; i < pooledObjects.Count; i++)
{
 if (!pooledObjects[i].activeInHierarchy)
 {
 //we have an available object
 return pooledObjects[i];
 }
}

can quickly become a serious performance bottleneck, that will negate any performance gains normally associated with pools. This problem is usually solved by actually removing "in-use" objects from the pool. And explicitly adding them back once pooled object is "released". This guaranties that any object that is present in the pool at any given time is not "in-use" so you can just "pop" the first one on every request (no search is needed). However, I do not know whether or not such design is applicable to Unity.

Also, both classes look like they can use a common ancestor and/or separate ObjectPool<T> component to represent generic re-usable pool (SRP). As it stands, instantiation logic, population logic, search logic, etc. are basically copy-pasted.

t3chb0t
44.6k9 gold badges84 silver badges190 bronze badges
answered Dec 27, 2016 at 12:28
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.