Object pooling

San_Alfonso2

One issue was brought to my attention in the comment by broxxar. As mentioned in this thread http://forum.unity3d.com/threads/unityengine-object-name-allocates-for-each-access.237380/ and as I saw it in the profiler, the call gameObject.name creates an allocation of 40B each call.I will later come up with a new version that avoid that problem.

Most games tend to make heavy use of short-lived objects. The first reflex of any programmer is to create a new object when needed and get rid of needed when done with it. Problem is that creating is slow and releasing may lead to a garbage collection which is also slow. One solution would be reuse identical items to avoid those two problems or at least to minimize them.

In this article we will keep it short and simple so you can start using it right on. Later on, we may add some more features.

What is a pool?

In our case a pool is just a like a bag in which we store items/objects. Before we start implementing, let’s take a minute to think how and why. Our pool should be a class most obviously, no wonder about that. this class should not be inherited to avoid any wrong doing with the existing code. Finally, there should be only one of it since we want to have all items in one bag and accessible from anywhere. This latest statement is personal and could be argued. One could say that one pool could be for a certain type of object and then other types should have their own pool. Up to you. We will make one to rule them all.

public sealed class ObjectPool 
{
    private static ObjectPool instance = null;
    public static ObjectPool Instance 
    {
        get
        {
            if (instance==null)
            {
                instance = new ObjectPool();
            }
            return instance;
        }
    }
    public void Reset() 
    {
         instance = null;
    }
    private ObjectPool() {}
}

The class is sealed to avoid any sub class (one could argue to make it static), it has a basic singleton pattern and an explicit private constructor as we do not want the user to call many constructor. The Reset method is just there to set the instance to null, the GC will do the rest.

The interface

Next, we should consider the interface of the class. In this case, we will not create an actual interface, we will simply make some methods public allowing the user to interact with the class. Just like an interface.

public bool AddToPool(GameObject prefab, int count, Transform parent = null) { }
public GameObject PopFromPool(string prefabName, bool forceInstantiate = false, 
    bool instantiateIfNone = false, Transform container = null) { return null;}
public GameObject PopFromPool(GameObject prefab, bool forceInstantiate = false, 
    bool instantiateIfNone = false, Transform container = null){ return null; }
public void PushToPool( ref GameObject obj, bool retainObject = true, 
    Transform parent = null) { }
public void ReleaseItems(GameObject prefab, bool destroyObject = false) { }
public void ReleasePool() { }

Each method represents a moment of the lifecycle of the object.

  1. Creation -> AddToPool
  2. Usage -> PopFromPool
  3. End of usage -> PushToPool
  4. Destruction -> ReleaseItems
  5. Get rid of the pool -> ReleasePool

The pool is generic, not that it uses generic code but that it returns the highest level of abstraction in Unity, a GameObject reference. The reason is that we use prefab to determine the items so it would make sense to fetch the matching item instead of looking for a type of script. It also makes sense that grabbing a specific game object indirectly means grabbing a set of scripts that it contains.

The concept

So the concept is simple, we want that providing a certain information, we are given a corresponding item. Somehow, we could say we give a key and we want a value. So the appropriate data collection to be used should be a dictionary since it is just what it does. Then via our interface, we add items to the dictionary, get items from it or reassigned items to it.

Our pool should not only be one key for one value so the best case is a dictionary with a prefab as key and a list of instantiated objects of that prefab as the value.

private Dictionary<string, Queue<GameObject>> container = new Dictionary<string, Queue<GameObject>>();
private Dictionary<string, GameObject> prefabContainer = new Dictionary<string, GameObject>();

Using a dictionary enables the fact we can extend freely the amount of key without constantly checking for a size.
Our dictionary will take a string as key and is linked to a Queue of GameObject instances.

The prefabContainer will store our prefab references so that we can add new items if needed later on in the project.

Adding items

public bool AddToPool(GameObject prefab, int count, Transform parent = null)
{
    if(prefab == null || count <= 0){ return false; }
    string name = prefab.name;
    if (this.prefabContainer.ContainsKey(name) == false) 
    {
        this.prefabContainer.Add(name, prefab);
    }
    if (this.prefabContainer[name] == null) 
    {
        this.prefabContainer[name] = prefab;
    }
    for (int i = 0; i < count ; i++)
    {
        GameObject obj = PopFromPool(name, true);
        PushToPool(ref obj, true, parent);
    }
    return true;
}

