The object pool pattern is a software creational design pattern that uses a set of initialized objects kept ready to use – an object pool – rather than allocating and destroying them on demand. A consumer of the pool will request an object from the pool and perform operations on the obtained object. When the consumer has finished its work, it returns the object back to the pool rather than destroying it manually or automatically.
Let’s define some common notions we will be using throughout the article. The object requesting instances from the object pool will be marked as a „consumer“. Every poolable object instance stored in object pool will be named as an „instance“. Object pool representing storage of instances will be denoted as a „pool“.
Pools are primarily used to improve performance. In some circumstances, pools significantly improve performance but on the other hand it complicates instance lifetime, as instances obtained from and returned back to the pool are not actually created or destroyed at this time, and thus pools as well as instances require care in implmentation.
We use object pool pattern mainly in situations when we need to (re)spawn a lot of objects of the same type on the scene at runtime:
various effects (particle effects, sounds, animated objects, bullets, etc.)
objects in the scene used by procedural generator (layout templates and their respective parts like walls, collectibles, enemies, obstacles, traps, etc.)
Be extremely cautios when you are operating with instances and always remember to reset the instance state to default when returning back to pool to avoid unexpected behaviour.
Simple Object Pool
Each pool is distinguishable by its unique name (string representation) and contains a reference to game object prefab used to instantiate pool’s instances. If you are not carefull enough during creation of new pools in the editor and you use the same pool name for more than one pool then you can not be sure that pooled instances in the scene will be of the expected type. Such cases can lead to unexpected runtime errors and can violate the stability of your game. Instead of using string representation of pool’s unique name you can also use variable InstanceID which is guaranteed to be unique for any game object’s component.
Our implementation allows us to set inital count of pool instances created during initialization as well as to decide whether or not to parent the instances under the pool game object in the Unity hierarchy.
There are few possibilities for Object Pool implementation of storing instances, for example you can use either Stack or Queue:
Stack is based on method Last-In-First-Out (LIFO) which means when consumer asks for instance the pool returns a reference of the first instance in the Stack and when the instance is returned back to pool it is automatically pushed back in the first position of the Stack
Queue is based on the First-In-First-Out (FIFO) method thus when consumer requests for instance the pool returns the reference of the first instance in the Queue and when the insatnce is returned back to the pool it is stored in the last field of the Queue
If you don’t need to perform significant time consuming clean up procedures of the instances after returning back to pool then usage of Stack is sufficient. Otherwise, implemetation of pool using the Queue is much more robust since instances have more time to perform their clean up actions.
We decided to formalize allowed operations with instances contained in various pools. Each instance in the pool has to implement IPoolableObject interface so it ensures the consumer that each pool’s instance will be properly initialized when they request the instance from pool and that any instance can have its own clean up procedure (if required).
Basic functionality of object pool design pattern can be summarized in following steps and it is described in Fig. 1:
1. pool creates and initializes all instances
2. consumer retrieves instances from pool as needed (if there isn’t any available instance pool creates a new one)
3. if instance isn’t needed anymore it is returned back to the pool (clean up procedure is performed if neccessary)
Pool instantiates predefined amount of instances and stores references to the instances in the Queue or Stack. Any particular pool can serve more than one consumer concurrently. After pool’s initialization, consumers are able to arbitrary request instances from pool in consideration of their needs. If pool recieves a request and runs out of instances it instantiates a new instance immediately and returns its reference to the consumer. As soon as the instance is not needed anywhere by the consumer it is returned back to the pool (clean up procedure is performed if required).
Object pool design pattern allows us to build and/or destroy environment at runtime in respect of our needs without any significant impact on performance. With such implementation we were able to design collections of object pools containing the instances of similiar object types that gave us more variety for random procedural generation of the world layout and usage of various effects at runtime. Collections of object pools are described in next article.