要以您可能想要的方式完全实现这一点,需要深入研究 Lua 的一些更深奥的部分。不过,这很值得。我将展示一个非常精简的版本,说明我是如何处理这个问题的。请注意,这里有很多小部件一起工作 - 主要是保存和调用保存的函数以及使用 c++ 对象作为 Lua 用户数据。
首先我们需要一个 c++ 类,它将事件处理程序(lua 函数)存储为简单 int 的 lua 引用。我在这里使用了一个数组,但你可以使用任何有意义的东西。这里发生的主要事情是您希望能够调用 int 引用所引用的 lua 函数。
#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);
}
}
};
现在您想向 Lua 公开您的 Entity 类。如果您不熟悉这是如何完成的,有一篇很好的文章 here。本质上,我们正在将 Entity.new() 返回的用户数据设置为指向指针的指针。这样 Lua 就不会垃圾收集你的对象。然后为“实体”创建一个元表,它将
保存所有暴露给 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}
};
现在设置 Lua 状态并创建实体表并创建测试。该测试将创建一个实体并将其事件处理程序设置为传递给它的 Lua 函数。最后测试一下 Lua 函数是否被 c++ 对象调用。
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;
}
这远未完成。首先,如果您需要设置一个事件处理程序两次,您需要先使用 luaL_unref 清除旧的引用。其次,您可能希望将有关发生的事件的一些数据传递给事件处理程序。当前的事件处理程序不获取任何数据,因此 api 的用户几乎不需要继续操作。它可能至少应该传递对调用事件的对象的引用。这可用于在 Lua 中创建非常强大且可用的 API。祝你好运!