Dolce Refinements

posted in D Bits
Published September 04, 2011
Advertisement
In my last post, I showed the minimal amount of code needed to get something up and running with Dolce. And while it's a really small amount of code, something kept bugging me about the implementation. It just wasn't "D" enough.

Over the years, the lion's share of my programming experience has been with C and Java. When working with either language, I naturally, and effortlessly, use appropriate design patterns. In other words, I design "to the language." Maybe "design pattern" is the wrong noun here, but the point is that my code structure differs based on the capabilities and features of each language. And I can switch between them seamlessly, thanks to the years of doodling around I've got under my belt. My C code is "C", and my Java code is "Java". Unfortunately, I haven't quite gotten that line of clarity yet in D.

In D, it's possible to structure things like I do in C. It's also possible to structure things like I do in Java. That's why I've refactored Dolce so many times already. It's mentally distracting having both styles in the same code base. Unfortunately, I've found that sticking to one of the two styles is a source of annoyance, as I can't get over the feeling that if I'm writing Java or C in D, then why am I using D?

The approach demonstrated in my last post, where you subclass a Game class and override certain methods as needed, was obviously very Java-ish. And I didn't like the fact that I was requiring any methods to be implemented at all, even if there were only two of them. Finally, it seemed silly to me that if you want to avoid using the Game class, your only option was to handle all the boilerplate yourself. I knew I could do better than that.

So, I went back to the drawing board and came up with this.


import dolce.game.framework;

class MyGame
{
}

int main(string[] args)
{
Framework.start(new MyGame, "MyGame");
return 0;
}


This will create a window that closes if the escape key or close button are pressed. Pretty useless in and of itself, but the point is that there are no longer any specific methods required and you don't need to subclass anything. In fact, you don't need a class at all. Change MyGame to a struct instead, and it will work fine (and you wouldn't need to 'new' it either, though you could if you wanted to). Even better, you can pick and choose the methods you want to implement.

Framework is a static class with a templated start method that looks like this:


static void start(T)(T game, string appname, string orgname = null)
{
init(appname, orgname);

static if(hasMember!(T, "init")) game.init();

_running = true;
al_start_timer(_frameTimer);

while(_running)
{
// Pump all events.
pumpEvents();

// If it's time to update, do it.
if(_update)
{
static if(hasMember!(T, "update")) game.update();
_update = false;
}

static if(hasMember!(T, "render")) game.render();
}

static if(hasMember!(T, "term")) game.term();
term();
}


The thing I want to focus on is the hasMember template. It's implemented in the standard library module std.traits. It is not a function template. It takes a type list and no parameter list. The template itself contains one field, a bool value called hasMember. This calls for a brief detour to talk about templates in D.

Let's say I want to define a template that acts as a boolean value. Let's call it "isTrue".


template isTrue()
{
enum bool val = true;
}


Here, the template has an empty type list and no parameter list. Internally, there is no function declared, only a single member, val (so it's not a function template). Notice that val is declared as an enum. In D2, single-member, anonymous enums are known as manifest constants. They are not actual variables, so cannot be used as lvalues or have their addresses taken. Essentially, every time the template is instantiated, that call to the template is replaced by the value of the enum.

Notice also that the template and the member have different names. This means that you have to explicitly type the member name when you instantiate the template. Also remember that D's template instantiation syntax is templateName!(). So, given that, this is how you would use this template:


void main()
{
assert(isTrue!().val);
}


Sometimes you may want a template to have multiple members, but the large majority of templates you write are going to have only one. In that case, D let's you take some shortcuts if the member name is the same as the template name. In this case, we can eliminate the .val part when instantiating the template.


template isTrue()
{
enum bool isTrue = true;
}

void main()
{
assert(isTrue!());
}


std.traits.hasMember uses this approach. But it also takes two args to its type list -- one is the Type that it is to operate on, and the other is a string that will be used to look up a member method or variable of that type at compile time (this is accomplished via D's compile time reflection capabilities).

You may be wondering how a string value could be part of the type list, rather than the parameter list. The type list isn't just for types. You can also pass any sort of symbol that can be known at compile time. Let's change our isTrue example now to demonstrate this. The new job of the template is to evaluate to true if and only if a given string is equal to the string "true".


template isTrue(string s)
{
enum bool isTrue = "true" == s;
}

void main()
{
assert(isTrue!("true"));
assert(isTrue!("bugaboo"));
}


Run this and the first assert will pass. The second will blow up. The string 's' is never used at runtime here. It's a compile-time-only symbol. When the template is instantiated, the compiler will run the test ("true" == s) and set the value of isTrue to the result. The asserts are basically being rewritten as:


assert(true);
assert(false);


By using hasMember the way I do in the Framework.start method shown above, no calls will be made to any methods not present in the given type. They simply won't be compiled in to the executable. If the method is implemented, a call to it will be compiled in. This means anyone using Framework.start can implement any combination of init, term, update and render methods.

One more thing to point out. Let's look at the minimal example again.


import dolce.game.framework;

class MyGame
{
}

int main(string[] args)
{
Framework.start(new MyGame, "MyGame");
return 0;
}


According to the rules for instantiating templates, the call to Framework.start should look like this:


Framework.start!(MyGame)(new MyGame, "MyGame");


Again, the compiler is letting me take a shortcut. Because I only have one type in the typelist, I can omit the typelist and the ! when I instantiate the template. The compiler has all the information it needs to infer the type, so there's no need for me to be verbose about it.

With the new implementation, I managed to cut out several lines of code from the framework (previously 'game') module. And though I haven't implemented it yet, the door is now open to use Framework for handling the boilerplate if you want to use free functions instead of a class or struct. This is more what I would call "D style" than the first pass was.

Head on over to d-programming-language.org for more info on templates and the std.traits module.
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement