What is Pooling?
Pooling is a technique that allows the program to repurpose objects that are no longer being used in order to manage memory and decrease processing time. Imagine a box with 10 objects inside, this would be your pool and when you want an object you simply open the box and take out an object. You keep doing this until you have enough objects. Once you’re finished with the object you simply put it back in the pool so it can be taken out again when it’s needed instead of making a new object every time you need one. This is the pool decreasing processing time by having all the objects ready in a pool to use. In the case of memory management a pool creates a lot of objects at the start of the game and places them in memory so they are all grouped together in memory which means the space in memory will be less susceptible to memory fragmentation issues. This technique is commonly used for things like bullets and enemies in a game.
Why use pooling?
If you create a game where the main character can shoot a mini-gun and you have 60 bullets being shot per second then every frame you will be creating a new object which has a performance hit to your game and this performance hit will continue all through your game every time the main player fires his weapon. Now if you have a pool which creates 600 bullets and your player fires you will no longer creating objects every frame and instead be using one of the 600 bullets already created. Since you have 600 items in your pool you should have 10 seconds worth of objects already created and since bullets travel fast then they should have hit an objects or flew off screen within 10 seconds and those “dead” bullets can be put back in the pool ready to be used again. This eliminates the time needed to create the objects all through the game and instead creates the objects once at the start of the game.
What happens if I use all the objects in the pool?
If you happen to use all the objects in the pool then new ones will be created. The cost of creating these objects is still present however it will only happen once whereas non pooling methods would have these objects created every time.
Creating a pool
To create a pool for any object requires a couple of classes. One class which will be your poolable object(in our case a bullet) and second, a pool which will hold our objects. Our first step is to create a bullet class that implements the poolable interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Pool.Poolable; public class Bullet implements Poolable{ // fields for bullets position and direction public Vector2 position = new Vector2(0,0); public Vector2 direction= new Vector2(0,0); @Override public void reset() { //called when bullet is freed this.position.set(0,0); this.direction.set(0,0); System.out.println("Bullet is reset"); } // method for us to call to update our bullets logic public void update(){ position.add(direction); } // method for setting bullets position and direction (firing) public void fireBullet(int xpos, int ypos, int xvel, int yvel){ this.position.set(xpos,ypos); this.direction.set(xvel,yvel); } // same as above method with vectors public void fireBullet(Vector2 pos, Vector2 dir){ this.position = pos; this.direction = dir; } } |
In the bullet class we implement the poolable interface and override the reset method which will be called each time the bullet is freed and resets our bullet data. We also have an update method which we will use to update our bullets position and finally a couple of methods for setting the bullets initial position and direction.
Our second step is to create the pool which is in charge of creating new objects, freeing and resetting our objects once our program is finished with them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import com.badlogic.gdx.utils.Pool; public class BulletPool extends Pool<Bullet>{ // constructor with initial object count and max object count // max is the maximum of object held in the pool and not the // maximum amount of objects that can be created by the pool public BulletPool(int init, int max){ super(init,max); } // make pool with default 16 initial objects and no max public BulletPool(){ super(); } // method to create a single object @Override protected Bullet newObject() { System.out.println("Creating new bullet"); return new Bullet(); } } |
Here we extend the Pool and override the protected newObject method which should create and return a new Bullet. We also add a couple of constructors so we can create this pool with an initial amount other than the default of 16 and a max amount other than the default unlimited.
Using the pool
We have so far created both required classes for object pooling and now we only need to implement this into our application. To do that we first have to create the pool and an array to hold our live bullets.
1 2 |
private final Array<Bullet> activeBullets = new Array<Bullet>(); private final BulletPool bp = new BulletPool(); |
Next we need a method of adding new bullets. In this example we will use a mouse click to signal that a bullet should be added to the screen.
1 2 3 4 5 6 7 8 9 10 11 |
@Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { // get a bullet from our pool Bullet b = bp.obtain(); // fire the bullet from the place we click with a direction of straight up b.fireBullet(screenX,Gdx.graphics.getHeight() - screenY, 0,1); // add to our array of bullets so we can access them in our render method activeBullets.add(b); System.out.println(bp.getFree()); return false; } |
In our touchDown method we first use our bulletPool to get a new bullet from our pool with the obtain call. We then fire the bullet by telling the bullet its new position and direction. Next we add this bullet to our activeBullets array so we can access it later in our render method. Finally, a System.out call to output the amount of free objects left in the pool. This will output 0 for each bullet fired until the first bullet is replaced into the pool then this will report how many objects are in the pool ready to be used.
Our final step is to render the bullets and do their logic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@Override public void render () { //clear screen Gdx.gl.glClearColor(0f, 0f, 0f, 1f); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); // start batch sr.begin(); // set colour (yes, colour with a U. I'm British) sr.setColor(new Color(0.2f,0.2f,0.2f,1f)); //loop through all our active bullets for(Bullet bullet:activeBullets){ bullet.update(); // update bullet sr.circle(bullet.position.x, bullet.position.y, 2); // render bullet } sr.end(); // loop through bullets again for(Bullet bullet:activeBullets){ // check if bullet is off screen if(bullet.position.y > Gdx.graphics.getHeight()){ // bullet is off screen so free it and then remove it bp.free(bullet); // place back in pool activeBullets.removeValue(bullet, true); // remove bullet from our array so we don't render it anymore } } } |
In this example I have used the shapeRenderer to render our bullets but in a game you would probably use a spriteBatch with some textures. In our first loop we update and render the bullets then in our second loop we check if they have left the application window. If the bullet has left the window we use the bulletPool to free the bullet as it is no longer on screen and should be placed back in our pool for use again. We then remove it from our array of active bullets so it is no longer rendered. If the bullet is not removed from our active bullet array the bullet would be rendered at 0,0.
Pooling will increase the amount of memory and processing required when used to pool small amounts and should only be used when lots of object will be created. Another thing to note is that if an object from the pool is referenced to other objects it will still be referenced to those object even when the object is freed so you will need to unlink the object manually when it is freed.
The official LibGDX guide on pooling can be found here and the wikipedia entry for pooling can be found here.
thanks!
Thanks a lot!