Writing a custom game engine is a great way to have full control over whatever game you are making. However, one of the major drawbacks of using a custom engine is the potential drop in performance when compared to a robust commercial engine. Today I wanted to share with you my implementation of a custom memory manager that solved our FPS performance problems.
While working on Event Horizon, we noticed that our FPS would drop significantly when loading and unloading a level that contained a lot of background tiles. This is because we were relying on the operating system to allocate and deallocate memory for each game object. When transitioning between levels, each object had to be new’d and delete’d even though most of them were being reused. Because there was so much reuse going on, I decided that the best way to solve our problem was to use a custom memory manager rather than relying on the operating system. The actual implementation of a custom memory manager, especially for a smaller project, is rather simple. Essentially, the idea is that at load time, you allocate a large chunk of memory and then draw from it instead of calling new. In the case of Event Horizon, I allocated a different chunk for each type of thing I wanted to store (GameObjects, Textures, etc.). Since GameObjects are typically the most used object type in a game, I will be focusing on them.
Lists::Lists() { TextureList = new Texture*[MAX_TEXTURES]; ObjectList.reserve(MAX_GAME_OBJECTS); MeshList.reserve(MAX_MESH); //200 is an arbitrary number of background tiles for(int i = 0; i < 200; ++i) { BackGround[i] = new GameObject(); } }
I added to the GameObject class, a bool that I called mIsActive. When the GameObject was active, mIsActive is set to true, and false when it is not active. At load time, I allocated a very large std::vector of GameObjects, and set each GameObject’s mIsActive flag to false. Whenever I wanted to make a new GameObject, all I had to do was traverse the vector until I found a GameObject whose mIsActive flag was set to false.
GameObject* Level::FindEmptyObject() { for(int i = 0; i != gameLists->GetMaxGameObjects(); ++i) { if(gameLists->GetObjectList()[i]->ActiveFlag == 0) return gameLists->GetObjectList()[i]; } }
Then, I could take that GameObject, set its mIsActive flag to true, and update its other data as needed. Similarly, if I wanted to delete a GameObject, all I had to do was set it’s mIsActive flag to false since the engine would only draw GameObjects that were active. Clearing the lists at the end of each level was very simple as well. All I did was call the ClearObjects function which went through and set the mIsActive flag to false on each object.
void Lists::ClearObjects() { for(int i = 0; i < MAX_GAME_OBJECTS; ++i) { ObjectList[i]->Destroy(); } CollisionList.clear(); for(int i = 0; i < 200; ++i) { BackGround[i]->Destroy(); } }
By doing this, I was able to force the engine to only use the operating system to manage memory at the very beginning and the very end of the game, effectively increasing our FPS by 20 to 40 based on the number of objects in the level.
Overall, if you notice that your game’s frame rate is suffering, implementing a custom memory manager can be an easy way to fix the problem. See you next time!