|
上一节我们分析了tolua的反射机制,它可以在事先没有wrap的情况下依旧可以调用到C#的接口。本节我们来探讨一下tolua内部传递struct的机制,我们知道C#的struct是值类型,因此在push struct到lua层,或者从lua层读取struct到C#层,都不但愿发生装箱或者拆箱的行为。tolua使用了一种相对简单但巧妙的方式来解决此问题,来看下example 24,这个例子展示了,新增了一种struct类型,需要在C#层和lua层各做些哪些事情,来避免装箱或者拆箱的同时,还能顺利地访谒数据。
首先C#层需要定义一个新的暗示LuaValueType的类型:
public partial struct LuaValueType
{
public const int Rect = 13;
}
这是一个partial类,tolua默认已经定义了如下的value types:
public partial struct LuaValueType
{
public const int None = 0;
public const int Vector3 = 1;
public const int Quaternion = 2;
public const int Vector2 = 3;
public const int Color = 4;
public const int Vector4 = 5;
public const int Ray = 6;
public const int Bounds = 7;
public const int Touch = 8;
public const int LayerMask = 9;
public const int RaycastHit = 10;
public const int Int64 = 11;
public const int UInt64 = 12;
public const int Max = 64;
}
然后,lua层需要实现一个与C# Rect功能类似的类:
Rect = {}
function Rect.New(x,y,w,h)
local rt = {x = x, y = y, w = w, h = h}
setmetatable(rt, Rect)
return rt
end
function Rect:Get()
return self.x, self.y, self.w, self.h
end
Rect.__tostring = function(self)
return '(x:'..self.x..', y:'..self.y..', width:'..self.w..', height:'..self.h..')'
end这么做的原因是在lua层使用struct的方式时,不需要跑到C#层去调用C#的接口,对于struct来说,它往往只是一个保留数据的布局,包含的方式都斗劲简单,lua层去实现完全可以胜任。
同样地,lua层也需要新增一个与C#一致的value type:
AddValueType(Rect, 13)
这个lua函数定义在ValueType.lua,该lua还包含了其他预先定义的value type:
local ValueType = {}
ValueType[Vector3] = 1
ValueType[Quaternion] = 2
ValueType[Vector2] = 3
ValueType[Color] = 4
ValueType[Vector4] = 5
ValueType[Ray] = 6
ValueType[Bounds] = 7
ValueType[Touch] = 8
ValueType[LayerMask] = 9
ValueType[RaycastHit] = 10
ValueType[int64] = 11
ValueType[uint64] = 12
function AddValueType(table, type)
ValueType[table] = type
end下一步就是需要措置C#将struct push到lua层,从lua层获取数据转换为C#的struct,以及对lua层数据进行类型查抄的工作:
StackTraits<Rect>.Init(PushRect, CheckRectValue, ToRectValue); //撑持压入lua以及从lua栈读取
TypeTraits<Rect>.Init(CheckRectType); //撑持重载函数TypeCheck.CheckTypes
TypeTraits<Nullable<Rect>>.Init(CheckNullRectType); //撑持重载函数TypeCheck.CheckTypes
LuaValueTypeName.names[LuaValueType.Rect] = ”Rect”; //CheckType掉败提示的名字
TypeChecker.LuaValueTypeMap[LuaValueType.Rect] = typeof(Rect); //用于撑持类型匹配查抄操作
ToLua.ToVarMap[LuaValueType.Rect] = ToRectTable; //Rect作为object读取
ToLua.VarPushMap[typeof(Rect)] = (L, o) => { PushRect(L, (Rect)o); }; //Rect作为object压入例子中的注释已经写的很清楚了,但为了更深入地弄懂此中的细节,我们来看看实际运行时,上述的这些代码是如何调用的。例子中的测试代码如下:
LuaFunction func = luaState.GetFunction(”PrintRect”);
func.BeginPCall();
func.PushValue(new Rect(10, 20, 120, 50));
func.PCall();
Rect rt = func.CheckValue<Rect>();
func.EndPCall();显而易见,我们存眷的重点就是PushValue和CheckValue两个函数上了,先来看PushValue,它会调用到:
public void PushValue<T>(T v) where T : struct
{
StackTraits<T>.Push(L, v);
}
而在此之前我们调用了StackTraits的Init函数,将对应Rect类的PushRect, CheckRectValue, ToRectValue这三个函数都绑定到了StackTraits上,那么这里调用的即为PushRect:
void PushRect(IntPtr L, Rect rt)
{
LuaDLL.lua_getref(L, NewRect.GetReference());
LuaDLL.lua_pushnumber(L, rt.xMin);
LuaDLL.lua_pushnumber(L, rt.yMin);
LuaDLL.lua_pushnumber(L, rt.width);
LuaDLL.lua_pushnumber(L, rt.height);
LuaDLL.lua_call(L, 4, 1);
}
NewRect暗示的是lua层的Rect.New函数,所以这里的push实际上是把C#的Rect的每个数据成员都拆出来,作为基本类型挨个push到lua层,然后调用lua层的Rect.New函数在lua层完成struct的构造。
再看CheckValue,类似地它也会调用到StackTraits上:
public T CheckValue<T>(int stackPos)
{
return StackTraits<T>.Check(L, stackPos);
}
Rect CheckRectValue(IntPtr L, int pos)
{
int type = LuaDLL.tolua_getvaluetype(L, pos);
if (type != LuaValueType.Rect)
{
luaState.LuaTypeError(pos, ”Rect”, LuaValueTypeName.Get(type));
return new Rect();
}
return ToRectValue(L, pos);
}
CheckRectValue函数首先要对lua栈上的数据进行类型查抄,tolua_getvaluetype最终会调用到ValueType.lua中的GetLuaValueType函数:
local function GetValueType()
local getmetatable = getmetatable
local ValueType = ValueType
return function(udata)
local meta = getmetatable(udata)
if meta == nil then
return 0
end
return ValueType[meta] or 0
end
end
GetLuaValueType = GetValueType()lua层的Rect的metatable就是它自身,而我们在之前新增value type的时候已经调用过AddValueType了,那么这里返回的ValueType[meta]就是新增的类型13,与C#定义的完全一致。
类型查抄通过之后,就会走到ToRectValue这个函数上:
Rect ToRectValue(IntPtr L, int pos)
{
pos = LuaDLL.abs_index(L, pos);
LuaDLL.lua_getref(L, GetRect.GetReference());
LuaDLL.lua_pushvalue(L, pos);
LuaDLL.lua_call(L, 1, 4);
float x = (float)LuaDLL.lua_tonumber(L, -4);
float y = (float)LuaDLL.lua_tonumber(L, -3);
float w = (float)LuaDLL.lua_tonumber(L, -2);
float h = (float)LuaDLL.lua_tonumber(L, -1);
LuaDLL.lua_pop(L, 4);
return new Rect(x, y, w, h);
}
与push类似,这里也是借lua层之手,调用lua层的Rect:Get函数,把lua层的table拆成基本类型,push到lua栈上,这样C#层就可以直接获取到,然后再组装成C#的struct。
总结一下,tolua新增一个struct的过程如下:
1、在C#层和lua层都定义一个新的value type,类型的值要完全一致,这样类型查抄才会通过;
2、lua层按照C#层的写法,实现一个独立的struct;
3、C#层实现Push,Check,To三类函数,用于将C#的struct push到lua层;对lua层栈上的数据进行类型查抄,将lua层栈上的数据转换为C#的struct。
向lua层传递struct,就是将C#的struct拆分成若干基本类型,依次push到lua栈,然后调用lua层对应struct的new函数,创建lua层的struct;类似地,从lua层获取struct,就是调用lua层对应struct的get函数,将table转换为若干基本类型,push到lua栈,C#层直接取出,再进行组装。由于这里只涉及到基本类型的传递,因此没有拆装箱,不发生gc。
下一节我们将讨论tolua的自动代码生成机制。
如果你感觉我的文章有辅佐,欢迎存眷我的微信公众号 我是真的想做游戏啊 |
|