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

Push基础类型lua数据结构值类型引用类型

Get基础类型lua数据结构值类型引用类型

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

Push

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

public void PushByType(RealStatePtr L, T v)

{

Action push_func;

if (tryGetPushFuncByType(typeof(T), out push_func))

{

push_func(L, v);

}

else

{

PushAny(L, v);

}

}

基础类型

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

typeAPIintlua_pushintegerdoublelua_pushnumberstringlua_pushstringboollua_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(translator.PushUnityEngineVector2, translator.Get, translator.UpdateUnityEngineVector2);

translator.RegisterPushAndGetAndUpdate(translator.PushUnityEngineVector3, translator.Get, translator.UpdateUnityEngineVector3);

translator.RegisterPushAndGetAndUpdate(translator.PushUnityEngineVector4, translator.Get, translator.UpdateUnityEngineVector4);

translator.RegisterPushAndGetAndUpdate(translator.PushUnityEngineColor, translator.Get, translator.UpdateUnityEngineColor);

translator.RegisterPushAndGetAndUpdate(translator.PushUnityEngineQuaternion, translator.Get, translator.UpdateUnityEngineQuaternion);

translator.RegisterPushAndGetAndUpdate(translator.PushUnityEngineRay, translator.Get, translator.UpdateUnityEngineRay);

translator.RegisterPushAndGetAndUpdate(translator.PushUnityEngineBounds, translator.Get, translator.UpdateUnityEngineBounds);

translator.RegisterPushAndGetAndUpdate(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(RealStatePtr L, int index, out T v)

{

Func 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:

typeAPIintlua_tointegerdoublelua_tonumberstringlua_tostringboollua_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))

{

return new System.Func(__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);

}

相关文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: