I have a basic event handler implemented in C++. I also have an embedded Lua interpreter in my application that I need to interact with the Event Manager. The ultimate goal is to be able to have one event handler that will execute both c++ and Lua functions when an event is fired.
My problem is that I can't come up with a simple way to store references to the lua functions in my C++ code. I know how to execute Lua functions from c (using lua_getglobal
and lua_pcall
), but I would prefer to store a reference to the function itself, so that I can pass a Lua function directly to registerListener
Note It is acceptable to assume that userdata will be NULL
for all Lua Listeners.
Here's my code:
EventManager.h
#include <string>
#include <map>
#include <vector>
using namespace std;
typedef void (*fptr)(const void* userdata, va_list args);
typedef pair<fptr, void*> Listener;
typedef map<string, vector<Listener> > CallbackMap;
class EventManager {
private:
friend ostream& operator<<(ostream& out, const EventManager& r);
CallbackMap callbacks;
static EventManager* emInstance;
EventManager() {
callbacks = CallbackMap();
}
~EventManager() {
}
public:
static EventManager* Instance();
bool RegisterEvent(string const& name);
void RegisterListener(string const &event_name, fptr callback,
void* userdata);
bool FireEvent(string name, ...);
};
inline ostream& operator<<(ostream& out, const EventManager& em) {
return out << "EventManager: " << em.callbacks.size() << " registered event"
<< (em.callbacks.size() == 1 ? "" : "s");
}
EventManager.cpp
#include <cstdarg>
#include <iostream>
#include <string>
#include "EventManager.h"
using namespace std;
EventManager* EventManager::emInstance = NULL;
EventManager* EventManager::Instance() {
if (!emInstance) {
emInstance = new EventManager;
}
return emInstance;
}
bool EventManager::RegisterEvent(string const& name) {
if (!callbacks.count(name)) {
callbacks[name] = vector<Listener>();
return true;
}
return false;
}
void EventManager::RegisterListener(string const &event_name, fptr callback,
void* userdata) {
RegisterEvent(event_name);
callbacks[event_name].push_back(Listener(callback, userdata));
}
bool EventManager::FireEvent(string name, ...) {
map<string, vector<Listener> >::iterator event_callbacks =
callbacks.find(name);
if (event_callbacks == callbacks.end()) {
return false;
}
for (vector<Listener>::iterator cb =
event_callbacks->second.begin();
cb != event_callbacks->second.end(); ++cb) {
va_list args;
va_start(args, NULL);
(*cb->first)(cb->second, args);
va_end(args);
}
return true;
}
luaw_eventmanager.h
#pragma once
#ifndef LUAW_EVENT_H
#define LUAW_EVENT_H
#include "EventManager.h"
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
void luaw_eventmanager_push(lua_State* L, EventManager* em);
int luaopen_weventmanager(lua_State* L);
}
#endif
luaw_eventmanager.cpp
#include <assert.h>
#include <stdio.h>
#include <sstream>
#include <iostream>
#include "luaw_eventmanager.h"
using namespace std;
static int
luaw_eventmanager_registerevent(lua_State* L)
{
int nargs = lua_gettop(L);
if (nargs != 2) {
return 0;
}
stringstream ss;
ss << luaL_checkstring(L, 2);
EventManager::Instance()->RegisterEvent(ss.str());
return 1;
}
static int
luaw_eventmanager_registerlistener(lua_State* L)
{
return 1;
}
static int
luaw_eventmanager_fireevent(lua_State* L)
{
return 1;
}
static int
luaw_eventmanager_tostring(lua_State* L)
{
stringstream ss;
ss << *EventManager::Instance();
lua_pushstring(L, &ss.str()[0]);
return 1;
}
static const struct luaL_Reg luaw_eventmanager_m [] = {
{"registerEvent", luaw_eventmanager_registerevent},
{"registerListener", luaw_eventmanager_registerlistener},
{"fireEvent", luaw_eventmanager_fireevent},
{"__tostring", luaw_eventmanager_tostring},
{NULL, NULL}
};
void
luaw_eventmanager_push(lua_State* L, EventManager* em)
{
EventManager** emUserdata = (EventManager**)lua_newuserdata(L, sizeof(EventManager*));
*emUserdata = em;
luaL_getmetatable(L, "WEAVE.mEventManager");
lua_setmetatable(L, -2);
}
int
luaopen_weventmanager(lua_State* L)
{
luaL_newmetatable(L, "WEAVE.mEventManager");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_register(L, NULL, luaw_eventmanager_m);
assert(!lua_isnil(L, -1));
return 1;
}
All Lua-owned objects are garbage-collected. This includes functions. Therefore, even if you could get a reference to a Lua function, Lua would still own it and thus it would be subject to the GC whenever Lua detects that it isn't being referenced anymore.
External code cannot own a Lua reference. But external code can store that reference in a place that Lua code cannot reach (and thus cannot break): the Lua registry.
The Lua registry is a Lua table (which is at stack pseudo-index LUA_REGISTRYINDEX
, so it's accessible from the stack) which Lua code cannot (directly) access. As such, it is a safe place for you to store whatever you need. Since it is a Lua table, you can manipulate it like any other Lua table (adding values, keys, etc).
However, the registry is global, and if you use other C modules, it is entirely possible that they could start stomping on each other's stuff. So it's a good idea to pick a specific registry key for each of your modules and build a table within that registry key.
Step one: when initializing your C interface code, create a table and stick it in a known key in the registry table. Just an empty table.
When the Lua code passes you a Lua function to use as a callback, load that table from the special key and stick the Lua function there. Of course, to do that, you need to give each registered function a unique key (which you store as the Lua function's void*
data), which you can later use to retrieve that function.
Lua has a simple mechanism for doing this: luaL_ref
. This function will register the object on the top of the stack with the table it is given. This registration process is guaranteed to return unique integer keys for each registered object (as long as you don't manually modify the table behind the system's back). luaL_unref
releases a reference, allo
Since the references are integer keys, you could just do a cast from int
to void*
and have that be the data. I would probably use an explicit object (malloc
ing an int
), but you can store it however you like.
Step two: when a Lua function is registered, use luaL_ref
to add it to the registry table created in step 1. Store the key returned by this function in the void*
parameter for the registered function.
Step three: when that function needs to be called, use the integer key you stored in the void*
parameter to access the registry table created in step 1. That will give you the function, which you can then call with the usual Lua methods.
Step four: when you are unregistering the Lua function, use luaL_unref
to release the function (thus you avoid leaking Lua's memory). If you malloc
ed memory to store the integer key, free
it here.
I would suggest to store your functions into the registry and use the reference mechanism provided by the functions luaL_ref
and luaL_unref
.
These functions use an C int
value to access the values. It is easy to store such an integer value in a C++ class member for example.
@Nicolas Bolas has provided nice instructions, but are too vague for newbies (including myself). Through trial and error I have come up with working example:
lua_newtable(L); // create table for functions
int tab_idx = luaL_ref(L,LUA_REGISTRYINDEX); // store said table in pseudo-registry
lua_rawgeti(L,LUA_REGISTRYINDEX,tab_idx); // retrieve table for functions
lua_getglobal(L, "f"); // retrieve function named "f" to store
int t = luaL_ref(L,-2); // store a function in the function table
// table is two places up the current stack counter
lua_pop(L,1); // we are done with the function table, so pop it
lua_rawgeti(L,LUA_REGISTRYINDEX,tab_idx); // retrieve function table
lua_rawgeti(L,-1,t); // retreive function
//use function
//don't forget to pop returned value and function table from the stack
lua_pop(L,2);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With