lua 给userdata设置元表_slua unreal分析(二)LuaActor与lua表互访

652f484da57b501baff436df80e8c05f.png

相关文章:

南京周润发:slua unreal分析(一)LuaActor概览​zhuanlan.zhihu.com
9d5db0d4dec4ab186e128b91b5ea95bb.png
南京周润发:slua unreal分析( 三)slua与GC​zhuanlan.zhihu.com
9d5db0d4dec4ab186e128b91b5ea95bb.png
南京周润发:slua unreal分析(四)LuaArray&LuaMap​zhuanlan.zhihu.com
9d5db0d4dec4ab186e128b91b5ea95bb.png

前文介绍了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中对应的表,因此理论上访问该表上的方法并不复杂。过程如下:

ef545f4051790e7893b4a4c297461d73.png

其中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访问流程如下:

52f58530e85935b77f604b537ea3d8f6.png

其中用到了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。

662e5a4c21370736c0a037c5600c12c0.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值