2025-191

10:40:06, atom feed.

Since I last wrote, I extended the bump allocator from last time to also provide storage persistent across frames. This is necessary to contain at least two things: the general game state struct and tilemap indices. Both of these need to persist across frames. Previously, I was instantiating the game state on the platform side and passing a pointer to it into my game-update function. The platform is otherwise game-state-agnostic, though, so this didn't make much sense. For the tilemap indices, I was just malloc'ing space for them. My recent goal was to remove all instances of malloc from the game logic-side code.

Now, the allocator type definition looks like this:

struct Allocator {
  void* base_temp = nullptr;
  size_t size_temp = 0;
  size_t used_temp = 0;
  // Implementation detail: `base_persist` shall point to the beginning of the
  // allocated memory.
  void* base_persist = nullptr;
  size_t size_persist = 0;
  size_t used_persist = 0;
};

The implementation detail is because I allocate a single region of memory for both types of storage. The temporary storage starts size_persist bytes after base_persist. I also modified the API to separate allocations to temporary storage and persistent storage and to only provide the reset ability on temporary storage. Besides resetting, there is still no general ability to free allocated memory, meaning that allocations in persistent storage are expected to live for the lifetime of the program runtime.

The platform side sets up the allocator and its memory and passes it to my game update function. Perhaps a bit dirty, but although I am dealing with an allocator, it really is my game's memory pool with functions to manage the memory. Recall from the previous post that I do use the temporary storage a bit on the platform side to build a couple strings. This is no problem since no one should rely on exact positions of anything in temporary storage.

On the other hand, I require that the platform never allocate from persistent storage the game will use. This way, my game can always assume its game state object lives at the beginning of persistent storage. At the beginning of each pass through my game update function, I get at the game state via:

GameState* gs = (GameState*)allocator->base_persist;

My game state object has an initialized flag that tells me whether I need to actually set it up or not. Because I use mmap to allocate the backing storage, the storage is guaranteed to be zeroed after allocation. As a sanity check, I make sure that if the game state is unintialized that allocator->used_persist is also 0. I then stash pointers to this and that in persistent storage in my game state instance, and from there I can navigate the game memory across frames.

When initializing a tilemap, I previously dynamically allocated space for as many tilesets and layers as I needed based on what I discovered in my binary-encoded tilemap. Now, I set up storage for some fixed number of these things in persistent storage and point a tilemap instance at these persistent resources during tilemap initialization. This way I can avoid dynamic allocation entirely in the game logic, at the expense of allocating more memory up front than I may ultimately need. I can tune the allocations as the game matures.

Right now my game update function interface looks like this:

/**
 * Update the game state based on input.
 *
 * @param a The allocator containing the memory for the game. The game requires
 *          that the provider does not modify permanent storage in any way.
 * @param input The input to use to update the game state, assumed to be input
 *              toward the next frame to render.
 * @param renderer The rendering destination for all graphics updates.
 */
void game_update(Allocator* a, Buttons* input, SDL_Renderer* renderer);

At first I thought it would be nice to keep SDL things out of the game logic code, but SDL itself is supposed to be my operating system abstraction, so I'm fine bringing it into the game logic code. I do call SDL routines that (de)allocate, for example creating or destroying textures. To avoid this, I would need to build a graphics abstraction on the game logic side, probably via various memory buffers allocated on the platform side and passed to the game update function, have the game code write into those buffers, and then have the platform side move content from those buffers into SDL resources. But that is just abstraction on top of the thing I'm using for abstraction, which is not the way to go.