Jump to content

  • Log In with Google      Sign In   
  • Create Account

D Bits

Dolce Progress, Importing D Modules in a Function, and WYSIWYG Strings

Posted by , in D, Dolce 31 July 2011 - - - - - - · 852 views

I've made a bit of progress on Dolce, but I realized something while I was doing it. My purpose for starting the project was to work on a game idea I've had for a long, long time. I knew from the get go that graphics were going to be a problem The problem is the open-endedness and complexity of the game experience. To pull it off, I either need very detailed graphics, or simplistic graphics with a good deal of descriptive text.

I'm no artist and I can't be bothered to pay anyone for the level of detail I would need for the first option. This iteration is just for fun, not profit. So I have to go for the second option -- minimal graphics plus text. My intention was to go very, very, simplistic. Not quite Dwarf Fortress simplistic, but very nearly. I had it in my head to use a simple tile set, with symbols of some sort for game entities.

My problem, as always, is time. Between work, family, and other projects that have higher priority, I'm wasting too much time on the graphical side of this game idea. I want to get busy with the game itself. Since I'll be using a good deal of text anyway, why not just ditch Allegro and go for a pure text-based game? So that's exactly what I've decided to do. Dolce, however, is a neat little idea that I hope to come back to now and again when I have a lull. And at some point I will very much want to make a graphical version of this game if it turns out to be as fun as I hope. But for now, text it is.

One of my first problems to consider is colored text in the console. I want the game to run on Windows, Mac and Linux, so I want something that's portable and simple. That means ANSI escape codes. But the Windows command console doesn't support ANSI escape codes right out of the box. After a bit of digging around, I found a solution.

ansicon is a little console for Windows that can understand ANSI escape codes. And it's freely distributable so I can bundle it with my app and use it in place of the standard command prompt. Of course, I wouldn't want the user to have to open it and type a command to run my app. Luckily, any arguments passed to ansicon that it doesn't recognize are considered to be a program to execute and its arguments. So a one line batch file would do the trick:

ansicon MyApp

Another option would be to make a simple executable to launch ansicon. In D, it might look something like this:

int main(string[] args)
	import std.process;
	return system("bin\\ansicon.exe MyApp");

Notice how I've got the import statement inside the main function. That's a new feature that was added in the latest DMD release. It doesn't seem like a big deal, but I can't count how many times I've knocked up a quick D script to test out some idea only to realize that I forgot to import std.stdio at the top. If I'm only using a symbol in one function, then instead of scrolling back up and adding the import at the top, I can just add it in place and drive on.

There are other functions in std.process that can be used to execute processes in a cross-platform manner. Of course, before someone points it out, I do realize that the path I passed is not cross-platform because of the backslash. Calls to system on Windows will fail if the path contains a forward slash, so you'd have to do the right thing based on platform. But for this launcher, I don't need to be cross platform.

For those who don't like escaping special characters, there's one more D feature that can help: WYSIWYG strings.

// Using r""
string s = r"bin\ansicon MyApp";

// Using backticks (not single quotes)
s = `bin\ansicon MyApp`;

D also supports heredoc strings, which also do not require escapes.

So now I'm on the road to making a complex text-based game to prototype my idea and you know a little more about D's strings.

D's Default Initializers

Posted by , in D, Dolce 02 July 2011 - - - - - - · 748 views
D, Dolce
I've been away from Dolce for a couple of weeks now. Just came back to it last night and realized I don't like it. I've horribly over engineered some of the modules. So from last night I started stripping stuff out and refactoring. In the process, I realized a silly mistake in my resource management code. I'm throwing it out and rewriting it anyway, but it inspired a topic for this blog.

My resource system was, overall, designed to work with any imaginable resource. So it's template-based and has a flexible interface. But I started from the perspective of Allegro resources, which means working with struct pointers. And I imagined that other potential resources that I might want to use would be class based. That led to an implementation detail that could cause compilation to fail in certain cases.

