Object Pools Help You Reduce Lag in Resource-Intensive Games
Shooters and games that use particle systems have to create, manipulate, and then remove many objects at once - maybe even hundreds per frame. This can lead to the game lagging or even freezing. In this tutorial, we'll look at how object pools can help with this problem, by allowing us to re-use objects instead of recreating them from scratch.
Note: Although this tutorial is written using Java, you should be able to use the same techniques and concepts in almost any game development environment.
Introduction
A common occurrence in shooters and particle systems is to create and delete many objects in quick succession. From shooting guns to casting magical spells, creating and deleting the objects needed can be an expensive operation. When many of these operations are performed rapidly, it can cause the game to lag or freeze.
The reason is that a background program called the garbage collector requires system resources to clean up the used memory. It is this cleaning up that can cause the system to lag.
One way to prevent this is to use an object pool to reuse old objects instead of deleting them.
What Is Garbage Collection?
To understand why an object pool is needed, we must first understand how garbage collection works.
Garbage collection is the process of automatic resource management. It is responsible for freeing up memory space for reuse when it is no longer needed.
Take a look at the following example of a simple for
loop:
1 |
|
2 |
for (int i = 1; i < 10; i++) { |
3 |
System.out.println(i); |
4 |
}
|
When a program runs this loop, it will create a variable i
by assigning enough memory to hold the variable’s data (in this case, enough space to hold an integer). Once the loop has finished, the variable i
is no longer needed; the program will eventually detect this, and can then free up the memory for other uses.
For the most part, garbage collection happens automatically and without notice. Sometimes, however, if a lot of memory needs to be freed up all at once, garbage collection can force the program to use valuable system resources to free up the needed memory. This can cause the program to lag or freeze temporarily, as this takes time.
The system can also lag when a lot of memory is needed all at once to create new objects. This is because assigning memory can be just as resource-intensive as freeing it up.
Because of this, it becomes important to manage garbage collection so that it doesn’t interfere with your program.
What Is an Object Pool?
An object pool is a data structure which reuses old objects so as not to continually create or delete new ones. Instead of assigning new memory for an object and then freeing it once we’re done with it, we just keep reusing the object over and over again by altering its values. This way, the memory doesn’t have to be freed up, thus avoiding garbage collection.
Pooling of resources can offer a significant performance boost in situations where the cost of initializing a class instance is high, the rate of instantiation of a class is high, and the number of instances in use at any one time is low.
Think of a it like a deck of cards where the deck represents memory. Every time you need a new card (i.e. you need a new object), you draw one from the deck and use it; once you are finished with the card, you throw it into a small garbage can.
Eventually, you would run out of cards and need a new deck, or the garbage can would become full and need to be taken out (i.e. garbage collection). In either case, you must stop what you are currently doing to either get a new deck or take out the trash.
In an object pool, each card in the deck is blank. When you need a card, you write the basic information on it that you need and use it. Once you are done with the card, you erase the information and put it back in the deck. This way, no new cards are needed and you never have to throw a card in the garbage can. It’s the programmer's way of recycling!
Implementing an Object Pool
Implementing an object pool isn’t too difficult, but since it requires an object to act on, I’ll also be showing you how to implement the object the object pool will hold. Since an object pool works best on objects needing to be created and deleted quickly, a particle system seems like the most ideal choice. It’s a two for one special!
We’ll first start out by implementing the Particle
class. The following code is written in Java, but the same techniques can be used for most other programming languages. I’ll comment after each code snippet.
1 |
|
2 |
public class Particle { |
3 |
|
4 |
private int framesLeft; |
5 |
private int posX; |
6 |
private int posY; |
7 |
private int xVelocity; |
8 |
private int yVelocity; |
9 |
|
10 |
/**
|
11 |
* Constructor
|
12 |
*/
|
13 |
public Particle() { |
14 |
framesLeft = 0; |
15 |
posX = 0; |
16 |
posY = 0; |
17 |
xVelocity = 0; |
18 |
yVelocity = 0; |
19 |
}
|
20 |
|
21 |
/**
|
22 |
* Initialize all variables before use
|
23 |
*/
|
24 |
public void init(int pFramesLeft, int pPosX, int pPosY, int pXVelocity, int pYVelocity) { |
25 |
framesLeft = pFramesLeft; |
26 |
posX = pPosX; |
27 |
posY = pPosY; |
28 |
xVelocity = pXVelocity; |
29 |
yVelocity = pYVelocity; |
30 |
}
|
31 |
|
32 |
/**
|
33 |
* Animate the particle
|
34 |
*/
|
35 |
public boolean animate() { |
36 |
if (isAlive()) { |
37 |
posX += xVelocity; |
38 |
posY += yVelocity; |
39 |
framesLeft--; |
40 |
|
41 |
// Draw the object to the screen
|
42 |
|
43 |
return false; |
44 |
|
45 |
}
|
46 |
return true; |
47 |
}
|
48 |
|
49 |
/**
|
50 |
* Determine if a particle is is alive (or in use)
|
51 |
*/
|
52 |
public boolean isAlive() { |
53 |
return framesLeft > 0; |
54 |
}
|
55 |
}
|
As you can see, a particle is a very simple object. It holds a few variables to keep track of where it is on the screen (posX
and posY
), how fast it is going (xVelocity
and yVelocity
) and how many frames it should be drawn (framesLeft
). A particle is “alive” if it still has frames left to be drawn, and “dead” otherwise.
At every frame, the animate
function is called to update the particle’s position and draw it to the screen. It returns false
if the particle is still alive, otherwise it returns true signifying the particle died
.
Note: The code to draw the particle is beyond the scope of this tutorial.
Next, we’ll implement the ObjectPool
class:
1 |
|
2 |
public class ObjectPool { |
3 |
|
4 |
private int size; |
5 |
private List particles; |
6 |
|
7 |
/**
|
8 |
* Constructor
|
9 |
*/
|
10 |
public ObjectPool (int pSize) { |
11 |
size = pSize; |
12 |
particles = new ArrayList(); |
13 |
|
14 |
// Initialize the array with particles
|
15 |
for (int i = 0; i < size; i++) { |
16 |
particles.add(new Particle()); |
17 |
}
|
18 |
}
|
19 |
|
20 |
/**
|
21 |
* Get the first available particle and assign it the new values
|
22 |
*/
|
23 |
public void get(int pFramesLeft, int pPosX, int pPosY, int pXVelocity, int pYVelocity) { |
24 |
if (!particles.get(size-1).isAlive()) { |
25 |
particles.get(size-1).init(pFramesLeft, pPosX, pPosY, pXVelocity, pYVelocity); |
26 |
particles.add(0, particles.remove(size-1)); |
27 |
}
|
28 |
}
|
29 |
/**
|
30 |
* Animate the object pool. Any dead particles will be placed at the front of the list to be reused
|
31 |
*/
|
32 |
public void animate() { |
33 |
for (int i = 0; i < size; i++) { |
34 |
if (particles.get(i).animate()) { |
35 |
particles.add(size-1, particles.remove(i)); |
36 |
}
|
37 |
}
|
38 |
}
|
39 |
}
|
The ObjectPool
class is also a relatively simple object. It just holds a list of particles and the size of the list. The power of the object pool comes in its two methods, get
and animate
.
When the object pool needs to get a new object for use, it looks at the last item in the list and checks whether it's currently alive or dead. If it is alive, the pool is full, and so a new object must be created; if it is dead, the pool initializes the last item in the list, pops it from the end, and pushes it back on to the front of the list. This way, the pool always has available objects at the back and used objects in the front.
In the animate
method, if the particle's animate function returns true
, the object is ready to be reused. The pool removes the item from the list and pushes it to the back. Manipulating the list in this fashion makes creating and destroying objects in the pool constant and very efficient.
In this example, the objects the object pool will hold are Particles, but for your own object pool it can be whatever you want. As long as same functions exist in the object you will use as in the Particle class, it will work the same.
Putting It All Together
Equipped with an object pool, it's time to create a particle system to make a sparkler effect.
We start out by creating the object pool to hold all the particles on the screen.
1 |
|
2 |
ObjectPool pool = new ObjectPool(100); |
At every frame, we'll generate a new particle at the center of the screen and assign it a random velocity. Lastly, we'll animate the object pool.
1 |
|
2 |
Random randomGenerator = new Random(); |
3 |
int velX = randomGenerator.nextInt(5); |
4 |
int velY = randomGenerator.nextInt(5); |
5 |
pool.get(30, 200, 200, velX, velY); |
6 |
pool.animate(); |
Conclusion
Quickly creating and deleting objects can potentially cause a game to lag or freeze. By using an object pool, you can save both system resources and user frustration.