Using D templates for gamedev

I am using the D programming language to program my 2D-gamedev hobby projects. Many people disregard the D programming language for gamedev because many of its abstractions rely on the GC. But carefully designed D programs can be as efficient as corresponding C/C++ programs while being more typesafe and better structured. I want to share my experience and some tricks I discovered.

In this post I want to show how template metaprogramming can be used to create efficient and safe event dispatching. First let’s reconsider how the SDL dispatches events:

/* C code */

//main loop
while(/*...*/) {
    SDL_Event event;
    //poll all events from the event queue one by one
    while(SDL_PollEvent(&event)) {
        //handle event
    }
}

The SDL_Event is a union. Accessing it is inherently unsafe for type consistency and memory safety. The SDL library mitigates this problem by adding a tag (the member type) which encodes which union-member is to be used. Correct code checks this tag and uses the corresponding union member. But the language and the library do not hinder you from doing the wrong thing. Most of the time this code looks like this:

/* C code */

//event handling
switch(event.type) {
    case SDL_KEYDOWN:
        //use event.key
    break;
    case SDL_MOUSEMOTION:
        //use event.motion
        //but nothing hinders one from using event.key here
    break;
    default: break;
}


In my personal wrapper around the SDL for the D programming language this switch statement is encapsulated. This approach ensures the correctness and safety of the application code. Let’s start with a usage example:

/* D code */

//describing the behaviour (more or less) declaratively
struct Loop {
    public bool keepLooping = true;

    //called when a key is pressed
    public void on(KeyDown ev) {
        //ev is already of the correct type
        if(ev.keysym.scancode == SDL_SCANCODE_ESCAPE) {
            //quit the loop
            keepLooping = false;
        }
    }
}

void main() {
    //...

    Loop loop;
    //wait for the escape key to be pressed
    eventLoop(loop); //magic happens here
}

The eventLoop function takes a value of any type and tries to construct a sensible event loop of its members. But keep in mind that all the template instations happen at compile time. No dynamic dispatch of any kind happens at runtime.

Now let’s look at the definition of eventLoop:

void eventLoop(T)(T that) {

This code starts the definition. It is declared as a template for any type T. (The correct template guard is left as an exercise for the reader.)

    template caseOnEvent(string constant, string name) {
        enum caseOnEvent = "
        static if(is(typeof(that.on(polledEvent."~name~")))) {
            case "~constant~":
            that.on(polledEvent."~name~"); break;
        }";
    }

This is a little local helper template. It generates the case statements for the event dispatch as eponymous string constant that can be mixed in. Using this template even redundancy inside the eventLoop template is avoided. The generated case statement is guarded by a static if which ensures that only needed cases are compiled in. The parameters of the template are the name of the event type constant and the name of the corresponding union member.

    static if(is(typeof(that.beginLoop()))) {
        that.beginLoop();
    }

This part informs the object that the actual event loop is about to begin. It only generated if it that object declares the relevant member. Any needed initialisation can happen here. This makes it easier to use structs with this eventLoop template because they lack default construction.

    while(mixin(is(typeof(that.keepLooping))?
        "that.keepLooping" : "true")) {

        SDL_Event polledEvent;

This code is the beginning of the actual event loop. The looping condition is a boolean member of the struct (if existant).

        while(SDL_PollEvent(&polledEvent)) {
            switch(polledEvent.type) {
                mixin(caseOnEvent!("SDL_QUIT", "quit"));
                mixin(caseOnEvent!("SDL_ACTIVEEVEENT", "active"));
                mixin(caseOnEvent!("SDL_KEYDOWN", "eventKeyDown")); // (*)
                mixin(caseOnEvent!("SDL_KEYUP", "eventKeyUp")); // (*)
                mixin(caseOnEvent!("SDL_MOUSEMOTION", "motion"));
                mixin(caseOnEvent!("SDL_MOUSEBUTTONUP", "eventMouseButtonUp")); // (*)
                mixin(caseOnEvent!("SDL_MOUSEBUTTONDOWN", "eventMouseButtonDown")); // (*)
                mixin(caseOnEvent!("SDL_JOYAXISMOTION", "jaxis"));
                mixin(caseOnEvent!("SDL_JOYBALLMOTION", "jball"));
                mixin(caseOnEvent!("SDL_JOYHATMOTION", "jhat"));
                mixin(caseOnEvent!("SDL_JOYBUTTONDOWN", "eventJoyButtonDown")); // (*)
                mixin(caseOnEvent!("SDL_JOYBUTTONUP", "eventJoyButtonUp")); // (*)
                mixin(caseOnEvent!("SDL_USEREVENT", "user"));
                mixin(caseOnEvent!("SDL_SYSWMEVENT", "syswm"));
            default: //has to be there even if empty
                static if(is(typeof(that.onOther(Event.init)))) {
                    that.onOther(polledEvent); break;
                }
            }
        }

Here the events are actually handled. To generate the correct case statements the helper template, which was defined before, is used. Guarded by the static if only the needed cases are generated which dispatch to the corresponding overloads of the on member. It will work for anything which allows the that.on(...) syntax which in D opens plenty of possibilities. With structs it is for example simple static dispatch. The lines with (*) are explained below.

        static if(is(typeof(that.looping()))) {
            that.looping();
        }
    } //end of the actual event loop
} //end of eventLoop()

This code calls the member looping (if existant) for every loop iteration. Here the actual game logic and drawing can happen. The iteration will happen as fast as possible only suspended for handling the events.

Now returning to the lines above with the star (*) they are a bit harder to explain. Normally the SDL encodes both KeyUp and KeyDown with the same event struct because they have the same metadata. They are only differentiated by the their type member. This is good enough when the switch statement is manually coded. But here I want the library to overload a member based on the type of event. Therefore two subtypes have to be defined:

struct KeyUp {
    KeyboardEvent event;
    alias event this;
}
struct KeyDown {
    KeyboardEvent event;
    alias event this;
}

along with corresponding ones for other event types. This use D’s subtyping feature alias this. Additionally some private module level functions are defined:

private KeyUp eventKeyUp(Event ev) { return KeyUp(ev.key); }
private KeyDown eventKeyDown(Event ev) { return KeyDown(ev.key); }

These allow the Event union to be treated as if it had additional members eventKeyUp and eventKeyDown which can be used transpaently by the template. This possibility is opened by two special D features: Uniform Function Call Syntax (UFCS) and the property syntax. Again all these abstractions only exist during compile time. No runtime cost is associated with them.

As conclusion one can see that many of D’s compile time features allow wonderful abstractions. They fit together to create high-level yet very efficient code. And that is one the reasons I chose the D programming language for my hobby projects.

Addendum (30.04.15)

My wrapper library is now available on BitBucket.

Advertisements
This entry was posted in Gamedev, Programming and tagged , . Bookmark the permalink.

2 Responses to Using D templates for gamedev

  1. Neat! I’d love to see more… is it possible to see more of the code?

    • marfisc says:

      I am going to open source my wrapper very soon. It is very spartanic and I think the event dispatching code is the most interesting part. Nevertheless the the code in its whole form will be visible on BitBucket.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s