Quantcast
Channel: GameDev.net
Viewing all articles
Browse latest Browse all 17825

Getting Started with the D Programming Language

$
0
0
I’ve been using the D Programming Language for ten years now, these days more than ever. Over the years, I’ve experimented with different ways of compiling my D projects (make, build tools, custom build scripts) and with different editors and IDEs. Despite the fact that there are several great options now for using IDEs such as Visual Studio, MonoDevelop and Eclipse for D development, I have found myself consistently working from the command line.

In this article, I’m going to show how to get started with a D development process that mirrors my own. I’m not trying to push this process on anyone, nor am I making any claims that it’s the best way to go about developing software with D. Developers can be a fickle lot and each has their own preferences and habits, so I would never presume to say, “This is the One True Way!” Instead, I’m hoping to show those who are curious about D that it’s quite simple to get up and running so that experimentation can begin in no time.

I should also say before I go further that I live on Windows. I venture into the land of the penguins now and again, when I really have to, but I prefer to avoid it as much as possible. And the only Apple products I’ve used since the Apple II have been iPods/Pads/Phones. As such, this article is going to be quite Windows-centric. That said, it shouldn’t take too much effort to translate the setup I describe here to those other OSes.

Installing DMD


At the time of writing, there are three D compilers available. GDC is built on top of the GNU Compiler Collection. LDC is based on LLVM. DMD is the reference compiler maintained by Walter Bright, the creator of the D Programming Language. GDC and LDC are great compilers. In fact, they often produce more performant executables than DMD. But they do require a bit more effort to setup on Windows than DMD does. So for this article, I’m going to focus on DMD.

On the DMD download page you will find a Windows installer, a dmg file for Mac users, several deb packages and RPMs for a few different Linux distros and an all-inclusive zip file which contains binaries for all supported platforms. Personally, I prefer the zip file, even for the rare occasions I have to drop into Linux. I really believe that the Windows installer is superfluous. There’s nothing to configure besides the path and nothing needs to be copied into any system directories. Unzip, set the path, done.

A decision must be made in how to set the path. With the zipped DMD package, the path for the Windows binaries is “dmd2\windows\bin”, but there are also binaries for FreeBSD, Linux, and OSX (32- and 64-bit where supported). The simplest thing on Windows is to set the global path (a quick trip to Google should help in figuring out how to do this for a particular version of Windows). Be careful, though, as one of the Windows binaries is the Digital Mars version of ‘make’. If you have another version of make on your global path, that can cause some headaches. Setting the global path also makes it difficult to support multiple versions of DMD. There are easy ways around that (I use batch files and cmd.exe shortcuts, and there’s also Jacob Carlborg’s D Version Manager), but for someone just starting out it’s not a big deal. Linux users using the zip file can either copy some stuff around to make everything globally visible, or edit the appropriate shell config file to set things up for a specific user. More details can be found at dlang.

Once the path is configured properly, whether manually or via installer or deb package/RPM, get to a command prompt and type the following:

dmd

Invoking dmd with no args will cause it to display the version number along with all the valid command line options, the same as typing dmd --help. This will confirm that the path is properly configured. Next, open up a text editor (I used Crimson Editor, a.k.a. Emerald Editor, for years, but switched to Sublime Text 2 a few months back — well-worth the price of the license). Enter the following into a text file and save it somewhere as "hello.d".

import std.stdio;

void main() {
    writeln( "Hello D!" );
}

Now navigate on the command line to the directory in which hello.d was saved and enter dmd hello.d. If it compiles without error, then the compiler was able to find the standard library and all is well. If there were errors, be sure to read them carefully. Most DMD errors are fairly clear, but for someone without experience with compilers on the command line this might not be the case. At any rate, if it is not clear that the error was caused by the code or the configuration, the digitalmars.D.learn newsgroup is the place to go for answers. For those who left their newsreaders in the 90s or have no idea what a newsreader is, there is both a forum interface and a mailing list interface for all of the D newsgroups to make them more palatable.

