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!