Object Pooling

I began today with the intention of implementing the EnemyObject and getting that to work with in the GameScreen alongside the PlayerObject. However after a rather short time I decided that those two classes needed to interact so much that it was going to be a pain to make that work and then refactor once I’d moved all the Components into GameScreen when I was ready to implement object pooling.

Clearly that means I was ready for a bit of a refactor to make object pooling work – so off I went. Firstly I made arrays for each of my Component types in my GameScreen:

PhysicsComponent *m_pPhysicsComponents;
SpriteComponent *m_pSpriteComponents;
HealthComponent *m_pHealthComponents;

Then initialised these in the constructor. At this point I debated whether or not to set up each of the components with the correct data using their parametrised constructors (I actually had no idea that’s what non-default constructors were called) or if I should just use default constructors and then afterwards call some init function. I decided that having an init function meant that all the variables were getting set twice, so I decided to go for the parametrised constructor option. Mind you I’m now regretting that decision:

m_pPhysicsComponents = new PhysicsComponent[14]{ 
 { glm::vec3(400, 300, 0), 1.0f, 0.0f, 0.97f }, 
 {},
 {},
 {},
 //yeeeeep, this is shit
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false },
 { glm::vec3(), 1, 0.0f, 1, false }
 };
 m_pSpriteComponents = new SpriteComponent[14]{ 
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(51.0f, 86.0f), "ship.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(51.0f, 86.0f), "cylon.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(51.0f, 86.0f), "cylon.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(51.0f, 86.0f), "cylon.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false },
 { glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), glm::vec2(5.0f, 15.0f), "laser.png", m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, false }
 };
 m_pHealthComponents = new HealthComponent[4]{
 {100},
 {30},
 {30},
 {30}
 };

So now that the components are stored in GameScreen I need to pass pointers along to the GameObjects so they can still access the components they need. This was pretty easy for the players and enemies since I also had access to them in GameScreen:

m_player = new PlayerObject(m_pBulletManager, m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders);
m_player->AddComponent(m_pPhysicsComponents);
m_player->AddComponent(m_pSpriteComponents);
m_player->AddComponent(m_pHealthComponents);
m_gameObjects.push_back(m_player);

for (int i = 0; i < 3; i++)
{
 m_gameObjects.push_back(new EnemyObject(m_pBulletManager, m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders));
 m_gameObjects[i + 1]->AddComponent(m_pPhysicsComponents + i + 1);
 m_gameObjects[i + 1]->AddComponent(m_pSpriteComponents + i + 1);
 m_gameObjects[i + 1]->AddComponent(m_pHealthComponents + i + 1);
}

Getting the components into the BulletObjects was a bit more complex. I decided I would need to pass a pointer to the position of the first bullet related component in each component array to the BulletManager. Good idea, however I’d need to limit the number of BulletsObjects the BulletManager could create since I didn’t want to run out of Components. This meant a small refactor to pool the BulletObjects in the BulletManager too.

I also needed to change how I was shooting the bullets. Previously I’d been creating the bullet (and thus the components) in the Shoot function which meant I could just grab the position and rotation of the player and initialise the bullet with that info. However now I’d need to update the PhysicsComponent with this data *after* it had already been created. At this point I decided to make the position and rotation variables in PhysicsComponent public since I was clearly going to need to set them outside of the constructor. I added a shoot function to my bullet to accomplish all this:

void BulletObject::Shoot(int speed, float a_fRotationAngle, glm::vec3 a_startPos)
{
 if (m_physicsComponent != nullptr) {
  m_physicsComponent->m_fRotationAngle = a_fRotationAngle;
  m_physicsComponent->m_position = a_startPos;
  m_physicsComponent->AddForce(glm::vec2(speed, speed));
  GameObject::GetComponent(SPRITE)->m_bActive = true;
 }
}

As you can see in the last line – I’ve added a boolean to my Component class to determine if the component is currently active (since components hang around all the time now it makes sense to be able to turn them off when we don’t want them). So I start all the components in my bullets off as inactive – then turn them on when they are shot. There’s also a check in the BulletManager Update function to set them back to inactive when a bullet goes off screen.

I’ve implemented a rather basic system in BulletManager to handle looking for an inactive bullet that can be shot:

