00:00
00:00
MonoFlauta
Indie Game Developer + Senior Software Engineer @ Etermax

Facundo Balboa @MonoFlauta

Age 31

Indie Game Developer

Escuela Da Vinci

Buenos Aires, Argentina

Joined on 7/30/08

Level:
27
Exp Points:
7,866 / 8,090
Exp Rank:
5,253
Vote Power:
6.87 votes
Rank:
Police Sergeant
Global Rank:
9,164
Blams:
214
Saves:
823
B/P Bonus:
12%
Whistle:
Bronze
Trophies:
15
Medals:
773
Supporter:
8y 3d

Object Pool using Generics and Factory Method with an Unity example

Posted by MonoFlauta - July 4th, 2021


Full Post - Twitter Account - DevBlog


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.

iu_348517_2529638.webp

Witchcraft used Object Pool for the spells


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:

  1. You start with x amount of the object
  2. Each time you need an object, you ask for the Object Pool to give you one
  3. 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 pre-saved 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.

iu_348516_2529638.webp

We used the exact Object Pool you can see in Framework Goat for Blight (a game jam game) for all the bullets and enemies



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>
{
}

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.

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.

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.

iu_348515_2529638.webp

The spheres in Skeleball also used this Object Pool



You can finish reading this post at my DevBlog (for free of course!). Though with all this content you can already create it and use it :)


Thank you for reading and feel free to ask me if you have any question!


Tags:

Comments

Comments ain't a thing here.