
相关文章:
南京周润发:slua unreal分析(一)LuaActor概览zhuanlan.zhihu.com


前文介绍了LuaActor,LuaActor在lua中有一个对应的表,这样就能用lua代码实现蓝图部分逻辑。我们可以通过在lua表中访问LuaActor的属性和方法,也可以在LuaActor中调用lua表的方法,实现相互访问。南京周润发:slua unreal分析( 三)slua与GC前文介绍了LuaActor,LuaActor在lua中有一个对应的表,这样就能用lua代码实现蓝图部分逻辑。我们可以通过在lua表中访问LuaActor的属性和方法,也可以在LuaActor中调用lua表的方法,实现相互访问。
LuaActor访问对应lua表
LuaActor有"CallLuaMember"方法,可以支持在C++中调用Lua定义的方法,需要提供方法名和参数列表,定义如下:
UFUNCTION
从定义可以看出,在蓝图中也能进行访问,比较方便。
之前提过,在LuaActor中存储了"luaSelfTable"变量,它就是lua中对应的表,因此理论上访问该表上的方法并不复杂。过程如下:

其中C++和lua间的数据传递,都会用LuaVar进行封装,用户代码直接使用LuaVar即可,不用关心gc之类的细节。
Lua表访问LuaActor
从Lua表访问LuaActor的属性和方法的场景通常更多一些。在蓝图中,可以访问UObject暴露给蓝图的变量和方法,在lua中也能实现类似的功能,而且个人觉得有以下两个优点:
- 可以访问所有UProperty,不限于BlueprintRead/Write标记
- lua中没有类型,属性方法获取均基于反射,不需像蓝图一样进行DynamicCast,代码更加精简
之前简单介绍过LuaActor如何把自己指针传递给lua表,现在需要详细分析一下里面的细节。
UserData
C++和lua是用栈进行数据传递的,当想把一个UObject指针传递给lua层时,slua会做一些中间处理,真正压入栈的是UserData数据结构,它记录了一些关于UObject的描述信息。lua可以使用UserData来索引LuaActor,而UE引擎可以使用UserData来进行正确的GC管理。
当LuaActor初始化,把自己指针压栈时,会执行到
return pushGCObject<UObject*>(L,obj,"UObject",setupInstanceMT,gcObject,ref);
这行代码,该方法会创建UserData并传递给lua。其中有三个参数比较重要,obj是该UObject的指针,setupInstanceMT是设置元表方法的指针,gcObject是该UserData被lua销毁时将会调用的方法。
setupInstanceMT真正实现了lua到UObject的属性/方法访问,关于lua元表的功能在此不赘述了,setupInstanceMT代码如下:
int LuaObject::setupInstanceMT(lua_State* L) {
lua_pushcfunction(L,instanceIndex);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L,newinstanceIndex);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, objectToString);
lua_setfield(L, -2, "__tostring");
return 0;
}
可以看到,元表中只设置了三个方法,"__index"实现值获取功能,而"__newindex"实现赋值功能。先看下"__index"方法的实现instanceIndex方法。
instanceIndex
我们在lua中写类似"self.name"的语句时就会用到此方法,会把self和name都压栈,然后instanceIndex方法就会根据这两个参数来获取name值。instanceIndex基于UE的反射机制工作,因此可以不限于BlueprintRead/Write标记,只要是UProperty和UFunction,都可以访问。一次instanceIndex访问流程如下:

其中用到了FuncCache做加速,因为通过反射访问UClass上的Function和Property比较慢。
newinstanceIndex
newindex方法用于修改LuaActor上property的值,比如"self.name=1",不过这里的property要求不能被标记为BlurprintReadOnly。方法与之前的index类似,都是先通过反射获取到property。获取到property后就能通过反射机制继续设置该property的值。
看到这里,就能大致了解lua表如何访问LuaActor属性和方法了,但是还有个小问题,就是以上的__index和__newindex都是UserData元表的方法,lua函数栈里面也只有UserData,但lua在遇到形如"self.name"的语句时只会把self(luatable)和name压入函数栈,那self是如何变成UserData的呢?这要回到LuaActor初始化的地方。
// setup metatable
if (!metaTable.isValid()) {
luaL_newmetatable(L, typeName);
lua_pushcfunction(L, __index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, __newindex);
lua_setfield(L, -2, "__newindex");
metaTable.set(L, -1);
lua_pop(L, 1);
}
metaTable.push(L);
lua_setmetatable(L, -2);
通过以上方式设置了lua表的metatable,其中包括了"__index"和"__newindex"方法。"__index"方法定义如下:
int LuaBase::__index(NS_SLUA::lua_State * L)
{
lua_pushstring(L, SLUA_CPPINST);
lua_rawget(L, 1);
if (!lua_isuserdata(L, -1))
luaL_error(L, "expect LuaBase table at arg 1");
// push key
lua_pushvalue(L, 2);
// get field from real actor
lua_gettable(L, -2);
return 1;
}
可以看到,它是先获取到__cppInst,即UserData,然后对_cppInst进行key获取,随之调用UserData元表上的"__index"方法。UserData起到了转发的作用。
综上,LuaActor和lua表的相互访问方式如下,绿线为LuaActor访问lua表,黄线为lua表访问LuaActor。
