Compiling Data into a D Executable

posted in D Bits
Published March 03, 2012
Advertisement
I'm supposed to be posting part three of my series on binding D to C. It's going to be boring to write, so I keep putting it off. But I *will* get to it eventually. In the meantime, I wanted to blog about a neat feature of D that I'd sort of forgotten about until I needed it.

I'm working on an ASCII-based strategy/simulation game in the vein of Dwarf Fortress. One of the things I want to have is an ASCII bitmap that is always available, a default that I can fall back on if any custom bitmaps fail to load. The best way to do this is to have the bitmap data compiled into the executable. A simple, cross-platform way to do that in C or C++ is to convert the bitmap data into a C array in a source file to be compiled and linked. In D, the compiler can do it all for you.

The import keyword in D plays two roles. The most common (and important) role is in import declarations. These are what you use to make the content of one module available to another. D also has a feature called import expressions, which allow you to specify a file that the compiler will build in to the final executable.

[source]
// Import declaration (no parentheses)
import mypackage.mymodule;

// Import expression (with parentheses)
string s = import("some.file");
[/source]

The first thing you might notice is that the file is imported as a string. This isn't going to be very useful for binary data. But that's not a big deal. A simple cast to ubyte[] will do the trick. If other types are needed and casting doesn't fit, a compile-time function can be used to massage the data into the appropriate format. Also, the filename itself can be generated at compile time. Read more about D's compile time function execution.

Now for some real example code, straight from my game. I'm using SDL_image to load the default font image from a ubyte array,

[source]
// The name of the default font file, declared as a manifest constant.
enum DefaultFontName = "default_font.png";

// The image data. Loaded as a string, so casted to an immutable ubyte array.
immutable(ubyte)[] _defaultFont = cast(immutable(ubyte)[])import(DefaultFontName);

SDL_Surface* loadSurface(string filename)
{
if(filename == DefaultFontName)
{
log.writeln("Loading default font image.");
auto rwops = SDL_RWFromConstMem(_defaultFont.ptr, _defaultFont.length);
if(rwops)
{
auto surface = IMG_Load_RW(rwops, 1);
if(surface) return surface;
}
throw new Exception("Failed to load default font image: " ~ to!string(SDL_GetError()));
}
else
{
auto path = format("%s/data/gfx/%s", appDirectory, filename);
log.writefln("Loading font image %s.", path);
auto surface = IMG_Load(path.toStringz());
if(surface) return surface;
throw new Exception(format("Failed to load font image [%s]: %s", path, to!string(SDL_GetError())));
}
}
[/source]

A very important point in using this feature is that it won't compile unless you tell the compiler via a command line switch where it should look for import data. This is a security feature that, IIRC, was implemented to prevent things like remotely accessing a compiler to compile arbitrary data on that system. So you pass the root path with the command line switch -J (i.e. -Jpath). Any file you import via the import expression will be relative to that path.

This is one of those small features of D that mesh with the whole to make it such an enjoyable language to use.
Previous Entry Binding D to C Part Two
Next Entry BorderWatch
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