The method returns bool to make sure it all went fine. It takes three parameters, the first one is the prefab we want to instantiate, some bullets for instance. The second parameter indicates how many we wantto begin with (this can be changed later on). The last parameter is not compulsory and is set to null by default. It allows to keep a neat scene hierarchy by storing items under a specific game object. So you may add an empty game object to your scene and call it BulletContainer. Then pass the transform of that object to the method and all newly created bullet will go under it.

First, we do a couple of checks to make sure the call is not done wrong, we make sure the prefab is registered in the prefabContainer, then start a loop to create each object. So far, nothing special since the method we are using are not yet implemented. The method can be used in the Awake or Start of some manager script.

[SerializeField] private GameObject bulletPrefab = null;
[SerializeField] private Transform bulletContainer = null;
private void Awake()
{
    ObjectPool.Instance.AddToPool(this.bulletPrefab, 20, this.bulletContainer);
}

Getting to create

The first method we used in AddToPool may seem awkward since it tries to get from the pool while we are trying to add into the pool. Let’s look at the implementation.

public GameObject PopFromPool(GameObject prefab, bool forceInstantiate = false, 
    bool instantiateIfNone = false, Transform container = null)
{
    if(prefab == null) { return null; }
    return PopFromPool(prefab.name, forceInstantiate);
}
public GameObject PopFromPool(string prefabName, bool forceInstantiate = false, 
    bool instantiateIfNone = false, Transform container = null)
{
    if (prefabName == null|| this.prefabContainer.ContainsKey(prefabName) == false) 
    { 
        return null; 
    }
    if (forceInstantiate == true) { return CreateObject(prefabName, container); }
    GameObject obj = null;
    Queue<GameObject> queue = FindInContainer(prefabName);
    if (queue.Count > 0)
    {
        obj = queue.Dequeue();
        obj.transform.parent = container;
        obj.SetActive(true);
    }
    if (obj == null && instantiateIfNone == true)
    {
        return CreateObject(prefabName, container);
    }
    return obj;
}

The method returns a GameObject reference and takes the prefab as first parameter a boolean indicating whether we want to force the instantiation or not, then whether we want to create if we did not find any available item and finally a container transform. All those parameters are just there to be used in specific situation so they can be left as default. It is all up to the situation to define whether they are useful or not. You may consider the latest one for instance to be useless, it may be most of the time.

The first method is used when we have the reference to the prefab (probably in a manager), the second method will use the name of the prefab so a string will do, no need for a prefab reference.

Again, we first check if we got all we need. If we have forceInstantiate as true, it means we are simply creating a new object. Comes a call for a new method we will look at in a moment. This method returns a Queue<GameObject> reference. and if the collection is not empty, we dequeue an item out of it. In the case the queue was not empty but we did not get anything out (a reference may be null) or the queue was empty, it will get to the next stage where we check if the instantiateIfNone is set. Then we create a new instance.

You may wonder the difference between forceInstantiate and instantiateIfNone. The first is used when you want a new object whatever happens, at start or you are just adding new items to the level. Even though you have a full Queue, you would still get a new item. The second only creates if something went wrong or the Queue was empty. This comes for instance when you have been shooting all the bullets and you do not want to remain empty on a new shot of your player.

Next, let’s look at the FindInContainer method:

private Queue<GameObject> FindInContainer(string prefabName)
{
    if(this.container.ContainsKey(prefabName) == false)
    {
        this.container.Add(prefabName, new Queue<GameObject>());
    }
    return this.container[prefabName];
}

The FindInContainer is private since the user does not need it. Our container is the dictionary we already created. The purpose is just to get a reference to the Queue corresponding to the prefab (the prefab name in our case). Nothing too fancy here. Let’s move on.

And now the CreateObject method:

private GameObject CreateObject(string prefabName, Transform container)
{
    GameObject obj = (GameObject)Object.Instantiate(prefabContainer[prefabName]);
    obj.name = prefabName;
    obj.transform.parent = container;
    return obj;
}

This method does not do anything you should not understand by yourself.

Give back what you are given

 public void PushToPool( ref GameObject obj, bool retainObject = true, Transform parent = null)
{
    if(obj == null) { return; }
    if(retainObject == false)
    {
        Object.Destroy(obj);
        obj = null;
        return;
    }
    if(parent != null)
    {
        obj.transform.parent = parent;
    }
    Queue<GameObject> queue = FindInContainer(obj.name);
    queue.Enqueue(obj);
    obj.SetActive(false);
    obj = null;
}