One final note on the installation. The vanilla DMD package on Windows only fully provides what is needed to compile 32-bit programs. For 64-bit, it’s only half the story. 32-bit makes use of the Digital Mars C++ compiler backend toolchain (including the annoyingly ancient OPTLINK linker and some equally ancient Win32 libraries). 64-bit compilation actually relies on the Microsoft C++ compiler tools, meaning a version of the Windows SDK which includes the compiler tools must be installed. Right now, that means version 7.1 of the Windows SDK.

How to Avoid Invoking DMD Yourself


Many statically compiled system languages require a two-step process to create executables: compiling and linking. This can be condensed into one step if all of the source files and required libraries are passed on the command line together, but when compiling and linking separately each source file needs its own compile step, then all the object files need to be passed to the linker to generate the executable. Either way, when multiple source files are involved, this can be difficult to manage by hand.

It would be nice if DMD (and GDC and LDC) compiled in a way more similar to Java than C and C++. They don’t. However, there are some tools out there that do. One of them actually ships with DMD. Before I get to that, a little history (because I just can’t resist).

A few years back, a D user named Derek Parnell created what, as I recall, was the first build tool for D. Called ‘bud’, it worked by following the import tree from the main source module, gathering up each imported module, and passing all of them to DMD for compilation. For example, if bud foo.d were executed, and foo.d imported bar.d, then bud would see that import, add bar.d to its list of files to compile, then scan imports in bar.d, and so on, until there were no more imports to parse. The tool became quite popular. In my binding project, Derelict, I initially used make, but eventually switched over to supporting bud as the default build tool via a build script. I began to recommend that people not compile Derelict at all, but just use bud to compile their app so that the Derelict modules would be compiled automatically. Compilation with DMD has always been extremely fast. bud made it extremely convenient.

Not long after, Gregor Richards released DSSS, which was a build and package management tool. Initially, he was using bud for the build side, but decided later to create his own tool called Rebuild (a play on bud, which was originally called "build"), which he released individually. Now it became possible to make D libraries distributable and compilable via a simple configuration script. Users could execute dsss net install derelict, for example, to automatically download a library (in this case, Derelict), compile it, and make it available to any new D programs developed on the local system. It looked set to become the de facto standard.

Unfortunately, neither project is active any longer. They were still usable for quite a while, but they inevitably fell off the radar. Thankfully, some new options have since become available.

Shortly after Andrei Alexandrescu became involved with D, he slapped together a little utility called ‘rdmd’. The basic concept behind it is the same as bud. It follows imports and pulls in all the imported files, then hands them off to DMD for compilation. By default, it executes the binaries it compiles, but can be told via a command line switch to compile without executing. It has other options as well, such as the --eval switch which allows code to be entered on the command line. See the documentation for details. It now ships with DMD on all supported platforms, so it’s easy to get compilation of multiple D source files up and running out of the box without installing any third-party tools.

The newest build tool on the block, and the one I’ve taken to using, is ‘dub’. It’s not just a build tool, but also a package manager, the spiritual successor to DSSS. This tool comes from Sönke Ludwig (who also the gave us vibe.d). He has set up a registry for developers to register their projects. Users can specify registered projects as dependencies in a dub configuration file (package.json), and the tool will make sure all dependencies are downloaded, installed and compiled. Currently, only projects on github and Bitbucket are supported for automatic installation.

I now use dub for all of my current D projects and plan to continue using it in the future. There are two things I really love about it. First, the configuration file is dead simple to set up. The tool can generate a generic one for you along with a directory tree for your project, but since I don’t like the directory tree it creates, I just create my own and copy my package.json file from project to project, editing it as I go (really, it’s dead simple). Second, the tool has some simple heuristics to differentiate a library project from an executable project, but can be configured to compile any source tree in any way. This is extremely useful for library development. In the past, I’ve always had to keep a separate test program as I develop a library and compile it separately. With dub, I just specify two different configurations in the package.json file, set up the project to match dub's simple heuristics, and I can use a single source tree as a library or as an executable.

Just to more clearly illustrate what I mean, imagine a library called ‘MyLib’. All of the library source modules will be in the “source\mylib” directory. Then, a separate module, “source\app.d” is created. In package.json, dub is instructed of two configurations. One, the default which is used when dub build is executed, compiles a library. The second, let’s call it ‘dev’, is used when the command dub build --config=dev is executed and will produce an executable. The tool will automatically include “source\app.d” in the second case, but will ignore it in the first. The two configurations in the package.json file only need to specify a name for the configuration and a target type (library or executable). There are other options, but those are the only two required. An example is provided in the next section. More info is available at the dub registry.

To summarize, it’s easy to get going with multiple source files out of the box with DMD by using the rdmd tool that ships with the compiler. When experimentation moves beyond using the standard library and making use of third-party libraries, dub makes that a piece of cake as well.

Derelict 3


Now I’m going to toot my own horn a little. Given that this is a game development site, I would imagine that people experimenting with D for the first time will quickly move beyond “Hello world” style programs and want to get to something more visual. That’s where Derelict 3 comes in.

Derelict is a collection of D bindings to a number of popular C and C++ libraries that are useful for game development. I first started working on it in early 2004, providing dynamic bindings for OpenGL, SDL, and OpenAL. Over the years, it has evolved through three major versions, packages have been added and removed, build systems have come and gone, and I’ve pulled out more hair than I care to think about. I try to spend as little time on it as I can possibly get away with without feeling guilty. So far, that has served me well.

Getting started with Derelict isn’t too terribly difficult. The easiest way is to use dub. I’ll describe the harder way first.

Anyone who has been doing any sort of C or C++ development long enough must be familiar with git by now. If not, don’t look to me for help. Just move on to the next paragraph. Otherwise, Derelict 3 can be cloned from https://github.com/aldacron/Derelict3.git. Once that’s done, open up a command prompt with DMD on the path, cd to the “build” subdirectory in the “Derelict3” folder, and execute dmd build (or substitute gdc or ldc2). That compiles the build script. Now execute build (or ./build for the penguins). This will compile libraries for each Derelict package and each library will be output to “Derelict3/lib”. To use Derelict, make sure “Derelict3/import” is on the import path (the import path can be set on the command line with the -I switch) and that the libraries are linked (via pragma(lib, “path/to/lib”) in source or specified on the command line). Yeah, this is the more complicated way, particularly since each compiler has a different way of specifying the library path on the command line, and with DMD it depends on which backend is being used (I can never remember what it is for 32-bit DMD on Windows). So I recommend using dub anyway.

Here’s the package.json for another library I’m working on. The relevant bit for Derelict is in the “dependencies” section.

{
    "name": "derringdo",
    "description": "A framework for 2D games with SDL2.",
    "homepage": "",
    "copyright": "Copyright (c) 2013, Michael D. Parker",
    "authors": [
        "Mike Parker"
    ],

    "dependencies": {
        "derelict:sdl2": "~master",
        "derelict:physfs": "~master"
    },

    "configurations": [
        {
            "name": "lib",
            "targetType": "library",
            "targetPath": "lib"
        },
        {
            "name": "dev",
            "targetType": "executable",
            "targetPath": "bin"
        }
    ]
}

Nothing to it. Edit this to fit your project, save it in the parent directory of the project source tree, execute dub build or dub build --config=dev (as described above) and D Programming with Derelict 3 happiness is sure to follow.

Using Derelict in code is quite simple as well. Let’s assume a project using SDL2 and SDL2_image, two commonly used game development libraries. This snippet of code shows how to import the relevant Derelict modules and load the libraries (remember, Derelict is a set of dynamic bindings, meaning it is designed to load shared libraries (.dll, .so and .dylib) at runtime).

import derelict.sdl2.sdl;
import derelict.sdl2.image;

void main() {
    DerelictSDL2.load();
    DerelictSDL2Image.load();
}

