Hello, everyone!
I am going to explain the Object Pool I usually use. You can find the full version on my Framework Goat if you are interested.
What is an Object Pool
Object Pool is a programming pattern used to reduce performance issues related to object creation by expending extra ram by doing so.
The main concept or idea is that you don’t create new instances of object but instead recycle the ones you have after finish using them.
The full loop of an Object Pool would be something like this:
- You start with x amount of the object
- Each time you need an object, you ask for the Object Pool to give you one
- Once you finish using the object, instead of destroying it you give it back to the Object Pool
Of course there are some extra considerations to take care of like these ones:
- What happens if the Object Pool runs out of objects? It creates new ones? Returns null?
I personally recommend creating new ones. They will be added to the stock in case you reach a new amount and you will not need to check for null references. - With how many objects should I start?
It will depend on what the Object Pool pools. You can make tests to see the max amount and use that as base so you don’t have to create them on runtime.
Why use generics?
You can make an Object Pool without generics but using generics will give you a huge advantage. Basically, generics allows you to define placeholders for the types you are going to use in some parameters.
So, instead of doing something like:
public class ObjectPoolForBullets {
//Stuff
}
and
public class ObjectPoolForEnemies {
//Stuff
}
and…
public class ObjectPoolForEffects {
//Stuff
}
You can solve all of them by doing:
public class ObjectPool<T> {
//Stuff
}
And when you define the field type you will just need to say what type between the <> like this for example:
private ObjectPool<Bullet> _bulletPool = new ObjectPool<Bullet>(); //This will have parameters inside
Even more, you will be able to have this class presaved and use it in any game jam you want to participate!
Why use Factory Method?
When using generics, you won’t know what type of object you are going to use. Yet, you will need to know how to create it if you want to have more during runtime.
The goal of factory method is that you have a method you can call and it will return that object to you. You will only need to know the method instead of how to create it. So you can have something like this as parameter:
public ObjectPool(Func<T> factoryMethod...) //It will have more parameters
Notice how that T will be the same that the class defined. Basically, as it is a Func<T>, you will be able to call that method without parameters and it will return you a T created. So, thinking only in making this Object Pool, you have all what you need. You don’t have to worry anymore on how to create T, you just know what you need to call in order to create it and get it.
How to create it
I am going to show you step by step how to create this Object Pool. Notice you can make changes if you want or you have different requeriments.
So, first of all, as I showed before, we will need a Object Pool class with the generics <T>.
public class ObjectPool<T>
{
}
The constructor
From there, let’s create the constructor and see each value we will need:
private readonly List<T> _currentStock;
private readonly Func<T> _factoryMethod;
private readonly bool _isDynamic;
private readonly Action<T> _turnOnCallback;
private readonly Action<T> _turnOffCallback;
public ObjectPool(Func<T> factoryMethod, Action<T> turnOnCallback, Action<T> turnOffCallback, int initialStock = 0, bool isDynamic = true)
{
_factoryMethod = factoryMethod;
_isDynamic = isDynamic;
_turnOffCallback = turnOffCallback;
_turnOnCallback = turnOnCallback;
_currentStock = new List<T>();
for (var i = 0; i < initialStock; i++)
{
var o = _factoryMethod();
_turnOffCallback(o);
_currentStock.Add(o);
}
}
So lot of stuff going on here. First, lets check what every parameter will be used for:
- factoryMethod – As mentioned before, this one will be used in order to us be able to create new objects when needed without having to know how to create it
- turnOnCallback – We are going to use this method to activate our object. So, each time someone request an object to our Object Pool we will make sure it is ready to use and activated. We are using Action<T> because we don’t know how this object will be activated but we can send it by parameter.
- turnOffCallback – We are going to use this method to deactivate our object. So, each time someone returns an object to our Object Pool because they doesn’t need it anymore, we make sure it is deactivate. As in the last one, we will use the Action<T> for the same reasons.
- initialStock – This one is the initial stock we are going to use for the Object Pool. I had given it a 0 default value in case the user of the Object Pool doesn’t want to have an initial stock.
- isDynamic – Remember the question of what we should do if we run out of objects? Well, the Object Pool is called dynamic if it creates in runtime when running out of objects. If not, it will return the default value of the type.
Those are all the parameters our Object Pool will need to work. You can make it simpler than this but I prefer to have all this functionality in order to don’t have future issues like forgetting to activate or deactivate the objects.
Now let’s check the body of the constructor. The first lines are just saving those values and initializing the list of objects. Then, we have a for that creates each object, it deactivates it just in case and then adds it to the current stock of the Object Pool.
That is all for the constructor. We asked for everything we need, we saved all the stuff we are going to need later and we created the initial stock.
The Get Object method
We are going to need to extra methods now. The first one we are going to do is a method that lets me get an object from the Object Pool.
The method will be like this:
public T GetObject()
{
var result = default(T);
if (_currentStock.Count > 0)
{
result = _currentStock[0];
_currentStock.RemoveAt(0);
}
else if (_isDynamic)
result = _factoryMethod();
_turnOnCallback(result);
return result;
}
So, let’s see what we are doing here. First, let’s check the method firm. The Get Object will receive nothing, because we just want to get an object and it will return a T type that is the type of the object that saves the Object Pool.
Now we can check the body. First, we assign our result value as a default(T). Why default(T)? Because we don’t know if the object we are going to use has a null state.
Then, in the if we are going to check if we have stock. In case we do, we take the first one and remove it from our stock. If we don’t, we check if the pool is dynamic and we create a new one using the factory method.
For last, we now have the object we are going to use so we just need to turn it on using the turn on callback we got from the constructor and return it.
The Return Object method
Now we have the constructor to set all what we need and a method to get objects from the Object Pool. The last method we are going to need is one that lets us return the object to the Object Pool. For this, we are just going to need the following method:
public void ReturnObject(T o)
{
_turnOffCallback(o);
_currentStock.Add(o);
}
As you can see, it is a really simple method. We just need to pass it a T object and it will deactivate and add it to the stock. That’s all it needs to do.
Final result
The final result of what we did is the following:
public class ObjectPool<T>
{
private readonly List<T> _currentStock;
private readonly Func<T> _factoryMethod;
private readonly bool _isDynamic;
private readonly Action<T> _turnOnCallback;
private readonly Action<T> _turnOffCallback;
public ObjectPool(Func<T> factoryMethod, Action<T> turnOnCallback, Action<T> turnOffCallback, int initialStock = 0, bool isDynamic = true)
{
_factoryMethod = factoryMethod;
_isDynamic = isDynamic;
_turnOffCallback = turnOffCallback;
_turnOnCallback = turnOnCallback;
_currentStock = new List<T>();
for (var i = 0; i < initialStock; i++)
{
var o = _factoryMethod();
_turnOffCallback(o);
_currentStock.Add(o);
}
}
public ObjectPool(Func<T> factoryMethod, Action<T> turnOnCallback, Action<T> turnOffCallback, List<T> initialStock, bool isDynamic = true)
{
_factoryMethod = factoryMethod;
_isDynamic = isDynamic;
_turnOffCallback = turnOffCallback;
_turnOnCallback = turnOnCallback;
_currentStock = initialStock;
}
public T GetObject()
{
var result = default(T);
if (_currentStock.Count > 0)
{
result = _currentStock[0];
_currentStock.RemoveAt(0);
}
else if (_isDynamic)
result = _factoryMethod();
_turnOnCallback(result);
return result;
}
public void ReturnObject(T o)
{
_turnOffCallback(o);
_currentStock.Add(o);
}
}
You should be able to take this C# script and take it to any project. Even more, you can make this structure in other languages. I did something similar in AS3 for my Framework Mono.
Using it on Unity
Now let’s see an example of how can you use this script on Unity. Imagine you have a class Bullet : MonoBehaviour and you want to use it with the pool. First, we will make the three methods we will need to create the pool:
private Bullet BulletFactoryMethod()
{
return Instantiate(bulletPrefab);
}
With this one, you create a Bullet when you call it. Think that bulletPrefab is a reference to the prefab.
Now for the activate and deactivate we can use the followings:
private void TurnOnBullet(Bullet bullet)
{
bullet.gameObject.SetActive(true);
}
private void TurnOffBullet(Bullet bullet)
{
bullet.gameObject.SetActive(false);
}
Let’s imagine for now that we just need to activate and deactivate them. If you need extra stuff, you can modify these methods and add more.
For last, you can create the pool like this:
var objectPool = new ObjectPool<Bullet>(BulletFactoryMethod, TurnOnBullet, TurnOffBullet, 5, true);
So, like that you will have the Object Pool. You can save this variable somewhere and use it every time you need. This pool will use those methods to work and will start with 5 bullets turned off and will be dynamic.
What now?
You can just get that script and try it out! Also, I recommend you to make your own version or make changes to this one. Also, you can check out a more complex solution that has a manager at my Framework Goat.
Hope you find out this post useful! If you have any question or suggestion don’t hesitate in leaving a comment!
One Reply to “Object Pool using Generics and Factory Method with an Unity example”