Object Pooling v2

Object pooling v2

Sac_de_billes

Following the previous article on object pool, some legit comments came up concerning memory allocation.

The issue

When coding, it is required to find the most efficient code, that is the fastest way to solve a problem (fastest is not necessarily shortest). But sometimes in order to get fast, we hit some other trouble. In the previous article, the code could be considered fast (to some extents), but one line would be problematic. It happens that Unity runs some internal code when using

gameObject.name;

Despite the fact that name is just a property, Unity runs some deeper code that generates some memory allocation. Exactly, it seems to allocate 40B for each call.

The call happening during the creation of the object is no issue since it happens only once in the object lifetime, the problematic was during the pushing back in the pool:

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 FindInContainer method uses the name of the prefab as parameter and that is where it goes wrong.

So we need to figure out a way to get the prefab without using the name.

Getting a solution

The solution is simple, instead of storing the prefab in a dictionary (string,prefab), we will add a component to the pooled object. That component will contain only one item, a reference to the prefab.

This way, we save the allocation generated by the call to .name. On the other hand, we have a bigger constant memory usage since the component will be stored in memory for the whole object lifetime. Also, we lose that tiny efficiency as well on the call to the component to get the prefab.

As you can see, one problem could not entirely be solved without adding something else. This is one of the main programming paradigm, finding the balance between efficiency, memory allocation and memory usage.

The new interface

If you read the other object pooling article, you will see that this one is fairly similar but shorter. I would recommend to start with it first and back to this one.

The new interface is nothing more than a storage for a prefab reference and an init method:

public class IPoolObject : MonoBehaviour
{
   GameObject Prefab{ get; set; }
   void Init();
}

This is added to any component on an object that should be pooled. The compiler will then asked to implement the method and property. The Init method is there to reset the values on a pooled  component. If nothing should be done, just leave the method implementation empty. You can use to reset health, ammo or else.

The pool

The class containing the pool is about to start just like the other article, so I wont repeat myself and just paste the code:

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

Here goes the interface of our pool (interface as in public interface not actual interface, well,…):

public bool AddToPool(GameObject prefab, int count, Transform parent = null) { return false; }
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() { }
}

There is no more overload of the AddToPool method and we got rid of all string iterations.

Creating items

The first method we want to call is the AddToPool method which will create our items.

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

The method just runs a loop to create all needed items. As soon as they get created, they also get pushed back to the pool. The return value is only there to inform it all went fine.
There is not much fancy happening so let’s move on.

public GameObject PopFromPool(GameObject prefab, bool forceInstantiate = false,
    bool instantiateIfNone = false, Transform container = null)
{
    if (forceInstantiate == true) { return CreateObject(prefab, container); }
    GameObject obj = null;
    Queue<GameObject> queue = FindInContainer(prefab);
    if (queue.Count > 0)
    {
        obj = queue.Dequeue();
        obj.transform.parent = container;
        obj.SetActive(true);
        IPoolObject poolObject =  (IPoolObject)obj.GetComponent(typeof(IPoolObject));
        poolObject.Init();
    }
    if (obj == null && instantiateIfNone == true)
    {
        return CreateObject(prefab, container);
    }
    return obj;
}

So if you read the previous article, you should notice some changes here. There is no usage of any string name and all happens with prefab reference. Mostly, we create the object and return it if it is forced. If we forced the creation, we mean to use it right away so there is no need to push it to the pool. In our initial run, we have to push it back manually. The FindInContainer receives a prefab reference and returns a queue of GameObject (if found). Then we dequeue from the queue, set as active, set the parent and return. If something went wrong with the queue, we try a new creation and return it. Notice how we get the interface and call the method. First off, we know that the object contains the interface so there is no need to check, then we call the Init method regardless of the object we are dealing with. The bonus of interfaces is that you don’t need to know what you are dealing with as long as it is a IPoolObject.

NOTE: I use the non-generic version of GetComponent with interfaces as it used to be that Unity would not support it. Unity5 seems to have fixed that issue and you can freely use your interface in generic methods. For those of you still using Unity4.x, I can’t guarantee that generic will work so use the shown version.

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

The FindInContainer method adds the prefab to the dictionary with a new queue of GameObject and returns a reference to that queue.

Creating the object

private GameObject CreateObject(GameObject prefab, Transform container)
{
    IPoolObject poolObjectPrefab = (IPoolObject)prefab.GetComponent(typeof(IPoolObject));
    if(poolObjectPrefab== null){Debug.Log ("Wrong type of object"); return null;}
    
    GameObject obj = (GameObject)Object.Instantiate(prefab);
    IPoolObject poolObject = (IPoolObject)obj.GetComponent(typeof(IPoolObject));
    obj.name = prefab.name;
    poolObject.Prefab = prefab;
    obj.transform.parent = container; 
    return obj   
}

The first part is to make sure the prefab that was passed contains a script that implemented the interface. Then it is just instantiating the object and setting some info. Mainly, we are also passing the current prefab to the prefab reference in the IPoolObject interface.

Returning the item to the pool

Consider a bullet you have been taking out of the pool, it just hit an enemy player. Thanks to the collision callback (or any other means you may be using), you have a reference to the bullet:

void OnCollisionEnter(Collision col){
    if(col.collider.tag == "Bullet"){
        GameObject bulletRef = col.collider.gameObject;
        PushToPool(ref bulletRef, true, someContainer.transform);
        // bulletRef is nullified in the method, you cannot use it anymore!!
    }
}

Using that reference, we can push the item back in pool:

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;
    }
    IPoolObject poolObject = (IPoolObject)obj.GetComponent(typeof(IPoolObject));
    GameObject prefab = null;
    if(poolObject == null){ return;}
    prefab = poolObject.Prefab; 
    Queue<GameObject> queue = FindInContainer(prefab);
    queue.Enqueue(obj);
    obj.SetActive(false);
    obj = null;
}

We are passing the reference to the GameObject as ref, which means we get a reference to the reference and not to the object directly. This means we can modify the passed reference from within the method. This way we affect bulletRef in OnCollisionEnter. Without ref, the compiler would be copying the value stored in bulletRef and we would be working with that local copy, but we would not have any access to bulletRef itself. We want to make sure that bulletRef becomes unusable after the item has been pooled back. This prevents the consumer of the ObjectPool to push back and then continue using the bullet reference.

Back to the method implementation, if we set to not retain the item, it gets destroyed.

We parent to the container that has been defined and then we fetch the component containing the reference to the prefab. We make sure the object contains the component. If it fails, the method simply returns, it could (or should) inform further with returning false/null. Then we get the queue corresponding and enqueue the item. Finally we set the reference to null so that the item is no more usable outside.

Cleaning

The cleaning of the pool is similar to the ones in the other article, except for the fact that only one container is remaining.

public void ReleaseItems(GameObject prefab, bool destroyObject = false)
{
    if (prefab == null) { return; }
    Queue<GameObject> queue = FindInContainer(prefab);
    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 container)
    {
        Queue<GameObject> queue = kvp.Value;
        while (queue.Count > 0)
        {
            GameObject obj = queue.Dequeue();
            Object.Destroy(obj);
        }
    }
    container = null;
    container = new Dictionary<GameObject, Queue<GameObject>>();
}

The first ReleaseItems is used to get rid of a type of items, while ReleasePool is clearing the whole pool.

Conclusion

This is it for the second version. We got rid of the memory allocation (at least I could not see any in the profiler). Obviously, there is a little more done under the hood but that should not affect the runtime. Also, the Init method got added which enables a nice reset feature.

Any comments, go ahead (only positif ones will make it through moderation).

Find the script on the link below.

github-mark@1200x630

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s