Author Topic: Concise Lua Binding  (Read 795 times)

hailstone

  • Local Moderator
  • Battle Machine
  • ********
  • Posts: 1,568
    • View Profile
Concise Lua Binding
« on: 17 January 2011, 21:30:37 »
I've been looking at ways to bind a C++ function so it can be called from Lua. Instead of needing to create a static function and manually organise the lua arguments to call a C++ method I've been able to reproduce the same thing in two lines. I'm not sure how this method compares with the binding libs but this is small code and can be customised to fit in with GAE.

Here's an example of the usage:

Code: [Select]
class Gui {
public:
void displayText(string text) {
cout << text << "\n";
}

static Gui* getInstance() {
if (!thisPtr)
thisPtr = new Gui();
return thisPtr;
}

private:
lua_State* L;
static Gui *thisPtr;
};

Gui *Gui::thisPtr = 0;

class ScriptManager {
public:
void setState(lua_State* state) { L = state; }

static ScriptManager* getInstance() {
if (!thisPtr)
thisPtr = new ScriptManager();
return thisPtr;
}

void registerLuaFunctions() {
lua_register(L, "Gui_displayText", displayText);
}

private:
lua_State* L;
static ScriptManager *thisPtr;

// Lua Call Bindings
mbind_noret(Gui, displayText, string)
};

ScriptManager *ScriptManager::thisPtr = 0;

In this example mbind_noret (method bind with no return value) macro is called to setup the lua arguments and the static luafunc for displayText in Gui. Then lua_register registers the static function. At the moment classes with methods to bind need to have getInstance method and be a singleton. I'll post the other code when I'm not so tired.
Glest Advanced Engine - Admin/Programmer
https://sourceforge.net/projects/glestae/

silnarm

  • Local Moderator
  • Behemoth
  • ********
  • Posts: 1,373
    • View Profile
Re: Concise Lua Binding
« Reply #1 on: 19 January 2011, 11:11:42 »
So, hows this all work then??

I'm assuming this 'mbind_noret' macro expands to the static function that is registered? How are the parameters handled?

A very interesting idea, but do we lose all the nice error reporting? (wrong number of args or bad arg type).

If we can make this work, I think we can probably adapt for entities too, they all have factories now, and they all have ids, so from an id we can get the instance from the appropriate factory, which would replace the Class::getInstance() for singletons.
Glest Advanced Engine - Code Monkey

Timeline | Downloads

hailstone

  • Local Moderator
  • Battle Machine
  • ********
  • Posts: 1,568
    • View Profile
Re: Concise Lua Binding
« Reply #2 on: 20 January 2011, 07:28:50 »
Quote
So, hows this all work then??

Code: [Select]
#define mbind_both(obj, ret, name, ...) \
typedef ret (obj::*name_both) (__VA_ARGS__); \
static int name(lua_State *L) { \
return mcall<obj, name_both, ret, __VA_ARGS__>(L, *obj::getInstance(), &obj::name);\
}
The macro calls a function template determined by the number of arguments. This one has a return and arguments; the ones with no return need to be separate. It creates a method pointer based on this information and passes it as a template argument. The function takes the lua state, an object of the class (this could be a bit restrictive), and a reference to the method which conforms to the method pointer format.

Code: [Select]
template<typename Obj, typename F, typename ret, typename T>
static int mcall(lua_State *L, Obj &o, F func) {
int i = 0;
T one = luaGet<T>(L, ++i);
return luaReturn<ret>(L, CALL_MEMBER_FN(o,func)(one));
}
There are several function templates in this format which vary the number of argument types. I've done 5 so far (I don't think we would need many more). Each type is passed as an argument to another function template which returns the desired type each time incrementing the index. The member function is called with the given arguments and the result passed to luaReturn function template. I think the number of arguments could be checked in these functions.

Code: [Select]
template<typename T>
T luaGet(lua_State *L, int ndx);

template<>
int luaGet<int>(lua_State *L, int ndx) {
return luaL_checkint(L, ndx);
}
Each type is determined by a template specialization. Error checking could be added here and used as a wrapper for other types like LuaScript::getStringSet. The returns work in a similar way.

Code: [Select]
template<typename T>
int luaReturn(lua_State *L, T val) {return 0;}

template<>
int luaReturn<int>(lua_State *L, int val) {
lua_pushinteger(L, val);
return 1;
}
The number of values returned to lua is returned by the function.

The full code.

Quote
but do we lose all the nice error reporting? (wrong number of args or bad arg type).
At the moment yes, but I think it can be added in.

Quote
If we can make this work, I think we can probably adapt for entities too, they all have factories now, and they all have ids, so from an id we can get the instance from the appropriate factory, which would replace the Class::getInstance() for singletons.
Yer, any way to get an object should work.

A problem is you can only use it for one overloaded method since it uses the name for the static lua function. I looked at MLuaBind which is like LuaBind but uses a lightweight Boost-like library. It uses a dispatch system. I could probably get a simple method dispatcher working so we could register in one call and not need the macros. I'm reading through Programming in Lua now.

From MLuaBind:
Code: [Select]
int GenericClass::LuaMethodCallProxieFunction(lua_State *L)
{
LuaCustomVariable *lcv = (LuaCustomVariable *)lua_touserdata(L, lua_upvalueindex(1));
const char *name = (const char*)lua_touserdata(L, lua_upvalueindex(2));
const GenericClass *gc = lcv->getclass();
assert(gc != 0);
MethodsT::const_iterator i = gc->m_Methods.find(name);
if(i != gc->m_Methods.end())
{
return lcv->getclass()->MethodCallDispatcher(CHost::GetFromLua(L), L, lcv, name, (*i).second);
}
else
{
CHost::GetFromLua(L)->Error("GenericClass::LuaMethodCallProxieFunction: panic - can't find GenericMethod %s!\n", name);
return 0;
}
};

int GenericClass::MethodCallDispatcher(CHost *_host, lua_State *L, LuaCustomVariable *_lcv, const char *_name, const std::vector<GenericMethod*> &_overloads) const
{
...
return overloads[0]->PerformCall(_host, L, _lcv);
};


Update 23 January:
I have one line method registration working. I haven't bothered with functions this time. It still requires the class to be static and have getInstance method. Error checking still needs to be implemented. The id of methods is currently the name so that will need to change to be unique.

Code: [Select]
ScriptManager sm(L);
sm.register_method("display", &Gui::display);

Overloaded methods will need to use their full prototype.

The full code of v2.
« Last Edit: 23 January 2011, 03:05:02 by hailstone »
Glest Advanced Engine - Admin/Programmer
https://sourceforge.net/projects/glestae/