Object pooling

Speaking of temporary work buffers, object pooling is an excellent way of both minimizing and establishing control over our memory usage by avoiding deallocation and reallocation. The idea is to formulate our own system for object creation, which hides away whether the object we're getting has been freshly allocated or has been recycled from an earlier allocation. The typical terms to describe this process are to spawn and despawn the object rather than creating and deleting them in memory. When an object is despawned, we're simply hiding it, making it lay dormant until we need it again, at which point it is respawned from one of the previously despawned objects and used in place of an object we might have otherwise newly allocated.

Let's cover a quick implementation of an object pooling system:

  1. First, we define a common interface for the object we want to use in the object pool. An important feature of this system is to allow the pooled object to decide how to recycle itself when the time comes. The following interface class called IPoolableObject will satisfy this requirement nicely:
public interface IPoolableObject{
void New();
void Respawn();
}

 

This interface class defines two methods: New() and Respawn(). These should be called when the object is first created and when it has been respawned, respectively.

  1. Now, we need to implement a class that manages the poolable objects. The following ObjectPool class definition is a fairly simple implementation of the object pooling concept:
using System.Collections.Generic;

public class ObjectPool<T> where T : IPoolableObject, new() {
private Stack<T> _pool;
private int _currentIndex = 0;

public ObjectPool(int initialCapacity) {
_pool = new Stack<T>(initialCapacity);
for(int i = 0; i < initialCapacity; ++i) {
Spawn (); // instantiate a pool of N objects
}
Reset ();
}

public int Count {
get { return _pool.Count; }
}

public void Reset() {
_currentIndex = 0;
}

public T Spawn() {
if (_currentIndex < Count) {
T obj = _pool.Peek ();
_currentIndex++;
IPoolableObject po = obj as IPoolableObject;
po.Respawn();
return obj;
} else {
T obj = new T();
_pool.Push(obj);
_currentIndex++;
IPoolableObject po = obj as IPoolableObject;
po.New();
return obj;
}
}
}

This class allows ObjectPool to be used with any object type so long as it fits the following two criteria: it must implement the IPoolableObject interface class, and the derived class must allow for a parameter-less constructor (specified by the new() keyword in the class declaration).

  1. Finally, we need to implement the IPoolableObject interface for any object we want to pool. An example poolable object would look like so: it must implement two public methods, New() and Respawn(), which are invoked by the ObjectPool class at the appropriate times:
public class EnemyObject : IPoolableObject {
public void New() {
// very first initialization here
}
public void Respawn() {
// reset data which allows the object to be recycled here
}
}

Now, just consider this usage example: we want to have a continuous wave of monsters. Obviously, we do not want to create new enemies continuously, instead, we want to recycle the enemies killed by the player. To do that, first we create a pool of 100 EnemyObject objects (we assume we never need to show more than 100 enemies on screen at the same time):

private ObjectPool<EnemyObject> _objectPool = new ObjectPool<EnemyObject>(100);

The first 100 calls to Spawn() on ObjectPool will cause the enemy to be respawned, providing the caller with a unique instance of the object each time. If there are no more enemies to provide (we have called Spawn() more than 100 times), then we will allocate a new EnemyObject instance and push it onto the stack. Finally, if Reset() is called on ObjectPool, it will begin again from the start, recycling enemies and providing them to the caller.

Note that we are using the Peek() method on the Stack object so that we don't remove the old instance from the stack. We want ObjectPool to maintain references to all of the enemies we create.

Also, note that this pooling solution will not work for classes we haven't defined and cannot derive from IPoolableObject, such as Vector3 and Quaternion. This is normally dictated by the sealed keyword in the class definition. In these cases, we would need to define a containing class:

public class PoolableVector3 : IPoolableObject {
public Vector3 vector = new Vector3();
public void New() {
Reset();
}
public void Respawn() {
Reset();
}
public void Reset() {
vector.x = vector.y = vector.z = 0f;
}
}

We could extend this system in a number of ways, such as defining a Despawn() method to handle destruction of the object, making use of the IDisposable interface class and using blocks when we wish to automatically spawn and despawn objects within a small scope, and/or allowing objects instantiated outside the pool to be added to it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.116.20