The problem boils down to something like this. Given a member called _resource of type T, I want to clear it out when I no longer need it in certain circumstances. Since I'm dealing with struct pointers and classes, which are always references, I can just do this:

_resource = null;

That works and does what I want. Then to determine whether a resource is loaded I can just test _resource for null. Until, of course, I decide one day to do something like use a struct resource by value, rather than as a pointer. Not something too farfetched. In that case, I'd get a compiler error. While DMD's template error messages are a good deal better than what most C++ compilers give us, it's annoying to get them. And there's no reason why I shouldn't support non-nullable types.

So here's a contrived example of what happens in this situation.

module nullify;

void nullify(T)(T t)
 	t = null;

void main()
	int i = 10;

So nullify is a templated function with no constraints, meaning it can accept any type at all (I'll talk a bit about D's template syntax another day -- this is a specific case where you can declare the template without the template keyword and call it as you would a normal function). Try to compile this code and you'll get the following output:

nullify.d(8): Error: cannot implicitly convert expression (null) of type void* to int
nullify.d(14): Error: template instance nullify.nullify!(int) error instantiating

Right. To solve this problem, there are two obvious choices. One is to use template constraints to restrict the template only to pointers and classes. There would still be compiler errors of a different nature, but it would be a signal that this template is not intended to work with value types. In some cases, that might be preferable. In this particular case, a better option is to make use of default initializers.

Every type in D has a default value to which instances are automatically initialized on declaration. For example, ints are initialized to 0, floats to nan, classes and pointers to null. This value is readable as a property, .init, both on the type and on the instance. So we can modify the nullify template above like so:

void nullify(T)(T t)
	// You could use t.init or T.init here.
	t = T.init;

Now the code will compile. Pointers and classes will be set to null, floats and doubles to nan, characters to 0xff, and so on. What about value structs? Try this:

module nullify;

import std.stdio : writeln;
import std.string : format;

void nullify(T)(T t)
	t = T.init;

void main()
	struct Foo
		int x = 10;
		int y;
		// Without a toString method, the name of the type
		// would be output by default in the call to writeln
		// above. In this case, "Foo".
		string toString()
			return format("(%d, %d)", x, y);
	Foo f = Foo(23, 38);

The output from this is "(10, 0)". Foo.y, as an int, has the default initializer 0. I've changed the default initializer of Foo.x, however, in the definition of Foo. So all instances of Foo will have the value 10 for x on instantiation. This is not the same as assignment. For example, this will not print 10, but 0:

int i = 10;

Here, we are declaring an instance and assigning a value to this instance. We are not defining a type. Big difference.

So using default initializers is a convenient way to clear out a templated class/struct member for any given type. Then, tests like a hypothetical isLoaded method become

bool isLoaded()
	return _resource == T.init;

You can read more about the .init property in the D documentation at d-programming-language.org.

DerelictGLFW and a Word on Binding D to C

Posted by , in D, Derelict 24 June 2011 - - - - - - · 2,017 views
D, Derelict
Recently, in the Derelict forums, someone asked me if I wanted him to update his GLFW binding, based on the old Derelict, for the Derelict 2 branch so that I could add it to the trunk. We had a GLFW binding in Derelict before, but removed it due to issues with building the GLFW shared libraries. Derelict, you see, is designed to load shared libraries manually and cannot link with static libs. That was quite some time ago. In the intervening years, a new maintainer has taken over the GLFW project and made some improvements to it.

So I've had it in the back of my mind to give GLFW another look at some point for possible inclusion into Derelict 2. Today, I did. A new version was released late last year (2.7) and a new branch that streamlines the API (3.0) has been started. I really like the new branch. So, being the spontaneous sort of fellow I am, I decided I wanted a binding to it. I knocked one up in just over 30 minutes. It's now sitting in my local "scratch" copy of Derelict, waiting to be compiled and tested. Given that it's 1:30 am as I type this, I don't think I'm going to get to it just yet. Tomorrow for sure.

I won't be adding this new binding to the Derelict repository just yet. GLFW 3.0 is still in development. So, just as with the binding I've begun for SDL 1.3 (which will become SDL 2 on release), I'll wait until the C library is nearing a stable release before I check it in.

Making D bindings to C libraries is not a difficult thing to do. It's just tedious if you do it manually, like I do. I have a system I've grown used to now that I've done so many of them. It goes reasonably quick for me. Some people have experimented with automating the process, with mixed results. There are always gotchas that need to be manually massaged, and they might not be easily caught if the whole process is automated. One example is bitfields.

D doesn't support bitfields at the language level. There is a library solution, a template mixin, that Andrei Alexandrescu implemented in the std.bitmanip module. I don't know how compatible it is with C. I've only had to deal with the issue once, when binding to SDL 1.2, but that was before the std.bitmanip implementation. Besides, it's a D2 only solution and Derelict has to be compatible with both D1 and D2. So what I did was to declare a single integer value of the appropriate size as a place holder. The bits can be pulled out manually if you know the order they are in on the C side. I could have gone further by adding properties to pull out the appropriate bits, but I never did the research into how different C compilers order the bitfields on different platforms.

Another issue that crops up is dealing with C strings. For the most part, it's not a problem, but if you are new to D it's a big gotcha. Like C strings, D strings are arrays of chars (or wchars or dchars as the case may be). But, char strings in D are 8-bit unicode by default. Furthermore, D arrays are more than just a block of memory filled with array values. Each array is conceptually a struct with length and ptr fields. Finally, and this is the big one, D strings are not zero terminated unless they are literals. Zero-terminated string literals are a convenience for passing strings directly to C functions. Given a C function prototype that takes a char*, you can do this:

someCFunc("This D string literal will be zero-terminated and the compiler will do the right thing and pass the .ptr property");

If you aren't dealing with string literals, you need to zero-terminate the string yourself. But there's a library function that can do that for you:

import std.string;

// the normal way

// or using the Universal Function Call Syntax, which currently only works with D arrays

A lot of D users like the Universal Function Call Syntax and would like to see it work with more types instead of just arrays. Personally, I'm ambivalent. The way it works is that any free function that takes an array as the first argument can be called as if it were a member function of the array.

Going from the C side to the D side, you would use the 'to' template in std.conv:
// with the auto keyword, I don't need to declare a char* variable. The compiler will figure out the type for me.
auto cstr = someCFuncThatReturnsACharPtr();

// convert to a D string
auto dstr = to!(string)(cstr);

// templates with one type parameter can be called with no parentheses. So for to, this form is more common.
auto dstr = to!string(cstr);

Another gotcha for new users is what to do with C longs. The D equivalent of nearly all the C integral and floating point types can be used without problem. The exceptions are long and unsigned long. D's long and ulong types are always 64-bit, regardless of platform. When I initially implemented Derelict, I didn't account for this. D2 provides the aliases c_long and c_ulong in core.stdc.config to help get around this issue. They will be the right size on each platform. So if you see 'long' in a C header, the D side needs to declare 'c_long'. I still need to go through a few more Derelict packages to make sure they are used.

The issues that crop up when actually implementing the binding aren't so frequent and are easily dealt with. Sometimes, though, you run into problems when compiling or running applications that bind to C.

D applications can link directly to C libraries without problems, as long as the object format is supported by the compiler. On Linux, this is never an issue. Both DMD and GDC can link with elf objects. Problems arise on Windows, however. The linker DMD uses, OPTLINK, is ancient. It only supports OMF object files, while many libraries are compiled as COFF objects. If you have the source code and you can get it to compile with Digital Mars C++, then you're good to go. Otherwise, you have to use the DigitalMars tool coff2omf, which comes as part of the Digital Mars Extended Utilities Package. Cheap, but not free. Then you still might face the problem that the COFF format output by recent versions of Visual Studio causes the tool to choke. There are other options, but it's all nonsense to me. That's one of the reasons when I made Derelict I decided that it would only bind to libraries that come in shared form and they will be loaded manually. Problem solved. But there are other issues.

In a past update to DMD (not sure which), the flag '--export-dynamic' was added to the DMD config file (sc.ini) on Linux. So that means that every binary you build on Linux systems with DMD has that flag passed automatically to gcc, the backend DMD uses on Linux. Normally, not an issue. Until you try to build a Derelict app. The problem is that Derelict's function pointers are all named the same as the functions in the shared library being bound to. This causes conflicts when the app is built with --export-dynamic on Linux, but they don't manifest until run time in the form of a segfault. Removing the flag from sc.ini solves the problem. One of these days I need to ask on the D newsgroup what the deal with that is.

I know all of this could sound highly negative, giving the impression it's not worth the hassle. But, seriously, that's not the case. I have been maintaining Derelict for seven years now. Many bindings have come and gone. Version 2 currently supports both D1 and D2, as well as the Phobos standard library and the community-driven alternative, Tango. I can say with confidence that D works very well with C the large majority of the time. And for anyone planning to use D to make games, you will need to use C bindings at some level (Derelict is a good place to start!). As for binding with C++... well, that's another story that someone else will have to tell.

Dolce Progress and A Word on Mixins

Posted by , in D, Derelict, Dolce 11 June 2011 - - - - - - · 836 views
D, Derelict, Dolce
My time has been limited lately, but the core of Dolce continues to evolve. I think it's converging into something fairly decent. I'm still not completely happy with it, but I believe I'm most of the way there. As usual, I'm getting wild ideas for new projects. Like a renewed interest in building a 2D library from scratch in D. Or a more generic framework with pluggable backends for Allegro, SDL, SFML, and GLFW. You know, like I explicitly said at the beginning I had no interest in making. This time, though, I refuse to let myself get distracted. So I'm digging my heels in.

Recently, I've added event handling, mapping keycodes or mouse button ids to delegates. The first pass is usable, but it can be improved a good deal. D's delegates are great for this sort of thing. But delegates aren't what I want to talk about today.

Until now, my test app has only been using the keyboard. With the inclusion of the event handler, I added mouse support. While testing it out, I kept getting a puzzling result. Allegro has no constants for mouse buttons. They are numbered from 1 to whatever the maximum number is (which you can find out via al_get_mouse_num_buttons()). But my event handler kept getting button 0 passed to it, no matter which button I pushed. al_get_mouse_num_buttons was correctly reporting 3 (I dumped my 8-button gaming mouse some time ago). After several minutes of digging around, I started to suspect my DerelictAllegro binding. Sure enough, I had left out 4 fields from the struct declaration. I patched that up and all is well.

Looking at the binding source reminded me of a D feature that would be worth mentioning here. In my last post, I briefly mentioned D's string mixins. There is another type of useful mixin in D that I've made use of in the Allegro binding: the template mixin.

Allegro's event structs are all declared with the following macro at the top:

#define _AL_EVENT_HEADER(srctype)                	\
   ALLEGRO_EVENT_TYPE type;                      	\
   srctype *source;                              	\
   double timestamp;

So the mouse event structure looks like this:

typedef struct ALLEGRO_MOUSE_EVENT
   struct ALLEGRO_DISPLAY *display;
   int x,  y,  z, w;
   int dx, dy, dz, dw;
   unsigned int button;
   float pressure;

D has no #defines, and no preprocessor at all. But this C idiom can easily be replicated with template mixins. Here's what I did in the DerelictAllegro binding:

// First, declare the template that you intend to use as a mixin.
template _AL_EVENT_HEADER(T)
	T* source;
	double timestamp;

// Then, mix it in.
	int x, y, z, w;
	int dx, dy, dz, dw;
	uint button;
	float pressure;

Simple. String mixins could also be used here.

 // You would want to use a compile time function with a signature something like this
string allegroEventHeader(string type) { ... }

// Then call it like so:

Personally, I prefer template mixins in situations like this.

Take a look at the template mixin documentation over at the shiny, new d-programming-language.org website for more info.

Operator Overloading in D

Posted by , in D, Dolce 29 May 2011 - - - - - - · 1,406 views
D, Dolce
I didn't add much of anything new to Dolce in the past week. The time I had to work on it was mostly spent refactoring. I'm patching this thing together haphazardly as I go along. I have a good idea of what I want it to do, but how to organize it all wasn't clear from the beginning. Package hierarchy, structs vs classes, module content, and so on. My little test app has served two functions: making sure it all works and understanding how it will be used. It's the latter that led to the refactoring.

So now that the plumbing is done and in working order, it's time to start looking at game-related utility code. For that, my little test app is woefully inadequate. So I'll be porting the a5teroids demo that ships with the Allegro package. This will get me started. But before I begin the port, I'm looking at two additions: entities and a math package/module. Since entities will likely need the math, I'm starting with that.

I'm not yet sure if I want a whole new package for the math stuff or just a single module, but for now I'm going with the former. The first module in the math package provides an implementation of a 2D vector. It's been a while since I've done any operator overloading with D, and I had completely forgotten how different it is between D1 and D2.

In D1, operator overloading is similar to the way it works in C++. The biggest difference is that instead of overloading by symbol, you overload by purpose. The following overloads the '+' operator.

struct Foo
	int x;

	// Foo + int
	int opAdd(int i)
    	return x + i;

	// int + Foo
	int opAdd_r(int i)
    	return i + x;

The rationale behind naming overloads based on purpose, rather than the symbol, is that it encourages programmers not to change the meaning of the symbol. This way you can expect Foo + Bar to actually do some sort of addition, rather than an unrelated operation. I thought it was a pretty good idea when I first saw it. Whether or not it actually meets that goal in practice I couldn't say. Regardless, I'm not using D1, and D2 has changed this dramatically. For more on operator overloading in D1, see the D1 documentation.

In D2, most (not all) operator overloads are templated. You have a few basic templates that can be constrained based on the type of operator you want to overload. opUnary is for unary operations, such as negation. opBinary is for binary operators, such as addition. You can implement the templates once for each operator, or combine several into one instance, using template constraints in both cases. The following example demonstrates..

bool isMathOp(string op)
	return op == "+" || op == "-" || op == "*" || op == "/";

struct Foo
	int x;
	// Overload the negation operator '-'
	int opUnary(string op)() if(op == "-")
        	return -x;

	// Overload addition, subtraction, multiplication and division for the case of Foo op int.
	int opBinary(string op)(int i) if(isMathOp(op))
		mixin("return x" ~ op ~ "i;");
	// Overload the same for the case of int op Foo.
	int opBinaryRight(string op)(int i) if(isMathOp(op))
		mixin("return i" ~ op ~ "x;");

This code example uses two other nifty features of D. isMathOp is a function that qualifies for execution at compile time (the reasons for which I may talk about in a future post). Compile-time function execution (CTFE) is an awesome tool for generating code. In this case, it's not particularly needed, but it does make the template constraints more concise. Plus, I wanted to show off.

The second feature is string mixins. A string mixin basically inserts the given string into the code during compilation. In this case, it is generating strings of code like "x + i" or "x - i", for each template instance, using the append/concat operator. It's possible to call CTFE functions inside a mixin if you need to. That's a technique I use in Derelict to ensure compatibility between D1 and D2 (Dolce is D2 only, but Derelict has to support both).

So you can use CTFE and mixins, together with template constraints, to cut down the amount of code you need to implement in order to overload operators in D2. There are several more options for operator overloading, including some that aren't template-based. You can read more about in the D2 documentation.

Now, back to work on Dolce.

D Arrays