void BulletManager::Shoot(int speed, float a_fRotationAngle, glm::vec3 a_position) 
{
 //clearly need to make this 10 a const (or a variable if I pass it in, I guess)
 if (m_uiNumActiveBullets < 10) {
  while (true) { //is this bad? 
   //My teachers (and I) always taught it was bad coding practice
   // but otherwise I'm wasting a boolean and an extra check
 
   ++m_iLastAssignedBullet;
   if (m_iLastAssignedBullet >= 10) {
     m_iLastAssignedBullet = 0;
   }

   if (!m_bullets[m_iLastAssignedBullet].IsActive()) {
    m_bullets[m_iLastAssignedBullet].Shoot(speed, a_fRotationAngle, a_position);
    break;
   }
  }
 }
}

So basically I keep track of where I last assigned a bullet since it seems likely that the following position in the array will also be an inactive bullet. There’s probably a much better way of doing this – but for now it will do.

For completeness sake – the BulletManager constructor now looks like this:

m_bullets = new BulletObject[10]{
 //ok, this is shit - gotta find a better way of doing this.
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 { m_uiVBO, m_uiIBO, m_pShaderProgram },
 {m_uiVBO, m_uiIBO, m_pShaderProgram}
 };
 for (unsigned int i = 0; i < 10; ++i) {
 m_bullets[i].AddComponent(m_pPhysicsComponents + i);
 m_bullets[i].AddComponent(m_pSpriteComponents + i);
 }

Which is called like so, in the GameScreen class:

m_pBulletManager = new BulletManager(m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders, 
 m_pPhysicsComponents + 4, m_pSpriteComponents + 4);

This code clearly still needs some work (so many hard-coded values…) but it only took me about 2 hours to implement all this and get it up and running successfully. I’m pretty happy about this since I have often become lost and confused during refactors like this over the past year – getting tangled up half way through and needing a colleague to help me get untangled. I also seem to be getting faster since I know it’s taken me at least double the time to do something like this in the past.

If you have any comments on what I’ve done then please let me know. As always, my code is on github.

Advertisements

7 thoughts on “Object Pooling

  1. There is an advantage to having Init and constructors. My rule usually is constructors and destructors for allocating/dealocating stuff, and Init/Reset methods for setting up stuff.
    The advantage is that it’s easier to reset/reuse an object. Initializing values in the constructor means that you can’t really reuse the object or reset it’s state in an easy way, but If you have an Init method you just call that a second time, or make a Reset that calls it and does some other extra work.

    Liked by 1 person

  2. One way of tidying it up would be to not use raw arrays directly but have an object pool for each type you have. It’s possible to have a generalised object pool implementation, but even a non generic solution would make things easier to deal with.

    e.g.

    class PhysicsPool {
    PhysicsComponent* m_physicsComponents[10];
    bool m_active[10];
    public:
    PhysicsComponent* create(/*init parameters*/);
    void destroy(PhysicsComponent* ptr);
    void update(/* update parameters*/); // update all active components
    };

    This allows your code to look more like (if I understand it correcly):

    for (int i = 0; i create(m_pBulletManager, m_uiSpriteVBO, m_uiSpriteIBO, m_pShaders));
    enemy->AddComponent(m_pPhysicsPool.create());
    enemy->AddComponent(m_pSpritePool.create());
    enemy->AddComponent(m_pHealthPool.create());
    m_gameObjects.push_back(enemy);
    }

    The details of how memory is allocated and managed are internal details of the pool class. The m_active flag is a simple approach to this but it’s not the only approach. Keeping an active flag separate means when you are iterating the pool to update things you are not filling your CPU caches with inactive object data. On the down side, the create function will perform a linear search to try find an inactive component to use.

    This pattern can be generalised, this page talks a bit about a generalised pool http://gameprogrammingpatterns.com/object-pool.html. An example of a generic object pool I wrote which is a bit more general purpose but also more complex https://github.com/bitshifter/objectpool.

    Liked by 1 person

    1. Ah, ok, interesting about the separate active flag. I’m already doing a linear search for active bullets, so I might as well separate those so I don’t also lose cache space.
      I did reference the Game Programming Patterns chapter to create this – but I’ll have another read through to see if I can get my head around the generalisation. And thanks for the link to your code! After a very brief look (I’ll dive into it a bit more later) I can see this is accomplished with templates. I thought I had heard (perhaps from Mike Acton’s CppCon talk) that template use is to be avoided (or was he just talking about STL?)

      Like

      1. I can’t speak for Mike Acton, but I’ve certainly seen publicly released code from Insomniac Games that has used templates. Templates are useful but they can also be a rabbit hole in terms of increasing code complexity, compile times and generated code size. Of course, you can avoid using templates (much like the Game Programming Patterns pool example), it just means you’ll end up with a bit of code duplication.

        Liked by 1 person

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