toujamaru
toujamaru

Reputation: 111

How should I bind lua functions to C++ functions?

I have a class called Entity, which has many functions like onPickup, onDrop, onUse etc. What I want to do is, write a script that defines all of these functions and make them callable from the C++ functions. So the functions defined in C++ would just be calling their corresponding Lua functions that have some functionality.

But here's the problem, I want every script that I write, for every Entity in the program to be working in it's own scope.

I'm using LuaBind, and I have no prior experience with Lua, so I'm a little lost here.

Upvotes: 2

Views: 3014

Answers (3)

Nick Van Brunt
Nick Van Brunt

Reputation: 15484

To fully implement this the way you will probably want to will require digging around a bit in some of the more esoteric bits of Lua. It is well worth the time though. I'll show a very trimmed down version of how I have handled this. Be warned, there are a lot of little bits all working together here - mainly saving and calling saved functions and using c++ objects as Lua user data.

First we need a c++ class which will store events handlers (lua functions) as lua references which are simple ints. I am using an array here but you could use whatever makes sense. The main thing happening here is that you want to be able to call a lua function which is referred to by the int reference.

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include <string>
#include <iostream>
#include <assert.h>
using namespace std;

enum enum_event_types {
    ON_USE, ON_DROP, ON_WHATEVER, EVENT_COUNT
};

class Entity {
  private:  
    int events[EVENT_COUNT];
  public: 
    lua_State* lua;
    void setEventHandler(int event, int ref) {
        assert(event < EVENT_COUNT);
        events[event] = ref;
    }
    void callEventHandler(int event) {
        int error;
        assert(event < EVENT_COUNT);
            // to call the function we need to get it from the registry index
        lua_rawgeti(lua, LUA_REGISTRYINDEX, events[event]);
        error = lua_pcall(lua, 0, 0, 0); // use protected call for errors
        if (error) {
            printf("error: %s", lua_tostring(lua, -1));
            lua_pop(lua, 1); 
        }
    }
};

Now you want to expose your Entity class to Lua. If you are not familiar with how this is done there is a good article here. Essentially what is going on is that we are setting the user data returned from Entity.new() to a pointer to a pointer. This is so Lua does not garbage collect your object. Then create a meta table for "Entity" which will hold all of the methods exposed to Lua.

int L_newEntity(lua_State* L) {
    Entity **e = (Entity **)lua_newuserdata(L, sizeof(Entity *));
    *e = new Entity(); 
    (*e)->lua = L;
    lua_getglobal(L, "Entity");
    lua_setmetatable(L, -2); 
    return 1;
}

int L_setOnUse(lua_State* L) {
    Entity** e = (Entity**) lua_touserdata(L, 1);
    lua_pushvalue(L, 2);
    int ref = luaL_ref(L, LUA_REGISTRYINDEX);
    (*e)->setEventHandler(ON_USE, ref);
    return 0;
}

// this will be exposed to Lua as a table called Entity
static const luaL_Reg L_entityMethods[] = {
    {"new", L_newEntity},{"setOnUse", L_setOnUse},{NULL, NULL}
};

Now set up the Lua state and create the Entity table and create a test. The test will create an Entity and set its event handler to a Lua function passed to it. Finally test that the Lua function is being called by the c++ object.

int main() {
    Entity** e;
    int error;
    lua_State* L=lua_open();
    luaL_openlibs(L);
    luaL_register(L, "Entity", L_entityMethods); 
    lua_pushvalue(L,-1);
    lua_setfield(L, -2, "__index"); 
    lua_pop(L, 1);

    luaL_loadstring(L, 
    "e = Entity.new(); "
    "e:setOnUse(function()"
    "   print('Some of them want to use you')"
    "end);");
    error = lua_pcall(L, 0, 0, 0);
    if (error) {
        printf("error: %s", lua_tostring(L, -1));
        lua_pop(L, 1); /* errors must be popped from stack */
    }   
    lua_getglobal(L, "e");
    if (lua_isuserdata(L, 1)) {
        e = (Entity**) lua_touserdata(L, 1);
        (*e)->callEventHandler(ON_USE);
    }
    return 0;
}

This is far from complete. First of all if you ever need to set an event handler twice you will need to use luaL_unref to clear out the old reference first. Second you will probably want to pass some data about the event which occurred to the event handler. The current event handler does not take any data so gives the user of the api very little to go on. It should probably at least pass a reference to the object which is calling the event. This can be used to create very powerful and usable Apis in Lua. Good luck!

Upvotes: 0

jarmond
jarmond

Reputation: 1382

You can call a Lua function with, e.g.

int callLuaFunction(lua_State* lua_state) {
    return luabind::call_function<int>(lua_state, "myluafunction", param1);
}

if the Lua function returns an int and takes 1 parameter.

I'm pretty sure you can make as many lua_State's as you want. Just pass the correct one for the entity into call_function.

Upvotes: 1

daven11
daven11

Reputation: 3035

I don't use lua bind but this may help. The idea is to register the lua functions in your C++ class and keep a reference to the lua function in your C++ class.

To define a lua function that is callable from C/C++ I use luaL_ref to store a reference to the callback function in my C++ object.

// a little helper function
template <typename T>
T *Lua_getUserData(lua_State *L) {
    assert(lua_isuserdata(L, 1) == 1);
    T **v = (T **) lua_touserdata(L, 1);
    assert(v != NULL);
    return *v;
}

int lua_FormRegisterMethods(lua_State *L) {
    Entity *f = Lua_getUserData<Entity>(L);
    assert(lua_istable(L, 2) == 1); // check the next parameter is a table
    lua_pushvalue(L,2); // dup the table
    f->LuaTable = luaL_ref(L, LUA_REGISTRYINDEX); // keep a reference to the table
    lua_getmetatable(L, 2); // get the metatable
    lua_pushstring(L, "OnClick"); 
    lua_rawget(L, -2); // get the OnClick Lua Function
    f->LuaMethod = luaL_ref(L, LUA_REGISTRYINDEX); // save a reference to it
    return 0;
}

and then you can get the lua method in your C++ event

lua_rawgeti( LuaInstance->L, LUA_REGISTRYINDEX, LuaMethod );
assert(lua_isfunction(LuaInstance->L, -1) == 1);

now you can call this function with self set to the table you saved earlier. hth

Upvotes: 3

Related Questions