Adventures in Lua Binding

I’ve mentioned before (actually, probably only in that last post) about a game I’ve been working on on and off for the last few years (I think I said three, but it’s actually been five, with it being inactive for most of that time.) I had decided long ago that it would probably be scripted with Lua and I’m just now finally getting around to experimenting with that. I’ve ultimately decided it’s going to either be using OOLua or Luabind. I’ve given both a fairly quick look over and attempted to use each to bind a specific class in my project.

For the sake of conversation I’m going to use a very basic C++ class that’s based on my project’s Configuration class. I’ll go through and show how both libraries bind the class to Lua.

First, here’s the C++ class:

class Configuration {
public:
enum ConfigurationValue {
// String values
DATADIR, MODE,
// Int Values
RESOLUTIONX, RESOLUTIONY, DEPTH, WINDOWED, SOUND, MUSIC,
ENUMCONFLENGTH
};

Configuration(std::string directory = "") {
// if we don't have a path, save the config in the same dir
if(directory == "") {
m_Cfg = "config.cfg";
}
else {
m_Cfg = directory + "/config.cfg";
}
// Then we'd do things like loading the configuration/creating a new one
}

void set(ConfigurationValue cfg, std::string value) {
m_ConfStr[cfg] = value;
}

void set(ConfigurationValue cfg, int value) {
m_ConfInt[cfg] = value;
}

std::string getStr(ConfigurationValue cfg) const {
return m_ConfStr[cfg];
}

int getInt(ConfigurationValue cfg) const {
return m_ConfInt[cfg];
}

bool save() {
std::cout < < "Saving (but not really) Configuration" << std::endl; std::cout << "datadir = " << getStr(DATADIR) << std::endl << "mode = " << getStr(MODE) << std::endl << "resolution_x = " << getInt(RESOLUTIONX) << std::endl << "resolution_y = " << getInt(RESOLUTIONY) << std::endl << "depth = " << getInt(DEPTH) << std::endl << "windowed = " << getInt(WINDOWED) << std::endl << "sound = " << getInt(SOUND) << std::endl << "music = " << getInt(MUSIC) << std::endl; return true; } bool load() { set(DATADIR, "/usr/local/game/some_game_dir"); set(MODE, "opengl"); set(RESOLUTIONX, 800); set(RESOLUTIONY, 600); set(DEPTH, 32); set(WINDOWED, 1); set(SOUND, 1); set(MUSIC, 1); return true; } private: std::string m_Cfg; std::string m_ConfStr[ENUMCONFLENGTH]; int m_ConfInt[ENUMCONFLENGTH]; };

This code should look quite trivial to anyone with a background in C++, but not so trivial that it didn't introduce some challenges in binding them with each library (explained later.) All this class is doing is mimicing what a real configuration class might do, and that is, store and load configuration values. In this case, the data isn't saved to any kind of persistance. For the sake of an example, that would only complitate things.

Here's the basic rundown of the code:

  • A constructor with an argument (in this case, one that doesn't do anything useful.)
  • An enumeration which is used to represent the values available in the configuration.
  • Getters and setters for ints and strings.
  • A Save function which prints out the configuration.
  • A load function which sets some defaults to the configuration values.
  • A few private variables to save the fake configuration path, and two arrays to store the configuration in memory.

Now, we can start to get an idea of what the binding libraries are going to need to handle.

  1. Constructors & overloaded constructors
  2. Enumerations (or at least being able to let the decay to ints)
  3. Member functions
  4. Overloaded member functions with differing parameters

So, let's start with Luabind.

using namespace luabind;

// assume s in defined as a lua_State*
open(s);

module(s)
[
class_<Configuration>("Configuration")
.def(constructor<std::string>())
.def("getStr", &Configuration::getStr)
.def("getInt", &Configuration::getInt)
.def("setInt", (void(Configuration::*)(Configuration::ConfigurationValue, int))&Configuration::set)
.def("setStr", (void(Configuration::*)(Configuration::ConfigurationValue, std::string))&Configuration::set)
.def("save", &Configuration::save)
.def("load", &Configuration::load)
.enum_("ConfigurationValue")
[
value("DATADIR", Configuration::DATADIR),
value("MODE", Configuration::MODE),
value("RESOLUTIONX", Configuration::RESOLUTIONX),
value("RESOLUTIONY", Configuration::RESOLUTIONY),
value("DEPTH", Configuration::DEPTH),
value("WINDOWED", Configuration::WINDOWED),
value("SOUND", Configuration::SOUND),
value("MUSIC", Configuration::MUSIC)
]
];

So lets go through the above requirements:

  1. Supported. Luabind looks to provide good support for constructors and multiple constructors.
  2. Supported. Again, Luabind has a built-in method for dealing with enumerations.
  3. Supported. This one should obviously be supported by any C++ Lua binding library worth a damn.
  4. Supported. As you can see, Luabind allows/requires you to tell it what the function will be called in Lua, thus allowing overloaded member functions without any issue.

Other thoughts: Luabind seems to have pretty good support for binding C++ classes, functions, and enums to Lua. The syntax for the binding seems quite clean and well thought out.

So now lets do a binding to the same class in OOLua

// I use the Varadic macros, otherwise, I'd have to give the length of each
// function/constructor definition (and also to the EXPORT* macros)
OOLUA_CLASS_NO_BASES(Configuration)
// most likely not really needed in our example here, but it forces the use
// of Configuration(std::string)
OOLUA_TYPEDEFS No_default_constructor OOLUA_END_TYPES
OOLUA_CONSTRUCTORS_BEGIN
OOLUA_CONSTRUCTOR(std::string)
OOLUA_CONSTRUCTORS_END

OOLUA_MEM_FUNC(bool, load)
OOLUA_MEM_FUNC(bool, save)
OOLUA_MEM_FUNC_CONST(std::string, getStr, Configuration::ConfigurationValue)
OOLUA_MEM_FUNC_CONST(int, getInt, Configuration::ConfigurationValue)
OOLUA_MEM_FUNC_RENAME(setInt, void, set, Configuration::ConfigurationValue, int)
OOLUA_MEM_FUNC_RENAME(setStr, void, set, Configuration::ConfigurationValue, std::string)
OOLUA_CLASS_END

EXPORT_OOLUA_FUNCTIONS_CONST(Configuration, getStr, getInt)
EXPORT_OOLUA_FUNCTIONS_NON_CONST(Configuration, load, save, setInt, setStr)

OOLUA::Script s;
s.register_class<Configuration>();

s.register_class_static<Configuration>("DATADIR", Configuration::DATADIR);
s.register_class_static<Configuration>("MODE", Configuration::MODE);
s.register_class_static<Configuration>("RESOLUTIONX", Configuration::RESOLUTIONX);
s.register_class_static<Configuration>("RESOLUTIONY", Configuration::RESOLUTIONY);
s.register_class_static<Configuration>("DEPTH", Configuration::DEPTH);
s.register_class_static<Configuration>("WINDOWED", Configuration::WINDOWED);
s.register_class_static<Configuration>("SOUND", Configuration::SOUND);
s.register_class_static<Configuration>("MUSIC", Configuration::MUSIC);

Again, we’ll go through my above requirements:

  1. Supported. OOLua too has support for multiple constructors.
  2. Supported. At the time I started this post, it was actually broken. After asking about it on the mailing list, a bug report was filed for it, and it was fixed shortly after.
  3. Supported. Again, this should obviously be provided by any C++ to Lua library.
  4. Supported. OOLua uses a different macro than what’s used to define normal functions.

Other thoughts: What I really like about OOLua is how fast the developer’s response time is for fixing bugs/issues. It’s also nice that it strives to be as fast as possible. I also really like the Script class. Script->run_file(string) just feels like a natural way to run a Lua script. OOLua works, and works well. That said, I’m not personally very fond of the syntax for declaring/exporting classes. Mostly, I’d like to, just as an example, not have to type OOLUA_ as a prefix for every macro.

For my pretty basic example both OOLua and Luabind support all four requirements. So I suppose you’re now asking yourself, “which is better?” Ultimately, I think Luabind supports using more of C++’s features than OOLua. For instance, I don’t believe OOLua supports overloaded operators, but Luabind does. Depending on the classes you’re binding, OOLua may not be able to fully handle it, and you may be better off with Luabind. In most cases, Luabind will probably work for you. OOLua should not be overlooked though. Even though it may not support quite as many things as Luabind does, what it does support, it supports well and is fast. I also believe there’s a lot of potential for OOLua. So go try them out for yourself.

I’ve included the full source to the above test bindings with a quick run of each. The code is licensed under the MIT liscense (just like Lua, Luabind, and OOLua.) It’s also only been tested on Linux. The makefile should work for any POSIX environment, but the source is should be easy enough to compile anywhere. Lua, Luabind, and OOLua all need to have their headers & libraries installed or put in the project path (or really anywhere) to be included by the compiler, as pointed out by Liam.

adventuresInLuaBinding.tar.bz2


5 thoughts on “Adventures in Lua Binding

  • Liam

    Firstly as you know I am the dev for OOLua and thank you for the comparison.

    I do not want to turn this into an A v’s B comment so I will try hard not to but I should note that Luabind does many things which OOLua does not, the reasons and merits for this I will not go into here.

    “That said, I’m not personally very fond of the syntax for declaring/exporting classes. Mostly, I’d like to, just as an example, not have to type OOLUA_ as a prefix for every macro.”
    The first part of this is fair comment but the library was designed for my usage and projects I was involved with at the time. To me it makes perfect sense what each of these macros are doing but I accept your criticism on that matter. You actually do not need to use the macros yet I supply them as it allows a standard way of using the library that another user can pick up on.

    “…not have to type OOLUA_ as a prefix for every macro.””
    I would disagree with the second part of the comment as the OOLUA_ prefix is a design decision which I always try to use and encourage. I personally think that any dev writing a macro should use this design for a number of reasons. The primary reasons are:
    It simply makes the macros origin known
    and attempts to make the macro unique (MSCV and max anyone?)

    “Lua, Luabind, and OOLua all need to have their headers & libraries installed. ”
    Actually this is not true for any of the libraries as they cab all be just contained in your project.
    Thanks.

    Reply
    • losinggeneration Post author

      “I would disagree with the second part of the comment as the OOLUA_ prefix is a design decision which I always try to use and encourage. I personally think that any dev writing a macro should use this design for a number of reasons. The primary reasons are:
      It simply makes the macros’ origin known
      and attempts to make the macro unique (MSCV and max anyone?)”
      You are quite correct. Because they are macros, I think you made the correct design decision to prefix them. Additionally, I’ve been trying to think of a good design to generate the proxy classes without lengthy macros and so far, haven’t been able to come up with a good alternative. I haven’t put terribly much thought into it though. I’m really not sure if macros were the only real choice here, or if there’s some other means to generate the proxy class. In any case, my comment was more about the macros themselves than the prefix.

      Reply
  • Liam

    “It simply makes the macros origin know as macros” should read ‘It simply makes the macros origin known’ *fixed*

    Damn you small comment boxes and no previews 🙂

    Reply
  • losinggeneration Post author

    “Damn you small comment boxes and no previews”
    Well, the comment box should actually be resizable (lower right corner),
    looks like it’s a Webkit only feature (and isn’t even enabled with this new theme)
    and it appears my WP theme is perhaps causing the comment preview from showing up… I’ll need to fix that.
    New theme does appear to fix this.

    Reply
  • Pingback: Tweets that mention Ramblings and Broken Code» Blog Archive » Adventures in Lua Binding -- Topsy.com

Speak Your Mind

Your email address will not be published. Required fields are marked *

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>