Posted by , in D 26 May 2011 - - - - - - · 850 views
Every D programmer loves D's arrays. All of them. I challenge you to find one who doesn't. Particularly, it's the ability to slice and manipulate arrays without worrying about memory that really pulls people in.

In my previous post, I talked about Dolce's game screens and the stack-based screen manager. Internally, the stack that the screen manager uses is not a custom-built stack class, nor any sort of container from an existing library. It's an array. You can implement a stack quite easily with a D array.

// Declare an empty dynamic array to use as your stack.
Item[] stack;

// To push an item onto the stack, use the array append/concat operator.
stack ~= something;

// To pop, simply decrease the length of the array.
stack.length -= 1;

// What if we have a queue instead of a stack and want to take the first item?
// First, save the item at the head of the queue.
auto item = queue[0];

// Then reassign the array to a slice of itself, starting from index #1 to the last index. 
// The $ is a substitute for the length property of the array. The first number in a slice
// is inclusive, the second exclusive. So the resulting slice below will contain all elements
// from queue[1] to queue[queue.length - 1].
queue = queue[1 .. $];

D's arrays are quite powerful. In addition to being arrays, they are also ranges, which is something that would take more than a simple post to explain. I don't quite fully grok them myself yet. But, when used in conjunction with the std.algorithm module, there's a great deal of power behind them.

You can read more about D's arrays in this article on slicing in D by Steven Schveighoffer. Also, see the documentation for the std.range module to get an idea of what ranges are.

But wait, there's more! The registry the screen manager uses is a built-in associative array. While there are some more sophisticated hashmap implementations out there for when you really need them, the built-in AAs will cover a lot of your use cases.

// Declare an AA that uses strings as keys.
Item[string] registry;

// Add an item.
registry["foo"] = myItem;

// Remove an item.

// Test if an item exists in the AA .
 // The in operator returns a pointer.
auto item = key in registry;
if(item !is null)
   	// Do something

// Efficiently iterate all the values via a delegate
foreach(val; registry.byValue())

// Or the keys
foreach(key; registry.byKey())

// But don't try to remove anything while iterating the delegates as above. Instead,
// iterate over an array of keys or values using the .keys or .values properties.
foreach(key; registry.keys)
	auto item = registry[key];

D's arrays and AAs will get you farther than those of C++ or Java. But, when you need more, there is also a range-based container module, std.container, which will eventually hold a number of container types. Also, as an alternative, Steven Schveighoffer's DCollections library is quite awesome.

Dolce and Free Functions

Posted by , in D, Dolce 22 May 2011 - - - - - - · 1,168 views
D, Dolce
There's one thing that's missing from D that I really wish it had: namespaces. There have been discussions about this on the D newsgroup in the past. Walter Bright, D's daddy, has taken the position that D's modules *are* namespaces. In a way, they are. But, not really.

For those new to the party, D eliminates the need for header files. Interface and implementation are all part of the same code unit, called a module. Instead of #including headers, you import modules. Modules can reside in a package hierarchy, much like Java classes. So, given this source file:

// This is the module 'bar' in the package 'foo'.
module foo.bar;

void baz()
	// Do something.

You would use it like this:

module myapp.mymod;

// Import the foo.bar module so its symbols are accessible by this module
import foo.bar;

void myfunc()
	// baz is visible, so it can be called like this

	// Or, with the fully-qualified name

So yes, a module namespace does exist, but it isn't enforced by default. That means that there can be conflicts if multiple imported modules export functions/types with the same signature. Now, from the user side, it can be enforced two ways. First, is by using static import.

static import foo.bar;

Now, everything you access in the foo.bar module must be prefixed with "foo.bar". You can go one better and add an alias that let's you call foo.bar by another name:

alias foo.bar Foobar;

This allows you to call Foobar.baz(), but it's still foo.bar.baz() that is being enforced under the hood. Remove the static keyword from the import, and baz() will still be accessible by itself. The alias is just an alternative, not an enforced namespace.

The second approach is to use a feature called "named imports", which effectively combines the two steps above, but with enforcement of the given name instead of the module name.

import Foobar = foo.bar;

Now, everything you access in the foo.bar module must be prefixed with "Foobar".

These approaches are all fine, but they put the onus on the user to create his own namespace. As a solution for solving namespace conflicts in client code, I think they're just peachy. In fact, I really like them. But from the perspective of a library designer, I have one major issue with it. I want to wrap my functions in a namespace, so that the client can't have conflicts to begin with and doesn't have to remember to use a named or static import.

Currently, in Dolce, I'm presented with the dilemma of how to handle certain functions that really ought to be free functions. I really don't want to take the C approach with function naming conventions, because it just doesn't mesh with the parts of the library that aren't free functions. On the other hand, I don't want to pollute the global namespace with function names that could easily clash with other libraries or client code. One option is to use static methods in a struct, or a non-instantiable class. But, to me, that just really feels dirty. Plus, every new type you define in a D program adds a TypeInfo instance to the final executable. It's not a lot, but it's just useless bloat for a class that is serving as a namespace hack. It offends my sensibilities, I suppose.

So one more option that I'm considering is having a sort of wrapper module that publicly imports the modules with free functions, giving them a name.

module foo.bar;

public import Bar = foo.realbar;

Imported symbols, by default, are accessible only in the module that imports them. So if module A imports module B, and you then import module A , you can't access B's symbols. However, if module A publicly imports B, then you can access B's symbols just by importing module A. So this enforces the namespace on the client if they import this module. Furthermore, clients will have a choice. They can import foo.bar and get access to the Bar namespace, or they can import foo.realbar and get the free functions, or create their own namespace if they need it. I only see a couple of drawbacks. The only problem I have is deciding if this really improves the situation or not.

Of course, the other option is just not to worry about it. Phobos is full of free functions, after all. Ultimately, my point of view is that I'm creating this library for myself, so whatever makes me happy is the way I'm going. I just have to decide which approach makes me happy.

To Wrap or Not To Wrap

Posted by , in D, Dolce 18 May 2011 - - - - - - · 421 views
D, Dolce
One issue I briefly considered with Dolce is whether or not I should wrap Allegro in a nice, D-style API. I played around with it a little bit, wrapping an audio stream, first as a class and then as a struct. For those of you unfamiliar with D, yes, there is a difference.

Structs in D are, by default, value types. Classes are reference types. You generally allocate structs on the stack and classes on the heap (though the reverse is possible, but uncommon). Furthermore, structs have no vtables -- they aren't inheritable. This is a difference that C++ programmers sometimes have difficulty with. Typically, you'd use structs for POD types and classes for everything else. Of course, there are exceptions to the rule.

So I tried both types to see how they would play with my resource manager. It was immediately clear that classes make more sense, as I'd not want to be passing structs around by value everywhere and would likely allocate them on the heap anyway. In both cases, it was nice applying D's property syntax to some of the wrapped Allegro functions. 'al_set_audio_stream_playing(stream, true)' is a lot harder on the eyes, even for a C programmer like me, than 'stream.playing = true'.

But no. Let's not go there. It looks nice, but it really isn't my goal with this library. All I really want is boilerplate and utilities. Right now, you can load and initialize the Allegro shared library, along with any addons you want to use, set up default configuration, and create the display, all with one function call that takes three parameters. Or you can initialize each addon and create the display in your own time. That's what I mean by boilerplate. And by utilities, I don't mean wrappers. I mean a resource manager to help you with loading and managing resources, helper functions that reduce a multi-function-call process to one, and game tools like a simple math package, a 2D scene graph, entities and the like.

The goal is not to give Allegro an OOP interface, but to enable the client to get straight to the game code without mucking about with the other stuff first. Allegro gets you a long way toward that goal already, but because it's mostly just an OS abstraction, there's still another layer that can sit on top of it and give you a bit more utility. That's what Dolce is about.

January 2017 »

2223 24 25262728

Recent Entries