That’s it. If the libraries fail to load, a DerelictException will be thrown, specifically one of the subclasses SharedLibLoadException or SymbolLoadException. The former is thrown when the library fails to load, the latter when a symbol from the library fails to load. Derelict also allows the throwing of SymbolLoadExceptions to be skipped so that if specific symbols are missing, loading can continue. This is useful for loading older versions of a library. Importing derelict.util.exception will pull all DerelictExceptions into the namespace so that they can be handled appropriately. Also, each loader has a version of the load method that allows shared library names to be specified explicitly. By default, the loaders use the common library names as output by each distribution's build system (such as SDL2.dll on Windows) and uses the default system search path to find them. Sometimes it may be desirable to override this behavior, such as when shipping all shared library dependencies in a subdirectory (DerelictSDL2.load(”libs/SDL2.dll”)).

Notice that there are no corresponding calls to any “unload” methods. This is because Derelict makes use of D’s static module destructor feature to automatically unload libraries when the app exits. Static module constructors could have been used to load them as well, but that would take away the opportunity to handle exceptions, or to specify alternate library names. The unload methods do exist and are publicly accessible in case a library needs to be explicitly unloaded at a particular time, but they are not required to be called otherwise.

When compiling the above manually, the libraries DerelictSDL2.lib and DerelictUtil.lib need to be linked. There is no DerelictSDL2Image.lib. The loaders for SDL2_image, SDL2_ttf, SDL2_mixer, and SDL2_net are all part of DerelictSDL2.lib. The format of the file names and the file extensions depend on the platform and compiler. On Posix systems, it is also necessary to link with libdl, since Derelict uses dlopen and friends to handle the shared libraries. When compiling with dub, libdl will still need to be linked on Posix systems. This can be done with a “libs-posix” entry in the package config file (see the dub package documentation for details).

All Derelict packages adhere to this basic format, with the one exception being DerelictGL3. There are two OpenGL loaders in Derelict, one that does not include deprecated functions and one that does. For the former, import derelict.opengl3.gl3 and call DerelictGL3.load. For the latter, import derelict.opengl3.gl and call DerelictGL.load. In both cases, after an appropriate OpenGL context has been created, a reload method must be called (DerelictGL3.reload and DerelictGL.reload respectively). The load methods load the OpenGL 1.0 and 1.1 functions. The reload methods load the functions for versions 1.2+, as well as all supported ARB extensions (support for all ARB extensions may not yet be implemented). It is also recommended to call the reload method each time the context is switched. Both loaders also provide a means of determining the version of OpenGL that actually loaded.

Derelict 3 provides bindings for OpenGL, OpenAL, SDL2, SFML2, GLFW3, AssImp3, Lua 5.2, FreeType 2.4 and more. At the time of writing, the project README claims it’s all in an alpha state, but it’s 100% usable. The only thing missing is the documentation and a couple of more packages I want to add. Of course, one thing I haven’t mentioned here is that, when using Derelict, it’s necessary for the user to get possession of the appropriate shared libraries, otherwise there's nothing to load. Most projects provide binary distributions, some do not. In the latter case, it is necessary to have a build environment set up to compile C binaries. I try to keep the bindings up to date for each project, but sometimes I fall behind. I’m always happy to accept pull requests to correct that.

Conclusion


This has been a brief introduction on one way to get ready to compile programs with the D Programming Language, with an eye toward would-be D game developers. I hope the information here is useful enough to help those new to the language in getting started. As for how to actually program in D, I’ll leave that to Andrei Alexandrescu to explain in his excellent book, The D Programming Language, and to the helpful souls in the D Community, which includes the D Newsgroups and #D on freenode.net.

For those who want to go beyond the bounds of what I’ve described here, I encourage a look at VisualD, MonoD, and DDT for Eclipse to experiment with D IDEs, and of course GDC and LDC to get a feel for alternative compilers. Anyone using Derelict is encouraged to join the Derelict forums to ask for help and to report bugs and other issues on the project page at github.

I find D an enjoyable programming language to use and I look forward to seeing more new faces join the ranks of our ever-growing community.

Viewing all articles
Browse latest Browse all 17825

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>