Monday, February 15, 2016

Does Flyweigh pattern really help in memory management?

In a game architecture design, memory allocator is one of the core modules sit in the game system. I’m so wondering how could I implement this module and why do I need this? After reading many articles from the experts, understand that this is a crucial part of game performance when there are thousands of game objects being spawned at a time. Due to the slow performance of the new/delete operator, it is advisable to implement a very own or game specific memory allocator. But how could I do it?

Forget about those big games adopting very advance features of memory allocator. I should focus on my game since my game is categorized as a casual game. So the most basic need for me would be:
  1. Able to allocate from OS and release memory back to the OS.
  2. The memory pool should be expandable and return the memory back into the pool.
  3. The memory pool should not release the memory back to OS until the game exit.
For this purpose, I’m borrowing the idea of Flyweigh pattern, the idea of this pattern is to minimize the memory usage by sharing as much data as possible with other similar objects. First thing first, I define a default pool size of 10 whenever a new memory pool was created:
template <typename T>
class GameObjectPool
{
private:
 static const int POOL_SIZE = 10;

 T *freshPiece; 
}
Notice the use of template for this class, it is to allow the game to be able to allocate different kinds of game object (eg. particle, sprite) in the pool. freshPiece will be responsible for holding the available memory chunk for the game object. When the pool is first constructed, freshPiece was empty. There isn't any game object hold by freshPiece. Thus, the pool will first acquire some memory from the OS. This was done in the constructor:
template <typename T>
class GameObjectPool
{
public:
 GameObjectPool () {
  fillUpMemory();
 }
 ...
private:
 void fillUpMemory() {
  T *curObj = new T();
  freshPiece = curObj;

  for (int i = 0; i < POOL_SIZE - 1; i++) {
   curObj->setNext(new T());
   curObj = curObj->getNext();
  }

  curObj->setNext(nullptr);
 }

}
Once the game was finished, the memory is released back to the OS:
template <typename T>
class GameObjectPool
{
public:
 ~GameObjectPool() { 
  Particle *curObj = freshPiece;

  if (freshPiece != nullptr) {
   for (; curObj; curObj = freshPiece) {
    freshPiece = freshPiece->getNext();
    delete curObj;
   }
  }
 }
}
During the game runtime, the game object will acquire the memory from the pool instead of using the new keyword:
template <typename T>
class GameObjectPool
{
public:
 inline T *create() {
  if (freshPiece == nullptr)
   fillUpMemory();

  T *curObj = freshPiece;
  freshPiece = freshPiece->getNext();

  return curObj;
 }

}
Once the game object has done his job, the memory will release back to the pool. Same thing as acquiring the memory, no delete keyword is used:
template <typename T>
class GameObjectPool
{
public:
 inline void release(T* obj) {
  obj->setNext(freshPiece);
  freshPiece = obj;
 }
}
Notice the POOL_SIZE I pre-code it to 10, this is due to my laziness. I let the pool automatically allocate the predefined pool size if it exceeds the available pool size. May be in the future, I will need a more elegant way to adjust the pool size as shown below:
template <typename T>
class GameObjectPool
{
public:
GameObjectPool() {
  fillUpMemory();
 }

 GameObjectPool(int poolSize) : mPoolSize(poolSize) {
  fillUpMemory();
 }

...

private:
 void expandPoolSize() {
  T *curObj = new T();
  particleHead = curObj;

  for (int i = 0; i < mPoolSize - 1; i++) {
   curObj->setNext(new T());
   curObj = curObj->getNext();
  }

  curObj->setNext(nullptr);
 }

private:
 int mPoolSize = 10;

 T *freshPiece; 
}
Well, this is my very first version that meets the most basic fundamental of my game.

No comments: