これを完全に実装するには、おそらく、Luaのより難解なビットのいくつかを少し掘り下げる必要があります。しかし、それは時間の価値があります。私がこれをどのように処理したかについて、非常に縮小されたバージョンを示します。注意してください、ここではすべてが一緒に機能する小さなビットがたくさんあります-主に保存された関数の保存と呼び出し、およびLuaユーザーデータとしてのc++オブジェクトの使用。
まず、単純なintであるlua参照としてイベントハンドラー(lua関数)を格納するc++クラスが必要です。ここでは配列を使用していますが、意味のあるものなら何でも使用できます。ここで起こっている主なことは、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);
}
}
};
次に、EntityクラスをLuaに公開します。これがどのように行われるかをよく知らない場合は、ここに良い記事があります。基本的に何が起こっているのかというと、Entity.new()から返されたユーザーデータをポインターへのポインターに設定しているということです。これは、Luaがオブジェクトをガベージコレクションしないようにするためです。次に、Luaに公開されるすべてのメソッドを保持する「Entity」のメタテーブルを作成します。
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;
}
これは完全にはほど遠いです。まず、イベントハンドラーを2回設定する必要がある場合は、luaL_unrefを使用して最初に古い参照をクリアする必要があります。次に、発生したイベントに関するデータをイベントハンドラーに渡します。現在のイベントハンドラーはデータを取得しないため、APIのユーザーはほとんど先に進むことができません。おそらく、少なくともイベントを呼び出しているオブジェクトへの参照を渡す必要があります。これを使用して、Luaで非常に強力で使用可能なApiを作成できます。幸運を!