(Better) Object Pooling

Why Reinvent?

Object Pooling 1.0

Yes, I’ve made videos on Object Pooling already. Yes, there a written post on it too. Does the internet really need another Object Pooling post. No, not really. But I wanted something a bit slicker and easier to use. I wanted a system with:

  1. No Object Pool manager.

  2. Objects can return themselves without needing to “find” the Object Pool.

  3. Can store a reference to a component, not just the gameObject.

  4. An interface to make the initialization of objects easy and consistent.

Easily the best video on generics I’ve ever made.

So after a lot of staring at my computer and plenty of false starts in the wrong direction, I came up with what I’m calling Object Pool 2.0. It makes use of generics, interfaces, and actions. So it’s not the easiest to understand object pooling solution but it works. It’s clean and easier to use than my past solutions. I like it.

If you’re just here for the code. You can get it on github. But you should definitely skim a bit further to see how it’s implemented.

If you’re asking why not just use the Unity 2021 object pool solution. Well, I’m scared of Unity 2021 (at this point in time) AND I’ve seen some suggestions that it’s not quite ready for prime time. Plus my solution has some features that Unity’s doesn’t and creating an object pooling solution isn’t hard.

Implementation

Maybe this seems backward. Maybe it is? But I think in the case of this object pool solution, it makes sense to show how it’s implemented before going into the gory details. It’s a reasonably abstract solution and that can make it difficult to wrap your head around. So let’s start with how to use it. Then we’ll get to how it works.

First, there needs to be an object that is responsible for spawning the objects. This object owns the pool and needs a reference to the object being pooled - in the case shown I’m using a gameObject prefab. The spawner object then creates an instance of the object pool and sends a reference to the prefab to the pool so it knows what object it is storing and what to create if it runs out and more are being asked for. To get an object from the pool, we simply need to call Pull.

Spawning object with the Object Pool

Just Slap on the Pool Object component and you’re good to go.

The objects being stored also need some logic to work with the pool. The easiest way to attach that logic is to slap on the Pool Object component to the object prefab. This default component will return the object to the object pool when the object is disabled.

Do you see why I like this solution? Now on to the harder part. Maybe even the fun part. Let’s look at how it works.

A Couple Interfaces

To get things started, I created two interfaces. The first one could be useful if I ever have a need to pool non-monobehaviour objects - but is admittedly not 100% necessary at the moment.The second interface, however, is definitely useful and is a big part of this system working smoothly.

But let’s start with the first interface which helps define the object pool. It has just two functions, a push and a pull. It is a generic interface, where the generic parameter is the type of object that will be stored. This works nicely as then our push and pull functions know what types they will be handling

Is this strictly necessary? Probably not.

The second interface is used to define objects that can be in the object pool. When used as intended the object pool can only contain types that implement the IPoolable interface.

This interface has an initialize function that takes in an Action. This action will get set in the object pool and is intended to be the function that returns the object to the pool. This action is then invoked inside of the ReturnToPool function.

If that doesn’t all make sense. Well, that’s reasonable. It can feel a bit circular. Let’s hope that’s not still the case by the time we get finished.

Creating the Pool

Let’s next take a look at the Object Pool definition - or at least the definition I’m using for monobehaviours. The Object pool itself has a generic parameter T and implements the IPool interface. T is then constrained to be a monobehaviour that must also implement the IPoolable interface.

Next, come the variables and properties for the object pool.

First up are two optional actions. These actions can be assigned in a constructor. This allows you to call a function (or multiple functions) EVERY time an object is pulled out of the pool or pushed back to the pool. This could be used to play SFX, increment a score counter or just about anything. It seemed useful so I stuck it in there.

Next is the stack (first in first out collection) that holds all the pooled objects.

Since we know the object being stored is a component we also know it’s attached to a gameObject. It’s this gameObject that will be instantiated if and when the pool runs out of objects in the stack.

Lastly, I added a property to count the number of objects in the pool. I stole this directly from the Unity object pool solution. I haven’t found a use for it yet, but maybe at some point.

Constructors

When we create a pool we need to tell it what object it will store and I think the easiest and best way to do that is to inject the object (prefab) using a constructor. In some cases, it’s also nice to pre-fill the pool. So the first constructor (and easily added to the second) takes in a number of objects to pre-spawn using the Spawn function.

The second constructor takes in the prefab as well as references for the pullObject and pushObject actions.

Push and Pull

The pull function is called whenever an object from the pool is needed.

First, we check if there are objects in the pool, if there are we pop one out. The gameObject is then set to active and the initialize function on the IPoolable object is called. Notice here that we are providing a reference to the Push function. This is the secret sauce.

This push function is the function used to return an object to the pool. This means the spawned object has a reference to this function and can return itself to the pool. We’ll take a closer look at how this happens later.

We then check if the pullObject action was assigned and if it was we invoke it and pass in the object being spawned.

Finally! We return the object so that whatever object asked for it, can have a reference to it.

The push function is pretty simple. It takes in the object and pushes it onto the stack. It then checks if the pushObject action was assigned and invokes it if it was. Lastly, the gameObject is turned off.

As a side note. the turning on and off of the object in the pull and push functions is not 100% needed, but is there to ensure the object is toggled on and off correctly and to help keep the initialize functions clean.

Poolable Objects

Every object that can go into this pool needs to implement the IPoolable interface. Now in some cases, you might want to implement the interface specifically for a given class.

Both as an example of how to implement the interface and also to provide an easy to use and reusable solution I created the PoolObject class. This component can simply be added to any prefab to allow that prefab to work with the object pool.

When implementing the IPoolable interface we should set the generic parameter to the class that is implementing the interface - PoolObject in the example.

The class will also need an action to store a reference to the push function. The value of this action is set in the initialize function - which was called in the Pull function of the object pool.

The ReturnToPool function, in this example, is called in the OnDisable function. This means all we need to do to return the object to the pool is turn the object off! Inside the function, we check if the returnToPool action has a value and if so, we invoke the action and pass in a reference to the object being sent to the object pool.

Overloads

To make the object pool a bit more useful and user friendly I also added several overloads for the Pull function. These allow the position and rotation of the object to be set when pulling it.

I also created functions that return a gameObject, as in some cases this is what is really needed and not the poolable object component.

One More Example

Since actions (and delegates) can be confusing I thought I’d toss in one more example - that of using the second constructor and assigning functions that will be called EVERY time an object is pushed or pulled from the instance of the object pool.

In this example, I’ve added the CallOnPull and CallOnPush functions. Notice that they must have the input type that is being stored in the object pool. Again the idea here is that these functions could trigger an animation, SFX, a UI counter, just about anything.

And that’s it. It’s an abstract solution but actually pretty simple (note that simple is not the same as easy). That’s both why it took a while to create and why I like it.