The method returns void and takes three parameters. The first is a ref to a GameObject, second defines if we want to keep the object, the latest is the container to assign the object. This is quite simple to understand here, the only problem could come from the ref parameter. Why would we want that? Think about your own code in which you would use this one:

void MyMethod()
{
    // Some code and something happened with a bullet object
    GameObject obj = GetThatBulletReference();
    // Doing some stuff with obj reference
    if(condition == true){ // Any reason
    PushToPool(ref obj,false, containerTransform);
    else{ // other}
    // More code with obj => Wrong
}

In this code, based on a condition, the bullet is returned to the pool. But the code keeps on using the bullet later on regardless if it is still in use or not. Obviously, this code is wrong and may never happen but it is just for the sake of demonstration. Back to our code, it seems we still have a reference to our bullet object despite the fact it is back in the pool and deactivated. Using the ref keyword, it means we have access to the reference outside the method and we can set to null. As a result, we cannot modify the dead object since we have no link to it anymore.

In our method, the obj reference is at 0x0055 and contains the value 0x00AA where the bullet object is stored. When we call the PushToPool method, the parameter is given 0x0055 instead of 0x00AA, so we can control the obj reference directly. In the method it is set to null, so out of PushToPool, the obj reference inside MyMethod is null.

If you decide not to retain the item in the pool, then it gets destroyed. Now you may ask “What if we want to keep it without setting it in the pool?” well, do so. Just don’t call the method (I know some of you will think of that despite being obvious).

Clean up after yourself

public void ReleaseItems(GameObject prefab, bool destroyObject = false)
{
     if( prefab == null){ return; }
     Queue<GameObject> queue = FindInContainer(prefab.name);
     if (queue == null)  { return; }
     while (queue.Count > 0) 
     {
         GameObject obj = queue.Dequeue();
         if (destroyObject == true) 
         {
              Object.Destroy(obj);
         }
     }
}

public void ReleasePool()
{
    foreach (var kvp in this.container)
    {
        Queue<GameObject> queue = kvp.Value;
        while(queue.Count > 0)
        {
            GameObject obj = queue.Dequeue();
            Object.Destroy(obj);
        }
    }
    this.container = null;
    this.container = new Dictionary<string, Queue<GameObject>>();
    this.prefabContainer.Clear();
    this.prefabContainer = null;
    this.prefabContainer = new Dictionary<string,GameObject>();
}

We are finally getting to the end, so ReleaseItems is somehow similar to PushToPool with retain as false. The only major difference is the fact that all items of a kind will be destroyed witht that method. Considers you want to get rid of all kind of bullets, use this one. ReleasePool does what it says, it empties the pool and set it ready for a new round.

Practical case

To sum it up first let’s wrap the whole code and then use it quickly in an example. Please do keep in mind, there may have been some details you may find wrong, just let me know and I can explain my decision and maybe (I doubt it though) admit I could be wrong and improve the code.

Here it is:

github-mark@1200x630

And a simplified use case:

[SerializeField] private GameObject enemyPrefab = null;
[SerializeField] private GameObject bulletPrefab = null;
[SerializeField] private Transform enemyContainer = null;
[SerializeField] private Transform weaponContainer = null;
// This needs to match the prefab name 
// I just made it simple here 
public const string PrefabName = "bulletPrefab"; 
private void Start()
{
    ObjectPool.Instance.AddToPool(this.enemyPrefab, 10 , this.enemyContainer);
    ObjectPool.Instance.AddToPool(this.bulletPrefab, 100, this.weaponContainer);
}
 
private void Shoot()
{
     // Using the prefab name
     var obj = ObjectPool.Instance.PopFromPool(PrefabName, false, true, this.transform);
}
 
private void Hit(GameObject bullet)
{
    ObjectPool.Instance.PushToPool(ref bulletPrefab, ref bullet, bulletContainer);
}

12 Comments

  1. GameObject.name causes an allocation. It’s best to avoid it’s use if you can. A bullet hell using this pool would trigger the GC very often with all those strings.

    Like

  2. I could have missed something but not sure why a .name call would trigger an allocation since it is just a property. The string that is assigned to it requires an instantiation but If you refer to the line in CreateObject, this is only happening when creating the object and never again which is the purpose of the pool. Could you tell more about what you are thinking?

    Like

    1. Ok I see it now. Despite the fact that this should be consider micro-optimization, I will turn it into some Dictionary cache to avoid it.

      For info, the allocation is 40B per call.

      Thanks for that.

      EDIT: Thinking about it, it wouldn’t make anything better trying to fix that issue. The little allocation would require a lot of caching of all possible objects and a search for the matching one which would slow down the process.

      At the moment, it looks for the name of the object, making it match the prefab name (string comparison). If it would use the actual object reference stored in a collection to be compared, it would have to keep track of all of them since none would match a generic one (comparison is made on address or I would have to override this to compare object with prefab and best would be by name so back to square one).

      Other solution is to use the type of a component on the object, but then comes other issues (like two different prefabs with same component) and other slow downs (looking for a component on the object with GetComponent).

      So unless, someone comes with an easy ans quick solution, I don’t see any workaround for this (I don’t know it all as you can see).

      Again, this should be considered a minor issue since this is a little cost of 40B compared to the saving of a full object creation and GC.

      Like

  3. I’d argue it’s not a micro optimization at all. On mobile, you avoid GC like the plague. Ideally you want to NEVER trigger the GC, certainly not at regular intervals.

    In my current project, 8 entities shoot pooled bullets at 3 per second, each bullet explodes with a pooled explosion on impact. That ends up being ~2kb of allocation per second, from a pooling system that is supposed to be preventing any allocation at all! And that’s a relatively light use case.

    So I’m only commenting because I JUST ran into this problem this week, using strings exactly the same. I used two dictionaries too, just the same.

    To solve the issue, I now require that anything you want to pool has a PooledObject component on it. The only purpose of this object is to store the prefab the object was created from.

    The keys for your dictionary are now prefabs, with the values still being your queues/stacks whatever. And the other string-prefab dictionary? Dump it, you don’t need it any more because when you want to create something, you pop with a reference to the prefab. When it comes time to push it back, the Pooling manager assumes the thing you want to push has the PooledObject component and grabs the prefab from that, and pushes to the appropriate stack.

    Now my real dream come true, a system where popping an object returns it in the state it was as a fresh prefab even if it has been recycled! Man that’s the dream. I think it’s doable with attributes maybe, but anything pooled would ended up cluttered. Any thoughts on that would be great to hear!

    Like

    1. Yes, the addition of an extra component is something I thought of, and could be a solution. Now as always, this also brings up extra issue. The easy way would be to make the component a MonoBehaviour. So the 40B of extra allocation is replaced by a constant allocation for the MonoBehaviour plus the references (minor). I would think a MB object is way more than 40B. So the GC is avoided but more memory is used. This is a matter of choice.

      Second, it requires to get the component which will obviously slow down the process and a proper development would require a property to get the prefab reference so it would also slows down a bit (minor again).

      The point being to find a balance between it all and sometimes it may mean to give up on something.

      You chose the path of more constant memory usage vs GC.

      One other way would be to use the layer mask, but this requires adding all pool layers at first…quite annoying but at least it removes the allocation and fetching is fast. But, there is a limited amount of layer slot.

      Though, you have a clear legit point and I am no saying you are wrong. So I guess an extension could be developed including an extra component with explanation of pros and cons.

      As for the component resetting, this has to be done manually as Unity does not seem to have runtime resetting.

      Like

    1. The name is meant to be used to replace the item in the corresponding queue, using GetInstanceID you get a unique value for each item so you would need to perform some further check to define where the ID came from.

      Could you tell more about what you are thinking?

      Like

      1. Prefab instances that are passed through the inspector have the same InstanceId. For example we have prefab and pass it to the different monobehaviours the InstanceId of this prefab instances in these monobehaviours will be the same.
        Well, it was just an idea to replace name, it fails when it comes to the situation when our objects should delete themselves so that its parent don’t know about it, so we don’t have an id to find realse or deactivate our object.

        Like

  4. I downloaded the GIT file and am getting:
    Assets/Scripts/ObjectPoolString.cs(32,17): error CS1520: Class, struct, or interface method must have a return type

    Am I missing something to get this file to even compile?

    Like

    1. I think your error comes from the fact that you called the script ObjectPoolString while the class is actually ObjectPool. I had to give an extended name to differentiate it from the ObjectPool. So simply, create a new script called ObjectPool.cs and copy paste the content of the git link. Don’t forget to add the required namespaces as well.

      Like

Leave a comment