Starting a Simple Example using Lua and SDL in C++

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

extern "C"
{
  #include "lua.h"
  #include "lualib.h"
  #include "lauxlib.h"
}
...
#define LUA_FILE "C:\\dev\\hilbert\\scripts\\simple.lua"
...
struct lua_State {};
...
  lua_State *L;
  L=lua_open();
  luaopen_base(L);
  if (luaL_loadfile(L,LUA_FILE)==0) // load and run the file
    lua_pcall(L,0,0,0);
  else
    printf("unable to load %s\n",LUA_FILE);
  lua_close(L);
...

And all simple.lua has in it is this

print("Hi")
for i = 1,3 do print(i) end

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

static int sumsq(lua_State *L);

In main(), after loading calling Lua_open() and luaopen_base() I registered my function like so:

  lua_State *L;
  L=lua_open();
  luaopen_base(L);      // load basic libs (eg. print)
  lua_register(L,"sumsquares",sumsq);
  if (luaL_loadfile(L,LUA_FILE)==0) // load and run the file
    lua_pcall(L,0,0,0);
  else
    printf("unable to load %s\n",LUA_FILE);
  lua_close(L);

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.

static int sumsq(lua_State *L) {
  int n = lua_gettop(L);    /* number of arguments */
  lua_Number sum = 0;
  lua_Number sumsquares = 0;
  int i;
  int currentArg;
  for (i = 1; i <= n; i++) {
    if (!lua_isnumber(L, i)) {
      lua_pushstring(L, "incorrect argument to function 'sumsquares'");
      lua_error(L);
    }
        currentArg = lua_tonumber(L, i);
    sum += currentArg;
    sumsquares += currentArg * currentArg;
  }
  lua_pushnumber(L, sum);        /* first result */
  lua_pushnumber(L, sumsquares); /* second result */
  return 2;                   /* number of results */
}

This will return the sum of the numbers passed in as the first return value and the sum of the squares of those numbers (useful for some stats) in the second return value. Then I modified my simple Lua script to call the new function.
print("Hi")
print(sumsquares(1,2,3,4,5))
print(sumsquares(10,20,30,40,50))

Predictably this prints out
Hi
15      55
150     5500

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.

2.97546
Your rating: None Average: 3 (163 votes)