I’ve been poking around with a couple C++ programs recently and I started thinking again about how cool it is that you can hook in a scripting language. I’ve wanted to try setting up scripting for a few programs in the past but it always seems too far removed from any one project. This sounds kind of abstract, more concretely I’ve been thinking about scripting behaviours in a game or exposing a script interface from some simple graphics program.
Lua’s a popular scripting language for games. It’s used for add-ons in World of Warcraft and I’ve heard it brought up over and over again at the Game Developer’s Conference. I tried a short example of Lua a while back but I never went back to write any real code with Lua. Since the license is so liberal and writing a little Lua would help me out writing WoW macros and add-ons, I decided Lua would be a good place to start - have a look at yesterday's post about setting up an SDL project in Visual Studio.
Next for the test harness. I took some code to draw a Hilbert curve from Wikipedia about a week ago and converted it into something roughly equivalent in C++ using SDL. The code compiles and runs under Linux (I used KDevelop) or Visual Studio 2003. In the code I implemented a SimpleGraphics class which takes an SDL surface and gives some methods to draw lines on that surface. The HilbertCurve class uses a SimpleGraphics object to do the drawing. I decided this little program would be a great candidate to get scripted.
So in one hand I have the Lua interpreter, source code, libraries and whatever else. In the other hand I have my little SDL program. Now what? I need to make some kind of binding that will expose functions from my program to the Lua interpreter. It seems like I should be able to do this a couple of ways. One way would be to make a dynamically loadable library (.so or .dll) out of the SimpleGraphics class. The SimpleGraphics library could then be loaded by a standalone Lua interpreter and the functions would be available to call from Lua. The second way, and this makes more sense in my situation, is to embed the Lua interpreter in my C++ program. Then that C++ host application will have to get a Lua script from somewhere and run it through the embedded Lua interpreter.
I’m choosing the second option. To do this as I understand it I’ll need to link with the Lua library and load it up in my program. There's something in the SWIG documentation that has a little sample of code that will load up a Lua script and execute it. I adapted this to my program, removing the custom functionality.
The important snippets look like this
And all simple.lua has in it is this
So as you can see all I want to do is load up the Lua interpreter that I'm building and spit out a couple lines to show it's alive. I try to rely on simple building steps like this because it reduces build headaches drastically. So my steps were first to build an SDL C++ application, then add the Lua interpreter source and build that, then test with this simple Lua two-liner.
At this point I should probably stop and clean up my code then make sure it still builds properly on both my platforms (I haven't got the Linux build happy since I added Lua). But what the heck, I really want to add a function to Lua and see how that works.
As it turns out it's not too hard at all to add one function. A C/C++ function you want to call from Lua has to take a lua_State * and return an int. I used the example at in the Lua manual to try it out. I added the prototype at the top of my code
In main(), after loading calling Lua_open() and luaopen_base() I registered my function like so:
The lua_register() call give the function the name sumsquares() in Lua and hooks that up to the function sumsq() in my C++ code. My sumsq() function is just a minor variation on the averaging function from the Lua manual that I linked to earlier.
Registering just one function is pretty easy but the functions that are exposed to Lua have to follow that particular protocol for getting data in and out. I also imagine things get more difficult with more complex data types. I imagine that things get much more complex in bigger projects and that's confirmed by the long list of tools to bind apps to Lua. When I started looking at this I wanted to grab a tool like one of these right away but I didn't understand enough to use one. That'll be my next step though.