Lua scripting with SWIG - Calling a Lua function from C++ and passing parameters to it

posted in owl's Blog
Published April 04, 2011
Advertisement
As I said in my last entry SWIG is a very easy and straightforward way of exposing your C++ functions, data structures and instances to Lua scripts. If you're not interested in learning the prestidigitation that occurs behind such an act SWIG is the library you're looking for.

Yet not everything in life is easy. There always seems to be a catch and this time the catch is that while SWIG is great for letting you access your stuff from Lua it doesn't seem to provide an easy way of calling your Lua stuff from C++. So at the end, you gotta get into the realm of lualib and find a solution for that.

For the moment I belong to the group of guys not really interested in learning to do by themselves what SWIG does without their help, so I tried to keep my incursion into lualib as simple as possible. As I mentioned in the last entry I resorted to a sort of functor from which I inherit each time I need to pass different values to the Lua function I need to call from C++. This is the base class that does the general stuff:

class callback
{
public:
void init(owl::script::script& s, const std::string& obj, const std::string& func)
{
L = s.L;
lua_getglobal(L, obj.c_str()); // Get the table instance
r_obj = luaL_ref(L, LUA_REGISTRYINDEX);

lua_rawgeti(L, LUA_REGISTRYINDEX, r_obj); // Get the object instance
lua_getfield(L, -1, func.c_str()); // Get the function
r_func = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pop(L, 1);
}

void init(owl::script::script& s, const std::string& func)
{
L = s.L;

lua_getglobal(L, func.c_str()); // Get the function
r_func = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pop(L, 1);
}

virtual ~callback()
{
luaL_unref(L, LUA_REGISTRYINDEX, r_obj);
luaL_unref(L, LUA_REGISTRYINDEX, r_func);
}

void call()
{
lua_rawgeti(L, LUA_REGISTRYINDEX, r_func);

int s;
if( (s=lua_pcall(L,0,0,0)) != 0)
report_errors();
}

protected:
void report_errors()
{
std::cout << "-- " << lua_tostring(L, -1) << std::endl;
lua_pop(L, 1); // remove error message
}

int r_obj;
int r_func;
lua_State *L;
};



The improvement in relation to the last version is that now I'm keeping an index to the Lua function I want to call instead of the string that represents the function's name. I also wrote a wrapper for the Lua State (the running script) and now I give the callback a pointer to it so it knows where to store the index I'll need to call later. The wrapper for the lua script is actually really simple:

class callback;

class script
{
friend class callback;
public:
bool init();
bool init(script* s);
bool load(const std::string& file_path);
void close();

protected:
void report_errors(lua_State *L);

lua_State *L;
};

bool script::init()
{
// Initialize lua
L = lua_open();
luaL_openlibs(L);
luaopen_owl(L); // load the wrappered module
return true;
}

bool script::init(script* s)
{
L = s->L;
}

void script::close()
{
lua_close(L);
}

bool script::load(const std::string& file_path)
{
// Load script
int s = luaL_loadfile(L, file_path.c_str());

// execute Lua program
if ( s==0 )
{
s=lua_pcall(L, 0, LUA_MULTRET, 0);

if (s!=0)
{
report_errors(L);
return false;
}
}
else
{
report_errors(L);
return false;
}

return true;
}

void script::report_errors(lua_State *L)
{
std::cout << "-- " << lua_tostring(L, -1) << std::endl;
lua_pop(L, 1); // remove error message
}


For the script to be able to tell C++ which lua_State to use for storing the reference to the Lua function I needed to have a way of getting the lua_State from C++. The only way I could come up with so far was to make the app instance global, writting a function for getting that instance and having a reference to the lua_State in it (it's actually wrapped in it's own class) so I can access it from the script later.


In short, this:
app* the_app = new app();

app* get_app()
{
return the_app;
}


class app
{
public:
owl::script script;
};



So for the sake of an example I want to be able to create an UI button from Lua and call a Lua function each time the button gets pressed.

I've inherited a class from the callback class that has a string as a member that is going to hold the value I want to pass back to the script when calling the Lua function. In this example the delegate I'm calling doesn't have parameters so I'm making up the value in the constructor. I could just as easily have gotten it from the delegate. I just don't wanna right now OK? :)

class callback_button : public callback
{
public:
void register_click(owl::render::gui::button_ptr obj)
{
obj->click += fd::delegate(&callback::call,this);
}

callback_button() {value = "hola";}

std::string value;
};
typedef callback_button button_callback_t;
typedef boost::shared_ptr callback_button_ptr;



I also wrote a couple of helper functions to create instances of the button and the callback:

static callback_button_ptr create_callback_button()
{
return callback_button_ptr(new callback_button());
}


static boost::shared_ptr create_control(owl::render::gui::object_ptr parent)
{
boost::shared_ptr ctrl = boost::shared_ptr(new T);
if (parent) parent->add(ctrl);
return ctrl;
}


Then the infamous interface file SWIG needs in order to write C++ for your (my actually) lazy arse:

%module owl
%{
#include "gui.h"
// and any other required headers
%}
%include
%include "gui.h"

%template(button_ptr) boost::shared_ptr;
%template(create_button) create_control;
%template(btn_callback_ptr) boost::shared_ptr;
// and all the data types you wanna interface with Lua


Then you run from the command prompt:
> swig -c++ -[INTERFACE_FILE_NAME]

The resulting cpp file should be part of your project. Then you build your app.

The lua script is quite simple:
-- // Get the app instance
app = owl.get_app()

-- // Declare the function we want to call from C++
function btn_new_game_click()
print (btn_new_game.handler.value)
end

-- // Create Button New Game
btn_new_game = {}
btn_new_game.btn = owl.create_button(app.gui.root)

-- // Create the callback
btn_new_game.handler = owl.create_callback_button()
btn_new_game.handler:init(app.script, "btn_new_game_click")
btn_new_game.handler:register_click(btn_new_game.btn)


And that's it. Of course this might seem a little convoluted for some programmers, but it works.

Any questions or comments let me know.

Beam me up, Scotty! *WOOSHHHHHES*
0 likes 1 comments

Comments

owl
85% of the time Spanish words share the root with their English counterparts. 15% of the time I end up looking like Jesus the janitor :D
April 05, 2011 06:40 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement