Reducing The Platform API Surface
This week I started work on the Windows platform layer for Rival Fortress, and while I was at it, I also reduced the number of functions exposed by the API.
The platform layer
As is common in many modern games, Rival Fortress doesn’t interact directly with the operating system, but make calls through a platform API that is a thin abstraction over OS services.
For example, the platform API exposes an AllocateMemory
function that returns a chunk of memory, and behind the scenes uses a custom allocator for the OS based on VirtualAlloc
on Windows or mmap
on Linux/macOS.
The advantage of this approach is that it removes platform implementation details from game code and makes portability much easier: as long as the platform can expose all functions in the API, the game will run.
You may argue that the C/C++ standard library already provides this level of abstraction and portability, but having a tight API boundary gives you the flexibility of using whatever you want behind the scenes, like optimized system calls if standard library functions are not ideal.
Striving for a minimal API
Designing the The platform API is tricky. The number of functions exposed needs to strike a balance between just enough to get the job done efficiently and not too many as to negate the usefulness of the idea.
The platform API for Rival Fortress has gone through many iterations, and in its current state it looks like this:
PFN_
is the prefix I use for typedef
-ed function pointers.
In debug builds the game code is built as a DLL
, so platform API is passed as a struct
that is copied to a global object, so that the game code can be unloaded and reloaded as I explain in the Hot Swappable Game Modules post.
In release builds the game and platform are built as a single executable, so function pointers are not needed and the API functions are called directly.
In code, platform calls go through the MPE_PLATFORM
macro, that conditionally calls the function pointers or the functions directly like so: