BlackOmen C++11 game engine part 1: Platform definition

31 03 2013

Hello all and welcome to the first part in a series of articles about my personal game engine I’m currently developing in my spare time. I called it BlackOmen like the last dungeon in Chrono Trigger. It is coded in C++11, the latest standard of C++.

My goal with this series is to teach you what I learned along the way developing my engine. Even if I work professionally in the game industry, I still have much to learn about engine programming so feel free to give me constructive comments.

First of all, I want to talk to you about a fundamental header I’ve seen in many libraries, the platform definition header. This header contains type definition and macro for identify which platform your code is currently being compiled.  It is very useful to make your code more portable.

What’s the big fuzz about types ?

Types are a fundamental part on any programming language. They help you tell the compiler how you want your variable to be stored and interpreted in memory. In a game, you are dealing with lots of data and knowing how your data is stored in memory can be critical in certain situations. Types are defined by the kind of data they represent and their size. They also have different size depending on which compiler and/or platform you are using.

As an example, the signed types in the Microsoft C++ compiler are defined like this:

Type name Size in memory (bytes) Range
int 4 –2,147,483,648 to 2,147,483,647
bool 1 false or true
char 1 –128 to 127 by default
0 to 255 when compiled with /J
short 2 –32,768 to 32,767
long 4 –2,147,483,648 to 2,147,483,647
float 4 3.4E +/- 38 (7 digits)
double 8 1.7E +/- 308 (15 digits)

I want to point you to the char type. It says that depending on a compiler switch, it can be signed or unsigned. If you are using char to store a character, it’s fine. But if you use char naively to store a number of 1 byte size, you can expect different results depending of which platform you are and can cause weird bugs. If you want consistent behavior across the platforms you are supporting, you need to create typedef for each kind and size of type you want to use.

My platform definition header

In my engine, I created a file called platform_types.h. It contains all the required typedef and macro for platform support.

It starts with this:

Platform support

#if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)
	#include "platform_windows.h"
#else
	#error "Please define a platform"
#endif

To ease maintenance, I decided to split platform support in a separate header so I can handle the various compilers for that platform. To identify the platform, I use macros that I’m 100% sure that are defined for the given platform. On Windows, if _WIN32 or _WIN64 is defined, you are pretty sure you are compiling on Windows. Notice also the
__CYGWIN__ test to make sure I support Cygwin also. I still only support Windows in my engine but I included an #error preprocessor notification to remind me to create the platform support header only I port my engine to another platform.

Windows platform header

Here’s how platform_windows.h looks like

#define BLACKOMEN_PLATFORM_WINDOWS

#if defined(_MSC_VER)
	#if defined(_WIN32)
		#define BLACKOMEN_COMPILER_VISUALCPP_X86
	#elif defined(_WIN64)
		#define BLACKOMEN_COMPILER_VISUALCPP_X64
	#endif
	#define BLACKOMEN_COMPILER_VISUALCPP

	#pragma warning(disable: 4127) // Disable warning C4127: conditional expression is constant
#elif defined(__CYGWIN__)
	#define BLACKOMEN_COMPILER_CYGWIN
#else
	#error "Define the compiler typedef"
#endif

#if defined(BLACKOMEN_COMPILER_VISUALCPP_X86)
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned int uint;
typedef unsigned int uint32;
typedef signed int sint;
typedef float float32;
typedef double float64;
typedef long long int64;
typedef unsigned long long uint64;
#elif defined(BLACKOMEN_COMPILER_VISUALCPP_X64)
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned int uint;
typedef unsigned int uint32;
typedef signed int sint;
typedef float float32;
typedef double float64;
typedef long long int64;
typedef unsigned long long uint64;
#else
	#error "Please define the types for your compiler
#endif

#if defined(BLACKOMEN_COMPILER_VISUALCPP)
	#define ForceBreakpoint() __debugbreak()
#else
	#error "Please define the ForceBreakpoint for your compiler"
#endif

First of all I created my own define BLACKOMEN_PLATFORM_WINDOWS to allow me to put Windows specific code inside a define if I want to.

After that, I try to identify which compiler I use to create my typedef of the basic types. I also use this occasion to disable some annoying false positive warnings per compiler. When you are compiling with warning-as-errors, those false positives become quite annoying. As before, I add #error to remind me myself to add support for additional compilers if required.

Then come the meat of the header, The typedef. For each basic type, I create a new name with the size of the type specified for it, expect for byte. I decided also to name double float64 to emphasis the fact it’s a float but with greater size.

And at last, I add a nifty macro which can be handy while debugging called ForceBreakpoint(). It forces the debugger to break at that exact line. Useful when you want to debug a specific object and the conditional debugging is too slow. I might move this macro outside of platform specific headers in the future thought.

Target macros

Next in my header is the target macro definition.

#if defined(DEBUG) || defined(_DEBUG) || defined(_BLACKOMEN_DEBUG)
#define BLACKOMEN_DEBUG
#elif defined(_BLACKOMEN_FINAL)
#define BLACKOMEN_FINAL
#elif defined(NDEBUG) || defined(_BLACKOMEN_RELEASE)
#define BLACKOMEN_RELEASE
#endif

It is used to create specific code depending of which target I’m compiling. For example, my debug info code is not compiled for the Final target. The target I’m using are Debug, Release and Final. Debug is the version with no optimization. Release is the version with full optimization but with debug information on. Final is the final release of the game with full optimization and no debug information.

I use the C++ preprocessor definitions in the project settings to set _BLACKOMEN_DEBUG, _BLACKOMEN_FINAL and _BLACKOMEN_RELEASE depending of the target.

DLL visibility macros

Next up is the DLL visibility macros. In a shared dynamic library, you can set the visibility of any function or class (symbols) to be public or private.   This is done using various compiler specific attribute you can’t afford to use directly if you want portable code.

#if defined BLACKOMEN_PLATFORM_WINDOWS
#define BLACKOMEN_DLL_EXPORT __declspec(dllexport)
#define BLACKOMEN_DLL_IMPORT __declspec(dllimport)
#define BLACKOMEN_DLL_LOCAL
#elif __GNUC__ >= 4 || defined __clang__
#define BLACKOMEN_DLL_EXPORT __attribute__ ((visibility("default")))
#define BLACKOMEN_DLL_IMPORT __attribute__ ((visibility("default")))
#define BLACKOMEN_DLL_LOCAL __attribute__ ((visiblity("hidden")))
#else
	#define BLACKOMEN_DLL_EXPORT
	#define BLACKOMEN_DLL_IMPORT
	#define BLACKOMEN_DLL_LOCAL
#endif

#ifdef BLACKOMEN_DLL
    #ifdef BLACKOMEN_BUILDING_DLL
        #define BLACKOMEN_PUBLIC BLACKOMEN_DLL_EXPORT
    #else
        #define BLACKOMEN_PUBLIC BLACKOMEN_DLL_IMPORT
    #endif
    #define BLACKOMEN_LOCAL BLACKOMEN_DLL_LOCAL
#else
    #define BLACKOMEN_PUBLIC
    #define BLACKOMEN_LOCAL
#endif

On a Windows system, you need two attribute to make DLL visibility works. The first one is BLACKOMEN_DLL_EXPORT when you are buidling the DLL itself which tells the linker to make the symbols (functions or class) public. And when you are using code from the DLL, you need to specific BLACKOMEN_DLL_IMPORT for the linker to correctly import the symbols. This is why I use BLACKOMEN_BUILDING_DLL to switch between the two. On Windows BLACKOMEN_DLL_LOCAL does nothing.

On an UNIX/Linux system, either you set the visibility to default or to hidden.  BLACKOMEN_DLL_EXPORT and BLACKOMEN_DLL_IMPORT are the same here. BLACOMEN_DLL_LOCAL tells the linker to hide the symbol.

In any case, if I want my symbols to be public, I use the macro BLACKOMEN_PUBLIC.

The last one is the BLACKOMEN_DLL define, it allows me to support static or dynamic library with the same code.

I don’t compile my engine in a DLL yet but I just put the macros there just in case I need them one day.

Engine specific types defitions

The last section on my platform definiton file is where I defined useful typedef to use for my game engine.

typedef const char* cstring;
typedef const float32 timestep_t;
typedef int64 system_tick_t;
typedef float64 gametime_t;

cstring is just an alias for a C-style string. timestep_t is the delta time between frames, I wanted this to be explicit in the code so that’s why I created a typedef. Same for system_tick_t and gametime_t. system_tick_t is the system time from a high-precision timer and gametime_t is the elapsed time since the start of the game.

How I include this header

Instead of including this header is every file that I use, I found a compiler switch that does it for me.

In Visual Studio, it is here:
VS_ForcedIncludes

In GCC and clang, it’s the flag –include that does this trick.

I agree it’s not the most portable solution but it’s works for me for now.

Conclusion

Having a platform defintion header is a key part in making your software portable across different compilers and systems. It is also a handy place to define DLL visibility macros and common types you use in your library/engine.





Cool features of the D language

12 05 2012

Since several weeks, I gave a second look at the D programming language after watching videos of talk gave by Andrei Alexandrescu and Walter Bright at Lang.NEXT conference (see Walter video and Andrei video). I decided to explain my favorite and cool features of the language. Of course there’s many more, I invite you to take a look at the website to find more information about the language.

scope guard statement

scope guard statement allows you to write code that will be executed either at the exit of the function, when the function succeeds (when they are no error or expection raised) and when you get failure by error or an exception.

It can be used to simulate the RAII pattern. Say you need to write data coming from a database into a File and the API isn’t RAII friendly. You could use try-catch-finally statements like this

auto file = new std.stream.File("test.txt", FileMode.In);
auto db = new SqlDatabase();

try
{
    db.open();
}
catch(SqlException e)
{
    writeln(e);
    return;
}

try
{
    db.callFunctionThatFails(file);
}
catch(SqlException e)
{
    writeln(e);
}
finally
{
    db.close();
    file.close();
}

It’s a simple code with some flaws (the File object is never closed).If we nest various try-catch statement, code can become pretty cluttered and hard to read. Here’s the version with the scope(exit) statement

auto file = new std.stream.File("test.txt", FileMode.In);
auto db = new SqlDatabase();

scope(exit)
{
    file.close();
    db.close();
}

try
{
    db.open();
    db.callFunctionThatFails(file);
}
catch(SqlException e)
{
    writeln(e);
}

Behind the scene, the compiler will write the correct try-catch statement for you.

The other scope guard are scope(success) and scope(failure). Here’s I created a small testcase with throw an exception every multiple of 3. It prints “Everything under control” and return 2 when there’s no exception and print “Just a flesh wound !” and return 5 when an exception is thrown.

import std.stdio;

int globalCounter = 0;

void functionThatThrowRandomly()
{
    globalCounter++;

    globalCounter = globalCounter % 3;

    if(globalCounter == 0)
    {
        throw new Exception("Forced exception");
    }
}

int test()
{
    scope(success)
    {
        writeln("Everything under control.");
    }
    scope(failure)
    {
        writeln("Just a flesh wound !");
        return 5;
    }

    functionThatThrowRandomly();

    return 2;
}

void main(string[] argv)
{
    foreach(i; 0..20)
    {
        writeln("Iteration ", i);

        int value = test();
        writeln("Value: ", value);
    }

    readln();
}

Here’s the output of this code:

Iteration 0
Everything under control.
Value: 2
Iteration 1
Everything under control.
Value: 2
Iteration 2
Just a flesh wound !
Everything under control.
Value: 5
Iteration 3
Everything under control.
Value: 2
Iteration 4
Everything under control.
Value: 2
Iteration 5
Just a flesh wound !
Everything under control.
Value: 5
Iteration 6
Everything under control.
Value: 2
Iteration 7
Everything under control.
Value: 2
Iteration 8
Just a flesh wound !
Everything under control.
Value: 5
Iteration 9
Everything under control.
Value: 2
Iteration 10
Everything under control.
Value: 2
Iteration 11
Just a flesh wound !
Everything under control.
Value: 5
Iteration 12
Everything under control.
Value: 2
Iteration 13
Everything under control.
Value: 2
Iteration 14
Just a flesh wound !
Everything under control.
Value: 5
Iteration 15
Everything under control.
Value: 2
Iteration 16
Everything under control.
Value: 2
Iteration 17
Just a flesh wound !
Everything under control.
Value: 5
Iteration 18
Everything under control.
Value: 2
Iteration 19
Everything under control.
Value: 2

More information about scope guard statement

templates and static if

D is influenced by C++ and contains similar features. One of the most powerful and confusing feature in C++ is template. Templates in C++ are used to create generic containers, algorithms, metaprogramming, compile-time type identification using traits and much more. If you are doing only generic container and algorithm, template are intuitive. But as soon you start doing metaprogramming, templates in C++ become a real clusterfuck.

In C++, to do a template that selects between two types according to a boolean expression looks like this (taken from Andrei’s book Modern C++ design)

template<bool flag, typename T, typename U>
struct Select
{
    typedef T Result;
};
template<typename T, typename U>
struct Select<false, T, U>
{
    typedef U Result;
};

template<typename T, bool isPolymorphic>
class NitfyContainer
{
    typedef typename Select<isPolymorphic,T*,T>::Result ValueType;
    ValueType m_value;
}

In D, using template declaration and static if, the same template looks like this

template Select(bool condition, T, F)
{
    static if (condition) alias T Select;
    else alias F Select;
}

class NitfyContainer(T,bool isPolymorphic)
{
    alias Select!(isPolymorphic,T*,T) ValueType;
    ValueType m_value;
}

First thing you notice is that D does not use the angular bracket for template like C++, Java and C#.  In C++, if you are using a C++03 compiler, type like map<string,Vector<int>> will confuse the compiler on >>, because it can’t decide if it’s a template or the shift right operator. (This problem was fixed in the C++11 standard). In D, they designed the language to be easy to comprehend by a tool with no or little semantic analysis as possible.

Another neat thing about template in D is if the variable inside the template has the same name as the template, you don’t need to access it, the compiler will use the value directly, as you can see in the Select example.

Variadic template are also supported (template that accepts arbitrary number of arguments)

template SelectFirstOrLast(bool selectFirst, Ts...)
{
    static if(selectFirst)
    {
        enum SelectFirstOrLast = Ts[0];
    }
    else
    {
        enum SelectFirstOrLast = Ts[$ -1];
    }
}

void test()
{
     auto firstItem = SelectFirstOrLast!(true,1,2,3,4,5);
     auto lastItem = SelectFirstOrLast!(false,1,2,3,4,5);

     // firstItem is 1
     // lastItem is 5
}

Variadic argument are accessed like an array: you can use the length of the array shortcut (the dollar sign), you can use slices and so on. Notice also the use of enum. In D, enum can be a compile-time constant of any type. It is not limited only to integer and the compiler can infer the type from the context.

More information about templates

More information about static if

Compile time function execution

D allows function to be called at compile time. Of course they are restrictions:

  1. The function source code must be available to the compiler.
  2. Executed expressions can’t reference global or local static variable
  3. Cannot call assembly code
  4. Non-portable cast
  5. C-style pointer arithmetic
  6. Non-recoverable errors (such as assert failures)
For functions to be evaluated at compile time, you must store the result of your function in a static variable, a const variable or an enum variable.
Here’s an example from Wikipedia that generate factorials into an array at compile-time
int[] genFactorials(int n) {
    auto result = new int[n];
    result[0] = 1;
    foreach (i; 1 .. n)
        result[i] = result[i - 1] * i;
    return result;
}

enum factorials = genFactorials(13);

// 'factorials' contains at compile-time:
// [1, 1, 2, 6, 24, 120, 720, 5_040, 40_320, 362_880, 3_628_800,
//  39_916_800, 479_001_600]

More info about compile-time function execution

mixin

mixin is like eval in most of the dynamic languages (Javascript, Python, Ruby), it takes a string literal and compile it to D.

void main(string[] args)
{
    mixin("auto x = 3.1415;");
    writeln("PI generated by mixin: ", x);
}

But as you learned in the last section, you can execute code at compile-time, including string concatenation. It opens many possibilities for code generation.

Philippe Sigaud’s Pegged, a parsing expression grammar (PEG) generator, is the quintessential example of mixin power. You define the grammar in a string literal

import pegged.grammar;

mixin(grammar(`
Arithmetic:
    Expr     <  Factor AddExpr*
    AddExpr  <  ^('+'/'-') Factor
    Factor   <  Primary MulExpr*
    MulExpr  <  ^('*'/'/') Primary
    Primary  <  '(' Expr ')' / Number / Variable / ^'-' Primary

    Number       Variable `));

Behind the scene, the grammar() template generated classes for each rule of the grammar. Here’s how to use the generated code by grammar()

// Parsing at compile-time:
enum parseTree1 = Expr.parse("1 + 2 - (3*x-5)*6");

pragma(msg, parseTree1.capture);
assert(parseTree1.capture == ["1"d, "+"d, "2"d, "-"d, "("d, "3"d, "*"d, "x"d, "-"d, "5"d, ")"d, "*"d, "6"d]);
writeln(parseTree1);

// And at runtime too:
auto parseTree2 = Expr.parse(" 0 + 123 - 456 ");
assert(parseTree2.capture == ["0"d, "+"d, "123"d, "-"d, "456"d]);

In my experiment with D, I was able to generate serialization code using templates, mixin, compile-time function execution and compile-time reflection. Here’s the full source code of that experiment, it doesn’t compile on ideone.com because the DMD compiler is too old.

More information about mixin

Uniform function call syntax

UFCS (as it’s called by the community) is a newest addition of the language that unifies the calling syntax. It allows free functions (functions outside classes) that take a class as a first parameter to be called like it was a member of that class,  similar to extension methods in C#. It’s ease the pain while using non-member functions to increase encapsulation, an idiom suggested by Scott Meyer in Effective C++.

Let’s say you don’t want your serialization code to clutter your class or struct definition, you could do something like this:

class MyClass
{
    // some fields and method
}

void serialize(MyClass p, JSONSerializer serializer)
{
    serializer.writeString(p.stringField);
    // etc...
}

void main()
{
    auto data = new MyClass();
    auto jsonWriter = new JSONSerializer();

    serialize(data, jsonWriter);
}

Without UFCS, you need to pass explicitly MyClass instance to serialize. But with UFCS, you can do instead.

data.serialize(jsonWriter);

The downside of UFCS is that it is harder to tell if the method is defined inside the class or if it’s a free function.

More information about UFCS

with

with is a simple statement inspired by Pascal and Visual Basic that simplify repeated calls or access to an object. It’s a nice feature to have when you interact with C code like the Win32 API which require lots of fields initialization.

Here’s a C code that register a window class in Win32


WNDCLASSEX wcex;
wcex.cbSize = sizeof( WNDCLASSEX );
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon( hInstance, ( LPCTSTR )IDI_TUTORIAL1 );
wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"TutorialWindowClass";
wcex.hIconSm = LoadIcon( wcex.hInstance, ( LPCTSTR )IDI_TUTORIAL1 );
if( !RegisterClassEx( &wcex ) )
   return E_FAIL;

And here’s the same block in D using the with statement


WNDCLASSEX wcex;
with(wcex)
{
    style = CS_HREDRAW | CS_VREDRAW;
    lpfnWndProc = &WndProc;
    cbClsExtra = 0;
    cbWndExtra = 0;
    hInstance = p_hInstance;
    hIcon = LoadIcon(p_hInstance, cast(const char*)IDI_TUTORIAL1);
    hCursor = LoadCursor(NULL, IDC_ARROW);
    hbrBackground = cast(HBRUSH)(COLOR_WINDOW+1);
    lpszMenuName = "";
    lpszClassName = "TutorialWindowClass";
    hIconSm = LoadIcon(p_hInstance, cast(const char*)IDI_TUTORIAL1);
}

Note that to remove ambiguity, I renamed hInstance parameter to p_hInstance.

More information about with statement

Conclusion

The D language is powerful and deep and they are many things to learn about this language. For go further, I suggest you read the official documentation on the website and to read Andrei’s book The D Programming Language. As for me, I think I’ve finally found a worthy successor of C++ for my projects that required C++ in the past. I’m currently investigating doing DirectX 11 code using D.