Howto: Haskell binding for the 3D engine Irrlicht

This blog and the associated code examples do not provide a haskell binding for the 3D engine irrlicht, instead it demonstrates, how such a binding could be built. For this purpose, the first steps of such an undertaking are shown.

Introduction

The creation of a a Haskell interface for a 3D engine, which currently is not widely available, has some difficulties associated with it. First, Haskell is basically not object oriented, therefore a language interface between Haskell and C++ - the main language for many 3D engines - is not natural. Second, there exists no tools, to automatically build interfaces to C++ libraries for Haskell. Third, a interface to a C++ library is a non-trivial task. Fourth, 3D engines do have complex interfaces, with different complexity, so also the choice of a proper engine needs also to be considered.

By the way, I found a nice article on Haskell OpenGL programming, which is a completely different approach and more low level: Haskell OpenGL Tutorial. There is also a Haskell binding to a subset of OGRE from Antti Salonen, who also wrote a blog about Programming 3D with Haskell.

Which 3D library to choose

On the market, there are hundreds of 3D engines available. From the freely available open-source engines OGRE, Irrlicht and Panda 3D are prominent examples. For the purpose of this blog Irrlicht has been choosen for the following reasons: comprehensive, clearly structured, modern and pure C++ API. OGRE has a more detailed interface and Panda 3D although more simpler has a C++/Python mixed interface, which makes it difficult to handle for the purpose of building a Haskell interface.

So what are the choices for the language binding and how can it be acomplished. Let's have a look, what kind of tools for interface conversion actually exist.

SWIG, a general wrapper generator for C++

There is a nice tool, to build interfaces to scripting languages from C++ sources and header, SWIG. It turned out, that this is a quite sophisticated tool, which parses important information from all C++ / C header files and generates an internal representation of what needs to be translated into a language binding. Then there exists a couple of language backends, which translate this internal representation into different language bindings. Those "backends" exists for perl, python, tcl, ruby and quite a large number of other languages. Unfortunately, nobody so far build one backend for Haskell.

The Haskell FFI mechanism

Haskell has a mechanism, to connect to C-libraries, the Foreign Function Interface, or FFI. This mechanism allows, to build wrapper functions, which can be called from Haskell and which invoke a C-Funtion and vice versa. This FFI mechanism is pretty well standardized, so that different Haskell compilers support it. This is good news, a starting point for an integration to the C/C++ world exists. Two points needs to be noted, nevertheless. Although standardized, the FFI is pretty low level and manually defining new language bindings with FFI is quite cumbersome. Second, it is still a C only mechanism, C++ constructs are not included or supported.

The Greencard library

There exists a Haskell tool, called Greencard, which simplifies the FFI process. This largely helps, if one is going, to build FFI interfaces manually. Greencard comes with an own language, which allows, to define the different aspects of a language binding and which then generates automatically the needed low-level FFI code.

Other examples of Haskell/C++ bindings

One could think, that there should be some examples around, where people build Haskell interfaces to large C++ libraries. Acutally, there exists at least one prominent one, wxHaskell, which is a Haskell binding for the C++ wx graphics library. A closer inspection of how this is done, confirmes that there is no easy way. Actually, wxHaskell is build, by a toolset of wxHaskell specific parsers and translators, written itself in Haskell, which parse the wx headers and generate low-level FFI code. So this road is not easily applied to the 3D engine, since the parsers are wxHaskell specific.

Approach

So the approach would be, to use the Greencard library as a tool to simplify the manual process of building a Haskell binding and test with a few examples, how it would work. After this has been acomplished, it might be a next step, to program a Irrlicht specific parser and translator, to automate the task of creating the necessary Greencard instructions.

Example Code

As a first target, the Irrlicht Demo "Hello World" is put into Haskell. The program loads a animated mesh and displays it. See picture above.

The program text of the Haskell file looks as follows:

-- helloworld.hs
-- Haskell Irrlicht binding demonstration
-- copyright, Peter Althainz, 2009
-- see http://www.althainz.de/haskell-irrlicht-interface.html

module Main where
	
import Irrlicht

runLoop = 
	do
		a <- deviceRun
		videoBeginScene True True 100 101 140 255
		sceneDrawAll
		guiDrawAll
		videoEndScene
		if (a > 0) 
			then runLoop
			else return ()

main = do
	irrCreateDevice EDT_OPENGL 640 480 False False False
	deviceSetWindowCaption "Hello World! - Irrlicht Engine Demo (Haskell Version)"
	guiAddStaticText "Hello World! This is the Irrlicht Software renderer!"	10 10 260 22
	mesh <- sceneGetMesh("./sydney.md2")
	node <- sceneAddAnimatedMeshSceneNode(mesh)
	nodeSetMaterialFlag node EMF_LIGHTING False
	nodeSetMd2Animation node EMAT_STAND
	texture <- videoGetTexture("./sydney.bmp")
	nodeSetMaterialTexture node texture
	sceneAddCameraSceneNode (0, 30, (-40)) (0, 5, 0)
	runLoop
	deviceDrop

This code has been directly translated from the corresponding C++ source file and demonstrates, that the basic mechanism works. In the following some details of the language binding implementation are shown. The complete example can be downloaded below.

Example Source Code

The following MIT License applies:

Copyright (c) 2009 Peter Althainz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Download Source Files of Example

Details

Instructions

To get this running, you need Irrlicht 1.6, Greencard 3.x, Haskell ghc and mingw including a proper console. The makefile contain all the build steps and copies the necessary files from irrlicht for runtime data (Irrlicht.dll, media). You need to modify the place, where irrlicht and greencard is installed in the makefile. After that you can start building the example.

Files

The following files are contained in the source zip, with the following purpose:

