New Pooling API in Unity 2021
In games in most cases you are faced with a lot of instantiations and disposes of objects but when you reach some amount of instantiations every second performance can degrade dramatically.
To deal with it you have several options:
- Reduce amount of instantiating objects per second
- Buy some pooling system in Asset Store
- Implement your own pooling system
- Use new Unity Pooling API
Yes, Unity 2021+ has a great built-in pooling API and we are going to talk about it in this article.
Object Pool
The object pool is a well known creational design pattern. The main idea is that right after start the pool contains a list of already instantiated objects. When in a program you need a new instance of a specific object you do not directly instantiate it instead you ask the object pool for it and when you already do not need it you return it back to the object pool.
In case of using object pool we have no instantiations and dispose all the time it means no memory allocations so for the garbage collector much less work here (and no micro-freezes).
UnityEngine.Pool
The UnityEngine.Pool namespace contains several pools:
- CollectionPool<T0,T1>
- DictionaryPool<T0,T1>
- GenericPool<T0>
- HashSetPool<T0>
- LinkedPool<T0>
- ListPool<T0>
- ObjectPool<T0>
- UnsafeGenericPool<T0>
CollectionPool<T0,T1>
This is the base implementation of object pool for collections like Dictionary (DictionaryPool<T0,T1>), List (ListPool<T0>), HashSet (HashSetPool<T0>).
Base CollectionPool<T0,T1> and derived classes have the same static API.
- Public static TCollection Get()
- Public static PooledObject<TCollection> Get(out TCollection value)
As an example let’s assume that you need to generate geometry at runtime, so you often need a few collections like List<Vector3> to store generated vertices and you need to run generation periodically. In that case you can use ListPool<T0>:
var vertices = ListPool<Vector3>.Get();
GenerateMesh(vertices);
ListPool<Vector3>.Release(vertices);
Example of using ListPool
As you can see, using ListPool<T0> is very simple:
- Get a list from the pool
- Use the list as you want
- Return the list back to the pool
The pool clears the list when it is returned back to the pool.
This approach is good if you need to keep a list for a while but in case when you need a list in one place in one frame CollectionPool has a shorter way:
using (var pooledObject = ListPool<Vector2>.Get(out List<Vector3> vertices))
{
GenerateMesh(vertices);
}
Example of using ListPool with using operator
When the pooled object leaves the scope it will be Disposed and returned to the pool automatically.
Usage of CollectionPool is easy but you don’t have control on how an object is created, initialized and deinitialized.
ObjectPool<T0>
The ObjectPool<T0> was created to allow you to have full control of how objects are created, initialized and deinitialized.
In many cases you need a pool for GameObjects so the example below will be for GameObject.
To create an instance of ObjectPool the first thing you need is to define how to create an object which will be in the pool.
GameObject CreatePooledItem()
{
var pooledGameObject = new GameObject("Pooled GameObject");
// Add components or set other properties
…
return pooledGameObject;
}
You can add components, add children GameObjects, instantiate prefabs and so on.
Then you need to define what happens when an object will be taken from the pool.
void OnTakeFromPool(GameObject pooledGameObject)
{
pooledGameObject.SetActive(true);
// reinitialize object, run animation and any other things
}
Then you need to define what to do when the object returns to the pool. Usually just deactivate an object.
void OnReturnedToPool(GameObject pooledGameObject)
{
pooledGameObject.SetActive(false);
// any other stuff here
}
In some cases that will be described a little bit later, objects need to be destroyed. So you need to define how to destroy.
void OnDestroyPoolObject(GameObject pooledGameObject)
{
Destroy(pooledGameObject);
}
Ok, by now we have defined all necessary methods which the pool needs. So let’s create the pool instance!
var poolInstance = new ObjectPool<GameObject>(
CreatePooledItem,
OnTakeFromPool,
OnReturnedToPool,
OnDestroyPoolObject,
collectionChecks,
10,
100);
You may notice the pool constructor needs three more parameters:
- CollectionChecks — collection checks are performed when an instance is returned back to the pool. An exception will be thrown if the instance is already in the pool. Collection checks are only performed in the Editor.
- DefaultCapacity — the default capacity the stack will be created with.
- MaxPoolSize — the maximum size of the pool, if you request more objects from the pool it will create a new instance for you but when you return it to the pool it will be destroyed. That’s why we need the method that we defined later OnDestroyPoolObject.
Now you can use the pool instance as previous ones. The instance has the same two methods to retrieve object from the pool:
- Public T Get()
- Public PooledObject<T> Get(out T v)
and one that allows you to return an object to the pool:
- Public void Release(T element)
Also there exist three more pool implementations:
- LinkedPool<T0> — the difference between ObjectPool and LinkedPool that ObjectPool uses stack for storing object and LinkedPool uses list
- GenericPool<T0> — static implementation of ObjectPool
- UnsafeGenericPool<T0> — the same as GenericPool but without collection check
Conclusion
As you can see the new Pool API in Unity 2021 is easy to use and allows you to have full control of how and what is done at every step.
We hope this article will allow you to make what you want before you have any performance issues caused by objects instantiations and GC work and let you implement your next great ideas.
Let us tell you more about our projects!
Сontact us:
hello@wave-access.com