Search Unity

Generic Pool discussion

Discussion in 'Scripting' started by craymond1849, Aug 15, 2021.

  1. craymond1849

    craymond1849

    Joined:
    Dec 23, 2017
    Posts:
    2
    I tried to come up with my own generic pool and I wanted to hear your advices about it and what other kind of solution you came up.
    I first tried to use generic type and interface like
    Code (CSharp):
    1. public class Pool<T> : MonoBehaviour where T : MonoBehaviour, IPoolable
    This would have allowed me to simply add the interface to any script to be able to use it in a pool.
    But in the end I found out that I can't have any generic Type to a child class of MonoBehaviour

    So instead I made a
    Code (CSharp):
    1. public class BasicPoolObject : MonoBehaviour
    Any object I wanted to be pooled will need to have this component or be its child.

    In the end however I am quite happy at how well it work.
    It pool a reference to any type of prefab
    you can get an object by either sending the prefab or the name of the prefab if it is already prefab pooled.
    The only issue I can see is if 2 different prefabs with the same name is sent to the pool, it won't give any error but will always send the first one.

    I am not an expert on benchmarking, I would like to know how efficient you think it is.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.  
    6. public class Pool : MonoBehaviour
    7. {
    8.     private static Dictionary<string, BasicPoolObject> pooledPrefabs;
    9.     private static Dictionary<string, List<BasicPoolObject>> pool;
    10.  
    11.     //singleton pattern implementation
    12.     private static Pool _pool;
    13.  
    14.     public static Pool Instance
    15.     {
    16.         get
    17.         {
    18.             if (!_pool)
    19.             {
    20.                 _pool = FindObjectOfType(typeof(Pool)) as Pool;
    21.                 if (!_pool)
    22.                 {
    23.                     Debug.Log("Error, no Pool on scene");
    24.                 }
    25.             }
    26.             else
    27.             {
    28.                 _pool.Init();
    29.             }
    30.             return _pool;
    31.         }
    32.     }
    33.  
    34.     private void Awake()
    35.     {
    36.         Instance.Init();
    37.     }
    38.  
    39.     private void Init()
    40.     {
    41.         pooledPrefabs = new Dictionary<string, BasicPoolObject>();
    42.         pool = new Dictionary<string, List<BasicPoolObject>>();
    43. }
    44.  
    45.  
    46.     public static BasicPoolObject GetObject(string prefabName)
    47.     {
    48.         BasicPoolObject prefab;
    49.         pooledPrefabs.TryGetValue(prefabName, out prefab);
    50.  
    51.         return GetObject(prefab);
    52.     }
    53.  
    54.     public static BasicPoolObject GetObject(BasicPoolObject prefab)
    55.     {
    56.         if (prefab == null)
    57.             return null;
    58.  
    59.         BasicPoolObject value;
    60.         //try to find if this type of prefab is already pooled
    61.         if(!pooledPrefabs.TryGetValue(prefab.name,out value))
    62.         {//add the prefab to the pooled prefabs and init its pool list
    63.             pooledPrefabs.Add(prefab.name, prefab);
    64.             pool.Add(prefab.name, new List<BasicPoolObject>());
    65.             value = prefab;
    66.         }
    67.  
    68.         BasicPoolObject inactiveObject = FindInactiveObjectInPool(pool[value.name]);
    69.         if(inactiveObject == null)
    70.         {//create object
    71.             int index = pool[value.name].Count;
    72.             pool[value.name].Add(CreateObject(value));
    73.            
    74.             inactiveObject = pool[value.name][index];
    75.             inactiveObject.Init(pool[value.name].Count - 1, value.name);
    76.         }
    77.         inactiveObject.gameObject.SetActive(true);
    78.         inactiveObject.Reset();
    79.  
    80.         return inactiveObject;
    81.     }
    82.     /// <summary>
    83.     /// return true if a new prefab is added
    84.     /// </summary>
    85.     /// <param name="prefab"></param>
    86.     /// <returns></returns>
    87.     public static bool AddPrefab(BasicPoolObject prefab)
    88.     {
    89.  
    90.         if(pooledPrefabs.ContainsKey(prefab.poolName))
    91.         {
    92.             return false;
    93.         }
    94.         else
    95.         {
    96.             pooledPrefabs.Add(prefab.name, prefab);
    97.             pool.Add(prefab.name, new List<BasicPoolObject>());
    98.             return true;
    99.         }
    100.     }
    101.  
    102.     public static bool DeactivateObject(BasicPoolObject poolObject)
    103.     {
    104.         List<BasicPoolObject> objList;
    105.         if (pool.TryGetValue(poolObject.poolName, out objList))
    106.         {
    107.             if(poolObject.index < objList.Count)
    108.             {
    109.                 objList[poolObject.index].gameObject.SetActive(false);
    110.                 return true;
    111.             }
    112.         }
    113.  
    114.         return false;
    115.     }
    116.  
    117.  
    118.     private static BasicPoolObject FindInactiveObjectInPool(List<BasicPoolObject> poolList)
    119.     {
    120.         return poolList.Find(x => !x.gameObject.activeSelf);
    121.     }
    122.  
    123.     private static BasicPoolObject CreateObject(BasicPoolObject prefab)
    124.     {
    125.         BasicPoolObject createdObject = Instantiate(prefab, prefab.transform.position, prefab.transform.rotation).GetComponent<BasicPoolObject>();
    126.  
    127.         return createdObject;
    128.     }
    129. }
    130.  
     
  2. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    Why don't you just pool the GameObject or Transform of the instance? What benefit does a certain component give you?

    Why do you use string as key in your Dictionary? I would prefer an enum with the "type" of object to spawn.

    If the pool does not find the object just create it and add the component.

    It seems your pool contains all pooled objects. I would consider a pool to only contain objects of a certain type. A poolmanager would hold a list of pools and would be indexed by an Enum.

    There are some free pooling solutions on the asset store (and probably in blogs and tutorials too). Have a look at them for inspiration. But in the end when it works for you it is fine.
     
  3. Adrian

    Adrian

    Joined:
    Apr 5, 2008
    Posts:
    1,066
    I do like using static pools on generic classes instead of having a central pool manager. Just having a concrete implementation of the generic class implicitly creates a pool and you don't have to look up the right pool from the pool manager. It's also more type-safe than using string or enums as identifiers.
    Note sure what your issue was here?

    Here's a quick example of how it could work:
    Code (CSharp):
    1. public abstract class Poolable : MonoBehaviour
    2. {
    3.     public abstract void Initialize();
    4.     public abstract void Reset();
    5. }
    6.  
    7. public abstract class Poolable<T> : Poolable where T : Poolable
    8. {
    9.     static Queue<T> pool = new Queue<T>();
    10.  
    11.     public static T Get()
    12.     {
    13.         T item;
    14.         if (pool.Count > 0) {
    15.             item = pool.Dequeue();
    16.         } else {
    17.             var go = new GameObject(nameof(T));
    18.             item = go.AddComponent<T>();
    19.         }
    20.  
    21.         item.Initialize();
    22.         return item;
    23.     }
    24.  
    25.     public static void Return(T item)
    26.     {
    27.         item.Reset();
    28.         pool.Enqueue(item);
    29.     }
    30. }
    31.  
    32. public class MyBehaviour : Poolable<MyBehaviour>
    33. {
    34.     public override void Initialize()
    35.     {
    36.         //
    37.     }
    38.  
    39.     public override void Reset()
    40.     {
    41.         //
    42.     }
    43. }
    44.  
    45. var instance = MyBehaviour.Get();
    46. // do something with it...
    47. MyBehaviour.Return(instance);
    48.  
     
    Bunny83 likes this.
  4. craymond1849

    craymond1849

    Joined:
    Dec 23, 2017
    Posts:
    2
    A little late but I reworked my pool system, I know there are premade solution but I am working on a test project as a hobby to better my skills.
    the code is cleaner and more efficient, and now I can use the IPoolable interface so it will be way simpler do add stuff to the pool, Im still new at using interfaces and generics.
    The other biggest change is that I do not have to do a for loop to find and inactive object

    I will probably change the string PoolName to an Enum later

    Code (CSharp):
    1. public interface IPoolable<T> where T : MonoBehaviour
    2. {
    3.     T Mono { get;}
    4.     int Index { get;}
    5.     string PoolName { get; }
    6.     void Init(int _index, string _poolName);
    7.     void Reset();
    8. }
    A pool of 1 object type
    Code (CSharp):
    1. public class Pool<T> where T : IPoolable<MonoBehaviour>
    2. {
    3.     private Dictionary<int, T> activePool;
    4.     private Queue<T> inactivePool;
    5.     private string poolName;
    6.     private T basePrefab;
    7.     public T BasePrefab { get => basePrefab; }
    8.  
    9.     public Pool(IPoolable<MonoBehaviour> prefab)
    10.     {
    11.         activePool = new Dictionary<int, T>();
    12.         inactivePool = new Queue<T>();
    13.         poolName = prefab.PoolName;
    14.         basePrefab = (T)prefab;
    15.     }
    16.  
    17.     public T GetItem()
    18.     {
    19.         if (inactivePool.Count == 0)
    20.             return default(T);
    21.         T item = inactivePool.Dequeue();
    22.         activePool.Add(item.Index, item);
    23.         item.Mono.gameObject.SetActive(true);
    24.         item.Reset();
    25.  
    26.         return item;
    27.     }
    28.  
    29.     /// <summary>
    30.     /// Only send instantiated active objects, return as active
    31.     /// </summary>
    32.     public void AddItem(T item)
    33.     {
    34.         item.Init(activePool.Count + inactivePool.Count, poolName);
    35.         activePool.Add(item.Index, item);
    36.         item.Reset();
    37.     }
    38.     /// <summary>
    39.     ///  Only send instantiated inactive objects
    40.     /// </summary>
    41.     /// <param name="item"></param>
    42.     public void AddInactiveItem(T item)
    43.     {
    44.         item.Init(activePool.Count + inactivePool.Count, poolName);
    45.         inactivePool.Enqueue(item);
    46.     }
    47.  
    48.     public bool DeactivateItem(T item)
    49.     {
    50.         return DeactivateItem(item.Index);
    51.     }
    52.  
    53.     public bool DeactivateItem(int itemIndex)
    54.     {
    55.         T item;
    56.         if (!activePool.TryGetValue(itemIndex, out item))
    57.         {
    58.             return false;
    59.         }
    60.  
    61.         activePool.Remove(itemIndex);
    62.         item.Mono.gameObject.SetActive(false);
    63.         inactivePool.Enqueue(item);
    64.  
    65.         return true;
    66.     }
    67. }
    The cleaner PoolManager
    Code (CSharp):
    1. public class PoolManager : MonoBehaviour
    2. {
    3.     private static Dictionary<string, Pool<IPoolable<MonoBehaviour>>> pools;
    4.     //singleton pattern implementation
    5.     private static PoolManager _pool;
    6.  
    7.     public static PoolManager Instance
    8.     {
    9.         get
    10.         {
    11.             if (!_pool)
    12.             {
    13.                 _pool = FindObjectOfType(typeof(PoolManager)) as PoolManager;
    14.                 if (!_pool)
    15.                 {
    16.                     Debug.Log("Error, no Pool on scene");
    17.                 }
    18.             }
    19.             else
    20.             {
    21.                 _pool.Init();
    22.             }
    23.             return _pool;
    24.         }
    25.     }
    26.  
    27.     private void Awake()
    28.     {
    29.         Instance.Init();
    30.     }
    31.  
    32.     private void Init()
    33.     {
    34.         //pooledPrefabs = new Dictionary<string, BasicPoolObject>();
    35.         pools = new Dictionary<string, Pool<IPoolable<MonoBehaviour>>>();
    36.     }
    37.  
    38.  
    39.     public static IPoolable<MonoBehaviour> GetObject(string prefabName)
    40.     {
    41.         Pool<IPoolable<MonoBehaviour>> prefabPool;
    42.         if (!pools.TryGetValue(prefabName, out prefabPool))
    43.             return null;
    44.  
    45.         return GetObject(prefabPool.BasePrefab);
    46.     }
    47.  
    48.     public static IPoolable<MonoBehaviour> GetObject(IPoolable<MonoBehaviour> prefab)
    49.     {
    50.         if (prefab == null)
    51.             return null;
    52.  
    53.         Pool<IPoolable<MonoBehaviour>> pool;
    54.         //try to find if this type of prefab is already pooled
    55.         if (!pools.TryGetValue(prefab.PoolName, out pool))
    56.         {//add the prefab to the pooled prefabs and init its pool list
    57.             pools.Add(prefab.PoolName, new Pool<IPoolable<MonoBehaviour>>(prefab));
    58.  
    59.             pool = pools[prefab.PoolName];
    60.         }
    61.  
    62.         IPoolable<MonoBehaviour> item = pool.GetItem();
    63.         //create object
    64.         if(item == null)
    65.         {
    66.             item = CreateObject(prefab);
    67.             pool.AddItem(item);
    68.         }
    69.  
    70.         return item;
    71.     }
    72.  
    73.  
    74.     /// <summary>
    75.     /// create a new pool with a prefab reference
    76.     /// </summary>
    77.     /// <param name="prefab"></param>
    78.     /// <returns></returns>
    79.     public static bool AddPool(IPoolable<MonoBehaviour> prefab)
    80.     {
    81.  
    82.         if (pools.ContainsKey(prefab.PoolName))
    83.         {
    84.             return false;
    85.         }
    86.         else
    87.         {
    88.             pools.Add(prefab.PoolName, new Pool<IPoolable<MonoBehaviour>>(prefab));
    89.             return true;
    90.         }
    91.     }
    92.  
    93.     public static bool DeactivateObject(IPoolable<MonoBehaviour> poolObject)
    94.     {
    95.         Pool<IPoolable<MonoBehaviour>> pool;
    96.         if (pools.TryGetValue(poolObject.PoolName, out pool))
    97.         {
    98.             return pool.DeactivateItem(poolObject.Index);
    99.         }
    100.  
    101.         return false;
    102.     }
    103.  
    104.  
    105.     private static BasicPoolObject FindInactiveObjectInPool(List<BasicPoolObject> poolList)
    106.     {
    107.         return poolList.Find(x => !x.gameObject.activeSelf);
    108.     }
    109.  
    110.     private static IPoolable<MonoBehaviour> CreateObject(IPoolable<MonoBehaviour> prefab)
    111.     {
    112.         GameObject createdObject = Instantiate(prefab.Mono.gameObject, prefab.Mono.transform.position, prefab.Mono.transform.rotation);
    113.  
    114.         return (IPoolable<MonoBehaviour>) createdObject.GetComponent(typeof(IPoolable<MonoBehaviour>));
    115.     }
    116. }
    A IPoolable implementation exemple with a function to test the pool system
    Code (CSharp):
    1. public class BasicPoolObject : MonoBehaviour, IPoolable<MonoBehaviour>
    2. {
    3.     public MonoBehaviour Mono { get => this;  }
    4.  
    5.     public int Index { get => index; }
    6.  
    7.     public string PoolName { get => poolName; }
    8.  
    9.     private int index;
    10.     [SerializeField]
    11.     private string poolName;
    12.  
    13.     public float endDelay;
    14.  
    15.     public void Init(int _index, string _poolName)
    16.     {
    17.         index = _index;
    18.         poolName = _poolName;
    19.      
    20.     }
    21.  
    22.     public void Reset()
    23.     {
    24.         StartCoroutine("Stop");
    25.     }
    26.  
    27.     private IEnumerator Stop()
    28.     {
    29.         yield return new WaitForSeconds(endDelay);
    30.         PoolManager.DeactivateObject(this);
    31.  
    32.  
    33.     }
    34. }
    a script to test the pool system
    Code (CSharp):
    1. public class PoolTest : MonoBehaviour
    2. {
    3.     [SerializeField]
    4.     BasicPoolObject[] prefabs;
    5.     [SerializeField]
    6.     BasicPoolObject poolTest;
    7.  
    8.     public float delay;
    9.     void Start()
    10.     {
    11.         StartCoroutine("Spawn");
    12.     }
    13.  
    14.     private IEnumerator Spawn()
    15.     {
    16.         while (true)
    17.         {
    18.             PoolManager.GetObject(prefabs[0]);
    19.             PoolManager.GetObject(prefabs[1]);
    20.             yield return new WaitForSeconds(delay);
    21.         }
    22.     }
    23. }
     
    Last edited: Aug 21, 2021