Makefile: this is the makefile, need to be modified for path information
helloworld.hs: contains the Haskell source for the Irrlicht helloworld program
irrlicht_stub.*: those are the files needed, to build the wrapper to the irrlicht library
irrlicht_stub.hs: input to Greencard, main file, to get an understanding of it
irrlicht_stub.h:
irrlicht_stub.cpp: implementation files for the wrapper

Greencard directives

The following example from irrlicht_stub.hs shows the basic definition of a function in Greencard directives. The % character is the beginning of such a directive. This example shows some of the interesting Greencard concepts. the videoMaterialFlag is created from an enumeration and is simply available as type, after using the %enum directive, explained below. The line %call shows, how pointers, which in this case are mapped to the C-ints are wrapped in a Haskell NodePtr type. Although this type carry a simple int with it, it is distinguishable as NodePtr simply by the Haskell type mechanism (data declaration). The line starting with %fail defines a fail condition and prints an error line. This error normally is a simple string, but in this case taken from a C function, which gives back a previously set error message. This enables the setting of different error messages in the implementation code of the wrapper functions.

%fun NodeSetMaterialFlag :: NodePtr -> VideoMaterialFlag -> Bool -> IO ()
%call (NodePtr (int pNode)) (videoMaterialFlag mflag) (bool value)
%code int result = NodeSetMaterialFlag(pNode, mflag, value);
%fail {result < 0} {getError();}

C / C++ conversion

The FFI interface is not able to cope with C++ structures. Therefore, some obstacles needs to be overcome. Especially cumbersom is the fact, that C++ namespaces are not supported by Greencard. This also leads to some difficulties with the ubiquitious enumerations one typically is faced in C/C++ libraries. Another difficulty are C++ class hierarchies. When you get a pointer to a class in C++, you are able to convert this pointer to pointers of classes, which are positioned upper in the hierarchy. Especially, you can call any method of classes from the actual class, down to the root of the hierarchy. In graphical frameworks, you typically have pointers to objects, that represent different specialized types of a general construct. In addition, you can call general methods on those pointers or more specialized ones. If you implement a function, which takes a pointer and invokes a method on this pointer, you probably would not like to copy this functions for all specialized versions of this pointer, which implement the same method. In the following some solutions to those topics are presented.

Enumerations

Greencard has a nice feature, to handle C enumerations. The %enum directive. Unfortunately, this is not working, if the enumeration is contained in a name space. Since the resulting C interface does not know about namespaces. The solution taken in the example is, to simple copy the enumerations into the unnamed namespace. The following code from the file irrlicht_stub.hs, which is the input to Greencard, shows this:

{- Enumerations -}

%C enum drivertype {EDT_NULL, EDT_SOFTWARE, EDT_BURNINGSVIDEO, EDT_DIRECT3D8, EDT_DIRECT3D9, EDT_OPENGL};

%enum VideoDriverType Int [EDT_NULL, EDT_SOFTWARE, EDT_BURNINGSVIDEO, EDT_DIRECT3D8, EDT_DIRECT3D9, EDT_OPENGL]

The first statement is a C enumeration, which gets copied to the generated C interface file and the second statement is the corresponding Greencard directive, which generates the corresponding Haskell-C mapper code.

C++ class hierarchies

The example below from the wrapper implementation file irrlicht_stub.cpp shows a potential solution to the issue of class hierarchies. A class named HelperMesh encapsulates pointers to all different types of Meshes. In the code, which transfers a mesh pointer to Haskell this mesh-pointer is encapsulated in this class. On the C++ side, the pointer is already coerced to all types of meshes, by the HelperMesh constructor function. When used, the type of pointer of the hierarchy which is needed is given back by a function of the HelperMesh class. If a pure IMesh pointer is needed, getMesh is called and when an animated Mesh is needed, the function getAnimatedMesh is called. Since the class HelperMesh itself does not have subclasses, its pointers can safely be transformed between int (for Haskell marshalling) and pointer to HelperMesh.

class HelperMesh
{
	irr::scene::IMesh *mesh;
	irr::scene::IAnimatedMesh *animatedMesh;
	
	public:
		HelperMesh(IAnimatedMesh* am)
		{
			animatedMesh = am;
			mesh = am;
		}
		
		irr::scene::IMesh* getMesh()
		{
			return mesh;
		}
		irr::scene::IAnimatedMesh* getAnimatedMesh()
		{
			return animatedMesh;
		}
};

extern "C" 	int SceneGetMesh(char* mesh)
{
	if (irrSceneManager == 0)
	{
		setError("SceneGetMesh: Irrlicht library not initialized (use irrCreateDevice)");
		return 0;
	}
	else
	{
		IAnimatedMesh* pMesh = irrSceneManager->getMesh(mesh);
		if (pMesh == 0)
		{
			setError("SceneGetMesh: could not load mesh");
			return 0;
		}
		else
			return (int) new HelperMesh(pMesh);
	}
}

extern "C" int SceneAddAnimatedMeshSceneNode(int pMesh)
{
	if (irrSceneManager == 0)
	{
		setError("SceneAddAnimatedMeshSceneNode: Irrlicht library not initialized (use irrCreateDevice)");
		return 0;
	}
	else
	{
		IAnimatedMesh *pam = ((HelperMesh *) pMesh)->getAnimatedMesh();
		if (pam != NULL)
			return  (int) new HelperSceneNode((IAnimatedMeshSceneNode* )irrSceneManager->addAnimatedMeshSceneNode(pam));
		else
		{
			setError("SceneAddAnimatedMeshSceneNode: provided mesh pointer is of wrong type");
			return 0;
		}
	}
}

What is missing here: Greencard has the ability to delete no longer needed C-Structures, when a pointer in a allocated Haskell structure is going to be garbage collected. This is currently not implemented here.