About Memory Management on my side project

Jan 11 2017

The hot reload functionality that I was so eagerly desiring to implement was a pain to get working using C++ classes, so I went full 1979 with it and split the logic from the data model, this provided me with some insight on how to manage the memory, and I’m trying out a memory management model that so far is looking nice.

First of all, as I discussed in my previous post I allocate chunks of memory that are passed to my start/update/render methods. I named these MemoryArea and look something like this:

struct MemoryArea
{
  void* start;
  void* current_position;
  Size size;
  Size used_memory = 0;
}

Besides this, the struct includes some methods to allocate aligned memory and do all the pointer math needed for that to work as expected and a constructor that actually allocates a given size in start.

As you may recall from the previous post, I wanted to have different chunks of memory for Permanent, Temporary, Editor and Debug memories. Each of these memories is allocated using a MemoryArea, and also have a pointer to it.

As an example let’s take a look at the current Editor memory.

namespace Editor
{
  struct Memory
  {
    MemoryArea* memory;
    Testing::Data* testing;
    Profiler::Data* profiler;
    QuickCommands::Data* quick_commands;
    Editor::Input::Data* input;
  };
}

When the game starts, it creates a memory area for the editor with say, 100mb. Inside that chunk of memory the first thing it does is creating an Editor::Memory instance, and makes its memory member point to the original memory area. May sound a little weird, but it makes sense. That memory area now holds the Editor::Memory struct, and will be used by all the code that access it. All the allocations done in Editor subsystems will use that memory area.

editor_memory = new(allocate(MT_EDITOR, sizeof(Editor::Memory), alignof(Editor::Memory))) Editor::Memory();
editor_memory->memory = &memory_areas[MT_EDITOR];

If you’re not familiar with the syntax new(memory) Class() it’s called placement new, and it’s used to construct an object in memory that has already been allocated. I use it all over the place to allocate my submodule’s memory.

Those members that you see below MemoryArea* memory are pointers to the data needed by each submodule. For example, Testing::Data* will be allocated on start by the Test submodule, and will hold all the automated tests, info regarding the testing editor window, and so on. Testing::Data* and Testing::start look something like this:

struct Data
{
  bool open = false;
  bool was_open = false;

  Test successful_tests[MAX_TEST_COUNT];
  int successful_test_count = 0;
  int selected_successful = 0;

  Test failed_tests[MAX_TEST_COUNT];
  int failed_test_count = 0;
  int selected_failed = 0;
};

void start()
{
  data = EDITOR_ALLOC(Testing::Data);
  EDITOR->testing = data;
}

EDITOR_ALLOC and EDITOR are just small macros to ease access to the allocator for the editor memory and editor memory.

#define EDITOR Editor::memory
#define EDITOR_ALLOC(type) new(Editor::memory->memory->allocate(sizeof(type), alignof(type))) type()

Nothing too fancy. Now that I see this, Editor::memory->memory reads ugly… I should rename that last memory to area. Whatever.

You may also notice that sucessful_tests and failed_tests are regular arrays instead of a dynamic one. One of the things that I wanted to do to keep things simple and performant is having as few dynamic arrays as I can, so if I mustn’t add to a dynamic collection in runtime, I will use MAX*COUNT, to provide a superior bound. I know this may waste memory, but I think it’s better to waste some memory than having indirections in the data structures, so I’m giving this a try. Also, I always have the ability to reduce the upper bound and reduce memory consumption.

Hopefully, all this stuff makes sense to you as it makes to me, I’d love to hear your thoughts on this memory model and maybe learn some lessons from your previous games.

Until the next post!

comments powered by Disqus