xlua源码分析(六) C#与lua的交互总结

上一节我们分析了xlua对struct类型所做的优化,本节我们系统性地梳理一下xlua中C#与lua的交互。所谓C#与lua的交互,其实主要就分为两部分,第一是往lua层中传数据,第二则是从lua层中取数据。

Push

往lua层中传数据定义为Push,在C#的ObjectTranslator类中,可以看到Push所有支持类型到lua层的入口函数:

public void PushByType<T>(RealStatePtr L,  T v)
{
    Action<RealStatePtr, T> push_func;
    if (tryGetPushFuncByType(typeof(T), out push_func))
    {
        push_func(L, v);
    }
    else
    {
        PushAny(L, v);
    }
}

基础类型

int,double,string,bool等基础类型,在lua层中也能找到与之所对应的类型,所以处理起来最简单,直接调用lua API即可:

typeAPI
intlua_pushinteger
doublelua_pushnumber
stringlua_pushstring
boollua_pushboolean

lua数据结构

之前的文章中分析过,xlua在C#中实现了映射lua table和lua function的类,默认无需额外生成代码的通用类叫做LuaTable和LuaFunction,以及通过接口或者委托的方式,生成代码的专用类XXXBridge和DelegateBridge类。它们都继承自LuaBase基类,而且本质上表示的是lua对象,所以push到lua层,也应该是push所持有的lua对象,而不是它自身。持有的lua对象保存在lua的registry表中,C#类记录了其reference:

internal virtual void push(RealStatePtr L)
{
    LuaAPI.lua_getref(L, luaReference);
}

值类型

为了性能考虑,C#层传递值类型时,对自定义的值类型,都会生成一个custom push函数,例如:

translator.RegisterPushAndGetAndUpdate<UnityEngine.Vector2>(translator.PushUnityEngineVector2, translator.Get, translator.UpdateUnityEngineVector2);
translator.RegisterPushAndGetAndUpdate<UnityEngine.Vector3>(translator.PushUnityEngineVector3, translator.Get, translator.UpdateUnityEngineVector3);
translator.RegisterPushAndGetAndUpdate<UnityEngine.Vector4>(translator.PushUnityEngineVector4, translator.Get, translator.UpdateUnityEngineVector4);
translator.RegisterPushAndGetAndUpdate<UnityEngine.Color>(translator.PushUnityEngineColor, translator.Get, translator.UpdateUnityEngineColor);
translator.RegisterPushAndGetAndUpdate<UnityEngine.Quaternion>(translator.PushUnityEngineQuaternion, translator.Get, translator.UpdateUnityEngineQuaternion);
translator.RegisterPushAndGetAndUpdate<UnityEngine.Ray>(translator.PushUnityEngineRay, translator.Get, translator.UpdateUnityEngineRay);
translator.RegisterPushAndGetAndUpdate<UnityEngine.Bounds>(translator.PushUnityEngineBounds, translator.Get, translator.UpdateUnityEngineBounds);
translator.RegisterPushAndGetAndUpdate<UnityEngine.Ray2D>(translator.PushUnityEngineRay2D, translator.Get, translator.UpdateUnityEngineRay2D);

值类型主要分为两种,一是enum,一是struct。对于enum,由于enum是全局唯一的,所以xlua在lua层以一个全局的table记录它,enum中的每一个元素都对应table中的一个userdata。而struct则有两种映射方式,一种是userdata,userdata直接保存struct的数据,一种是table,struct中的每个字段对应了table的每个key。如果该值类型找不到相应的custom push函数,那就当做C#引用类型来处理了。

引用类型

C#引用类型的对象,会当作一个userdata,push到lua层,push之前会在C#层中进行缓存,并返回一个缓存的index,这个index将作为lua层缓存中的key,与userdata相关联。

Get

从lua层中取数据,则要复杂一些,因为C#类型相比于lua类型来说,要多得多。同样ObjectTranslator类中也有一个包含一切的Get函数:

public void Get<T>(RealStatePtr L, int index, out T v)
{
    Func<RealStatePtr, int, T> get_func;
    if (tryGetGetFuncByType(typeof(T), out get_func))
    {
        v = get_func(L, index);
    }
    else
    {
        v = (T)GetObject(L, index, typeof(T));
    }
}

基础类型

int,double,string,bool等基础类型,这些类型与push类似,直接调用API:

typeAPI
intlua_tointeger
doublelua_tonumber
stringlua_tostring
boollua_toboolean

lua数据结构

与push时对应,要把lua栈上的值转换为LuaTable和LuaFunction这类对象,要么它是userdata,要么它就是对应的lua数据结构,此时get会把table或function缓存到registry表中,返回reference到C#层,用来创建LuaTable和LuaFunction对象。

private object getLuaTable(RealStatePtr L, int idx, object target)
{
    if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA)
    {
        object obj = translator.SafeGetCSObj(L, idx);
        return (obj != null && obj is LuaTable) ? obj : null;
    }
    if (!LuaAPI.lua_istable(L, idx))
    {
        return null;
    }
    LuaAPI.lua_pushvalue(L, idx);
    return new LuaTable(LuaAPI.luaL_ref(L), translator.luaEnv);
}

值类型

对于自定义的值类型,C#都有与之对应的custom get函数,如果lua栈上的值是userdata,就尝试从userdata中获取值类型中的数据;如果是table,就尝试根据值类型的字段名称,获取table中的值。这两种方法我们之前也都分析过了。

public void Get(RealStatePtr L, int index, out UnityEngine.Vector2 val)
{
    LuaTypes type = LuaAPI.lua_type(L, index);
    if (type == LuaTypes.LUA_TUSERDATA )
    {
        if (LuaAPI.xlua_gettypeid(L, index) != UnityEngineVector2_TypeID)
        {
            throw new Exception("invalid userdata for UnityEngine.Vector2");
        }
        
        IntPtr buff = LuaAPI.lua_touserdata(L, index);if (!CopyByValue.UnPack(buff, 0, out val))
        {
            throw new Exception("unpack fail for UnityEngine.Vector2");
        }
    }
    else if (type ==LuaTypes.LUA_TTABLE)
    {
        CopyByValue.UnPack(this, L, index, out val);
    }
    else
    {
        val = (UnityEngine.Vector2)objectCasters.GetCaster(typeof(UnityEngine.Vector2))(L, index, null);
    }
}

引用类型

对于一般的引用类型对象,C#层有一个genCaster函数,根据对象类型动态生成get函数。这个函数体比较庞大复杂,首先会判断lua栈上的值是否为userdata,如果是则尝试从C#对象缓存中根据userdata的key取出对应的object,取出成功则直接返回。但如果取出失败,则需要根据类型的不同,去处理不同的逻辑。

例如,如果引用类型为Delegate,则需要判断是否是lua function映射到delegate的情况,如果是则需要返回包装好lua function的delegate。这些包装函数也是自动生成的,位于DelegatesGensBridge这个文件中:

public override Delegate GetDelegateByType(Type type)
{

    if (type == typeof(System.Action))
    {
        return new System.Action(__Gen_Delegate_Imp0);
    }

    if (type == typeof(UnityEngine.Events.UnityAction))
    {
        return new UnityEngine.Events.UnityAction(__Gen_Delegate_Imp0);
    }

    if (type == typeof(System.Func<double, double, double>))
    {
        return new System.Func<double, double, double>(__Gen_Delegate_Imp1);
    }

    ...

    return null;
}

类似地,如果引用类型为interface,则需要考虑是否是lua table映射到interface的情况,如果是则需要返回包装好lua table的interface。包装类的字段对应table中的字段,方法则对应table中所包含的function。包装类也是自动生成的,在XLuaGenAutoRegister这个文件里:

static void Init(LuaEnv luaenv, ObjectTranslator translator)
{
    translator.AddInterfaceBridgeCreator(typeof(System.Collections.IEnumerator), SystemCollectionsIEnumeratorBridge.__Create);
    
    translator.AddInterfaceBridgeCreator(typeof(XLuaTest.IExchanger), XLuaTestIExchangerBridge.__Create);
    
    translator.AddInterfaceBridgeCreator(typeof(Tutorial.CSCallLua.ItfD), TutorialCSCallLuaItfDBridge.__Create);
    
    translator.AddInterfaceBridgeCreator(typeof(XLuaTest.InvokeLua.ICalc), XLuaTestInvokeLuaICalcBridge.__Create);
    
}

除此之外,xlua对于一般的引用对象,还支持更灵活的方式,如果lua栈上的值为table,则会自动地通过反射,获取C#对象的field info,根据field name获取table中的值,然后再根据field type调用合适的get函数填充C#对象的field value。

foreach (FieldInfo field in type.GetFields())
{
    LuaAPI.xlua_pushasciistring(L, field.Name);
    LuaAPI.lua_rawget(L, idx);
    if (!LuaAPI.lua_isnil(L, -1))
    {
        try
        {
            field.SetValue(obj, GetCaster(field.FieldType)(L, n + 1,
                    target == null || field.FieldType.IsPrimitive() || field.FieldType == typeof(string) ? null : field.GetValue(obj)));
        }
        catch (Exception e)
        {
            throw new Exception("exception in tran " + field.Name + ", msg=" + e.Message);
        }
    }
    LuaAPI.lua_pop(L, 1);
}
  • 16
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值