找回密码
 立即注册
查看: 228|回复: 0

tolua源码分析(十)struct

[复制链接]
发表于 2023-8-29 17:49 | 显示全部楼层 |阅读模式
上一节我们分析了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的自动代码生成机制。
如果你感觉我的文章有辅佐,欢迎存眷我的微信公众号 我是真的想做游戏啊
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-4-29 12:10 , Processed in 0.107459 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表