一直想解决这个问题,最近闲了点儿,就顺便完成了这个小模块。
最简单的c回调lua函数大家可能都比较熟悉了,对于调用lua中的全局函数,一个lua_getglobal和lua_call就行了,但是这需要宿主程序内写死lua中的函数名,耦合性太强,我正是因为以前实现的事件通知方式有问题才想换一种方法实现的。
后来,我又用了另外一种方式,即函数名注册方式,lua中写了一个函数,并将这个函数名注册给宿主,宿主在特定条件下调用该函数名的函数。这种方式解决了耦合性问题,但是使用不大方便,传入参数过多,并且字符串的匹配调用都有一点儿性能损耗。
后来想起cocos2dx内似乎使用了函数引用来调用lua中的函数,的确是一种不错的方法。宿主程序只要在特定事件的时候调用所有注册到该事件的函数,即可完成lua函数的调用,耦合性非常小,于是自己实现了一个简单的使用数字引用方式调用lua函数的方法。
核心思想即为在lua提供的注册表中添加一个table,该table的key就是所谓的lua函数所以,value就是lua函数的值,每次调用lua函数的时候,根据ref直接可以找到function,那么直接可以调用该lua函数了。
下面我们一步一步来。首先需要在全局的注册表中注册一个table。cocos2dx中使用了string来做key,这儿我根据云风大神的文章,使用了lightuserdata当key,这样key的索引时间会少一点儿,记住不能用number当key,这是被lua保留的。
//////////////////////////////////////////////////////////////////////////
// static light userdata key
static int s_nLuaFunctionRefKey = 0;
void* GetLuaFunctionRefKey()
{
return (void*)&s_nLuaFunctionRefKey;
}
// static lua function reference id
static int s_nLuaFunctionRefId = 0;
int GetLuaFunctionRefId()
{
return ++s_nLuaFunctionRefId;
}
在这里我们使用了一个静态变量的地址当做key,并且用了一个int当做ref的索引生成器。
下面我们就生成了一个table来记录ref和func。
void luaext_open(lua_State* L)
{
#ifdef _DEBUG
int nStackTop = lua_gettop(L);
#endif
// push key
lua_pushlightuserdata(L, GetLuaFunctionRefKey()); // stack: key
// push value
lua_newtable(L); // stack: key table
// set global table key
lua_rawset(L, LUA_REGISTRYINDEX); // stack:
#ifdef _DEBUG
assert(nStackTop == lua_gettop(L));
#endif
}
然后我们要实现的,就是注册lua函数到这个表中。也很简单,设置该table的值即可。
int luaext_registerLuaFunction(lua_State* L, int _nFuncIndex)
{
if(_nFuncIndex < 0)
{
return 0;
}
if(!lua_isfunction(L, _nFuncIndex))
{
return 0;
}
// push key
lua_pushlightuserdata(L, GetLuaFunctionRefKey()); // stack: func ... key
// get ref table
lua_rawget(L, LUA_REGISTRYINDEX); // stack: func ... reftable
if (!lua_istable(L, -1))
{
return 0;
}
// push new key
int nRefId = GetLuaFunctionRefId();
lua_pushnumber(L, nRefId); // stack: func ... reftable key
// copy function value from stack
lua_pushvalue(L, _nFuncIndex); // stack: func ... reftable key value
// set key
lua_rawset(L, -3); // stack: func ... reftable
// pop table
lua_pop(L, 1);
return nRefId;
}
既然做到了注册,那么也要做到反注册,也很简单,设置key=nil即可。
void luaext_unregisterLuaFunctionByRefId(lua_State* L, int _nRefId)
{
#ifdef _DEBUG
int nStackTop = lua_gettop(L);
#endif
// push key
lua_pushlightuserdata(L, GetLuaFunctionRefKey()); // stack: func ... key
// get ref table
lua_rawget(L, LUA_REGISTRYINDEX); // stack: func ... reftable
if (!lua_istable(L, -1))
{
return;
}
// push the key
lua_pushnumber(L, _nRefId); // stack: reftable key
// push nil
lua_pushnil(L); // stack: reftable key nil
// set nil
lua_rawset(L, -3);
// pop reftable
lua_pop(L, 1);
#ifdef _DEBUG
assert(lua_gettop(L) == nStackTop);
#endif
}
这个反注册是根据ref来反注册的,当然可能直接需要根据func来反注册,实现也很简单,遍历table,设置nil即可。
int luaext_unregisterLuaFunction(lua_State* L, int _nFuncIndex)
{
if(_nFuncIndex < 0)
{
return 0;
}
if(!lua_isfunction(L, _nFuncIndex))
{
return 0;
}
int nCount = 0;
// push key
lua_pushlightuserdata(L, GetLuaFunctionRefKey()); // stack: key
// get ref table
lua_rawget(L, LUA_REGISTRYINDEX); // stack: reftable
if (!lua_istable(L, -1))
{
return 0;
}
// push reftable index
int nStackTop = lua_gettop(L);
// push init key
lua_pushnil(L); // stack: reftable nil_key
while(lua_next(L, nStackTop))
{
// stack: reftable key value
if(lua_isfunction(L, -1))
{
// valid function
if(lua_equal(L, _nFuncIndex, -1))
{
#ifdef _DEBUG
int nTop = lua_gettop(L);
#endif
// set refFunc[key] = nil
// push key
lua_pushvalue(L, -2);
// push nil
lua_pushnil(L);
// stack: reftable key value key nil
lua_rawset(L, -5); // stack: reftable key value
#ifdef _DEBUG
assert(lua_gettop(L) == nTop);
#endif
++nCount;
}
}
// pop the value, and use the key to get the next key
lua_pop(L, 1); // stack: reftable key
}
#ifdef _DEBUG
assert(lua_gettop(L) == nStackTop);
#endif
return nCount;
}
做到了这些工作,我们使用的时候,就需要一个lua_getglobal类似的函数了。我们来实现一个getLuaFunctionByRefId来push要调用的函数到lua栈中。
int luaext_getLuaFunctionByRefId(lua_State* L, int _nRefId)
{
#ifdef _DEBUG
int nStackTop = lua_gettop(L);
#endif
// push key
lua_pushlightuserdata(L, GetLuaFunctionRefKey()); // stack: key
// get ref table
lua_rawget(L, LUA_REGISTRYINDEX); // stack: reftable
if (!lua_istable(L, -1))
{
return 0;
}
// push key
lua_pushnumber(L, _nRefId); // stack: reftable key
// get value
lua_rawget(L, -2); // stack: reftable (value or nil)
int nRetId = 0;
if(lua_isfunction(L, -1))
{
nRetId = _nRefId;
// stack: reftable value
lua_remove(L, -2); // stack: value(func)
#ifdef _DEBUG
assert(lua_gettop(L) == nStackTop + 1);
#endif
}
else
{
// pop reftable and nil
lua_pop(L, 2);
#ifdef _DEBUG
assert(lua_gettop(L) == nStackTop);
#endif
}
return nRetId;
}
上面就是核心功能的简单实现。上层实现的话各有各的思路,通过链表来保存所有注册事件的ref就行了,调用的时候调用全部的ref,这儿我也不细说了。然后就要把以前所用的各种lua_getglobal和注册函数名方式调用的函数都重构一下,又是个坑。