Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

All-Purpose Object Pooling System: Criticisms, Suggestions?

Discussion in 'Scripting' started by teatime, Mar 16, 2011.

  1. teatime

    teatime

    Joined:
    Jun 16, 2008
    Posts:
    129
    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:
    Code (csharp):
    1. IPool<GameObject> goPool =  new GameObjectPool(prefab, 40);
    2. GameObject poolie = goPool.Get();
    3. goPool.Return(poolie);
    4.  
    sample use of GenericPool with anonymous methods:
    Code (csharp):
    1. IPool<CustomType> customPool = new GenericPool<CustomType>
    2.     (
    3.         delegate() {return new CustomType();},
    4.         80,
    5.         delegate(CustomType customType) {customType.value = 0;},
    6.         delegate(CustomType customType) {customType.activated = false;},
    7.         delegate(CustomType customType) {customType.activated = true;},
    8.     );
    sample use of GenericPool with an object to copy and method references:
    Code (csharp):
    1. IPool<CustomType> customPool = new GenericPool<CustomType> (customInstance, 80, Reset, Disable, Enable);
    the code itself:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5. using System.Linq;
    6.  
    7. public interface IPool<T> {
    8.     T Get();
    9.     void Return (T objectToPool);
    10.     void CopyTo (T[] array);
    11.    
    12.     int Count{get;}
    13. }
    14.  
    15. public class GenericPool<T> : UnityEngine.Object, IPool<T> where T : UnityEngine.Object {
    16.     protected HashSet<T> pool;
    17.    
    18.     protected T original; //if provided, all pool objects will be copies of this object
    19.     protected Func<T> Constructor; //creates the pool objects from a given method.
    20.     protected Action<T> Recycler; //called on each object when it is returned to the pool. it should reset all changed values.
    21.    
    22.     protected Action<T> Disabler;
    23.     //called on each object when it is returned to the pool. it should "hide" the object from anything that might try to
    24.     //interact with it, or keep the object from doing anything.
    25.    
    26.     protected Action<T> Activator;
    27.     //called on each object when it is gotten from the pool. it should undo whatever the disabler did so that the object
    28.     //is able to act freely again.
    29.    
    30.     public int Count {get { return pool.Count; }} //gets the current total number of objects in pool.
    31.    
    32.     //create a pool containing copies of a given object.
    33.     public GenericPool (T original, int initialSize, Action<T> Recycler, Action<T> Disabler, Action<T> Activator) {
    34.         this.original = original;
    35.         this.Recycler = Recycler;
    36.         this.Disabler = Disabler;
    37.         this.Activator = Activator;
    38.        
    39.         for (int i = 0; i < initialSize; i++)
    40.         {
    41.             pool.Add(MakeNew());
    42.         }
    43.     }
    44.    
    45.     //create a pool of objects built by a passed-in function.
    46.     public GenericPool (Func<T> Constructor, int initialSize, Action<T> Recycler, Action<T> Disabler, Action<T> Activator) {
    47.         this.Constructor = Constructor;
    48.         this.Recycler = Recycler;
    49.         this.Disabler = Disabler;
    50.         this.Activator = Activator;
    51.        
    52.         for (int i = 0; i < initialSize; i++)
    53.         {
    54.             pool.Add(MakeNew());
    55.         }
    56.     }
    57.    
    58.     protected GenericPool () {
    59.        
    60.     }
    61.    
    62.     //creates a new instance of whatever object we're pooling
    63.     protected virtual T MakeNew () {
    64.         T copy;
    65.         if (original != null) //if the pool was provided an original objec to copy, copy it
    66.         {
    67.             copy = Instantiate(original) as T;
    68.             Disabler(copy);
    69.         }
    70.         else if (Constructor != null) //if a constructor was supplied instead, call it
    71.         {
    72.             copy = Constructor();
    73.             Disabler(copy);
    74.         }
    75.         else
    76.         {
    77.             copy = null;
    78.             Debug.Log("couldn't create object for pool because there was no original to copy and no constructor provided.");
    79.         }
    80.         return copy;
    81.     }
    82.    
    83.     T IPool<T>.Get () {
    84.         T unPooledObject;
    85.        
    86.         if (Count == 0)
    87.         {
    88.             unPooledObject = MakeNew();
    89.         }
    90.         else
    91.         {
    92.             unPooledObject = pool.First();
    93.             pool.Remove(unPooledObject);
    94.         }
    95.         if (Activator != null)
    96.         {
    97.             Activator(unPooledObject);
    98.         }
    99.         return unPooledObject;
    100.     }
    101.    
    102.     void IPool<T>.Return (T objectToPool) {
    103.         if (Recycler != null)
    104.         {
    105.             Recycler(objectToPool);
    106.         }
    107.         if (Disabler != null)
    108.         {
    109.             Disabler(objectToPool);
    110.         }
    111.         pool.Add(objectToPool);
    112.     }
    113.    
    114.     void IPool<T>.CopyTo (T[] array) {
    115.         pool.CopyTo(array);
    116.     }
    117. }
    118.  
    119. public class GameObjectPool : GenericPool<GameObject> {
    120.  
    121.     public GameObjectPool (GameObject original, int initialSize, Action<GameObject> Recycler = null) {
    122.         pool = new HashSet<GameObject>();
    123.         this.original = original;
    124.         this.Recycler = Recycler;
    125.         this.Disabler = Disable;
    126.         this.Activator = Activate;
    127.        
    128.         for (int i = 0; i < initialSize; i++)
    129.         {
    130.             pool.Add(MakeNew());
    131.         }
    132.     }
    133.    
    134.     private void Disable (GameObject go) {
    135.         go.SetActiveRecursively(false);
    136.         go.hideFlags = HideFlags.HideInHierarchy;
    137.     }
    138.    
    139.     private void Activate (GameObject go) {
    140.         go.hideFlags = 0;
    141.         go.SetActiveRecursively(true);
    142.     }
    143. }
    144.  
    145. public class ComponentPool<T> : GenericPool<T> where T : UnityEngine.Component {
    146.     protected GameObject attatchedObject;
    147.    
    148.     public ComponentPool (GameObject attatchedObject, int initialSize, Func<T> Constructor = null, Action<T> Recycler = null) {
    149.         pool = new HashSet<T>();
    150.         this.attatchedObject = attatchedObject;
    151.         this.Constructor = Constructor;
    152.         this.Recycler = Recycler;
    153.        
    154.         for (int i = 0; i < initialSize; i++)
    155.         {
    156.             pool.Add(MakeNew());
    157.         }
    158.     }
    159.    
    160.     protected ComponentPool () {
    161.        
    162.     }
    163.    
    164.     protected override T MakeNew () {
    165.         T copy;
    166.        
    167.         if (Constructor != null)
    168.         {
    169.             copy = Constructor();
    170.         }
    171.         else
    172.         {
    173.             copy = attatchedObject.AddComponent<T>();
    174.         }
    175.         return copy;
    176.     }
    177. }
    178.  
    179. public class MonoBehaviourPool<T> : ComponentPool<T> where T : UnityEngine.MonoBehaviour {
    180.    
    181.     public MonoBehaviourPool (GameObject attatchedObject, int initialSize, Func<T> Constructor = null, Action<T> Recycler = null) {
    182.         pool = new HashSet<T>();
    183.         this.attatchedObject = attatchedObject;
    184.         this.Constructor = Constructor;
    185.         this.Recycler = Recycler;
    186.         this.Disabler = Disable;
    187.         this.Activator = Activate;
    188.        
    189.         for (int i = 0; i < initialSize; i++)
    190.         {
    191.             T copy = MakeNew();
    192.             copy.enabled = false;
    193.             pool.Add(copy);
    194.         }
    195.     }
    196.    
    197.     private void Activate (T behaviour) {
    198.         behaviour.enabled = true;
    199.     }
    200.    
    201.     private void Disable (T behaviour) {
    202.         behaviour.CancelInvoke();
    203.         behaviour.StopAllCoroutines();
    204.         behaviour.enabled = false;
    205.     }
    206. }
     
    Last edited: Mar 26, 2011
  2. Kokumo

    Kokumo

    Joined:
    Jul 23, 2010
    Posts:
    416
    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.
     
  3. teatime

    teatime

    Joined:
    Jun 16, 2008
    Posts:
    129
    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.
     
    Last edited: Mar 16, 2011
  4. TriplePAF

    TriplePAF

    Joined:
    Aug 19, 2009
    Posts:
    246
    Can you post a simple demo scene with for example pooling some cubes and spheres?


    Thanks,


    Peter.
     
  5. TriplePAF

    TriplePAF

    Joined:
    Aug 19, 2009
    Posts:
    246
    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<GameObject> 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 ;-)


    Peter.
     
    Last edited: Mar 26, 2011
  6. teatime

    teatime

    Joined:
    Jun 16, 2008
    Posts:
    129
    wow, sorry, just add the word "public" to the beginning of the line "interface IPool<T> {" 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.
     
  7. TriplePAF

    TriplePAF

    Joined:
    Aug 19, 2009
    Posts:
    246
    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.
     
  8. teatime

    teatime

    Joined:
    Jun 16, 2008
    Posts:
    129
    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.