Metric Panda Games

One pixel at a time.

Generating Game Configuration

Rival Fortress Update #31

This week I extended Rival Fortress’s Meta reflection system to generate ‘rich’ overridable #define preprocessor directives as well as INI configuration parsing/writing.

As I explained when I first introduced the Meta reflection system, being able to generate automatically repetitive code is a huge time saver and cuts down on copy-paste type bugs.

Replacing #defines

I tend to use a lot of #define preprocessor directive, especially for default values and engine settings. Another usage pattern that I often use is to define minimum and maximum values along with defaults, like so:

#define MPE_WINDOW_WIDTH 1600
#define MPE_WINDOW_WIDTH_MIN 640
#define MPE_WINDOW_HEIGHT 900
#define MPE_WINDOW_HEIGHT_MIN 480

No suffix is the default, _MIN and _MAX suffixes are for minimum and maximum.

In order to clean up and make defines overridable, I replaced #defines with the following:

MDEFINE(MPE_WINDOW_WIDTH, 1600, Min: 640);
MDEFINE(MPE_WINDOW_HEIGHT, 900, Min: 480);
MDEFINE(MPE_RESOLUTION_SCALE, 1.0f, Min: 0.1f, Max: 2.0f);

This generates the equivalent #define directives, each guarded by an #ifndef, so default engine values can be easily overrided by gamecode by defining the preprocessor directives before the .h is included.

Generating the Config struct

I also went ahead and offloaded the generation of the config object to the meta reflection system. So, from the following snippet:

MCONFIG(WindowWidth, MPE_WINDOW_WIDTH,
        Type: i32, Section:"Engine",
        Min: MPE_WINDOW_WIDTH_MIN,
        Comment: "Viewport width in pixels. This value excludes OS window chrome")

MCONFIG(WindowHeight, MPE_WINDOW_HEIGHT,
        Type: i32, Section:"Engine",
        Min: MPE_WINDOW_HEIGHT_MIN,
        Comment: "Viewport height in pixels. This value excludes OS window chrome")

The following code is generated:

struct MPEConfig
{
  i32 Engine_WindowWidth = MPE_WINDOW_WIDTH;
  i32 Engine_WindowHeight = MPE_WINDOW_HEIGHT;
  //...
};

This takes advantage of C++11’s brace-or-equals initializer, and generates the code for loading and writing from an INI file from the configuration object, by enforcing specified minimum and maximums.