Lua与C#交互初析

背景

项目是全Lua开发,导致的其中一个结果是会遇到lua的gc性能问题。而且相对于C#这种强类型语言,Lua因为其自由性,对于团队后期维护还是有一定的成本,不做好代码复审,相对不好维护。这个时候就需要我们自主了解Lua和C#交互的底层逻辑和实现原理,用以之后在lua测开发的时候做出良好的代码优化。

项目采用xlua结构与c#端进行交互,在个别地方和其他类型的lua(比如和tolua的加载机制)不一样,但lua底层都是一致的。在此次分享中会有一些知识专属于xlua范围。

Lua和C#交互逻辑

Lua文件加载

xLua定义了两种在C#中调用lua代码的方式:

  1. DoString("xxxxxxxxxxxx").这个函数一般不建议使用,LuaEnv中就有此段代码:

Lua与C#交互初析_第1张图片

Lua与C#交互初析_第2张图片

这段代码内部主要定义了在Lua中访问c#对象时进行的具体操作。这里具体干了什么事情,还需要留待未来探讨。

  1. 自定义loader来加载文件。

通过调用LuaEnv中的AddLoader注册回调,这样每当lua脚本中调用require函数时,都会将参数传回给回调。如果这个回调返回null,则证明loader找不到相应的lua脚本,否则返回lua脚本的内容。

有了这种加载方式,则方便处理热更新下来的lua文件,或者是加密的文件,在回调中进行相应的操作即可。参考代码如下:

LuaEnv luaenv = new LuaEnv(); luaenv.AddLoader(MyLoader);

上述两种方式中,用自定义loader的一个好处就是,文件即便是已经加密也可以自己写loader读取文件解密后返回即可。

Lua和C#如何互相调用方法、对象(浅层)

xlua定义了独有的调用方式。在程序员层面上,我们只需要在c#代码某处加上LuaCallCSharp就可以在lua端调用代码。

[CSharpCallLua] public static List csharpCallLuaList = new List
{
    typeof(System.Func),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Action),
    typeof(System.Func),
    typeof(System.Func),
    typeof(System.Func),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionInt),
    typeof(Common.Util.ActionStr),
    。。。
};

[LuaCallCSharp] public static List luaCallCsharpList = new List
{
    //---Spine
    typeof(Spine.Unity.SkeletonAnimation),
    typeof(Spine.Unity.SkeletonRenderer),
    typeof(Spine.Unity.SkeletonGraphic),
    。。。
}; 
  

Lua Call C#代码的时候,C#处生成的代码基本都需要打标签[LuaCallCSharp]。

同理,C# call Lua代码的时候,都需要打标签[CSharpCallLua]。

在哪里进行C#代码和lua代码之间的转换的?

我们可以追踪哪里调用了这几个标签。在Generator.GenLuaRegister中可以看到对LuaCallCSharp的循环操作,这个方法会把这个类的静态方法(BeginClassRegister)、成员方法(BeginObjectRegister)注册到lua表里。

Lua与C#交互初析_第3张图片

Lua与C#交互初析_第4张图片

另外还有其他途径进行Xlua的生成,即从GenXlua生成XLua文件开始对这些方法、对象进行lua的装载和转换。

Lua与C#交互初析_第5张图片

Lua与C#交互初析_第6张图片

Lua与C#交互初析_第7张图片

Lua与C#交互初析_第8张图片

在这里看到了GenWrap这个函数,我们可以在里面看到XLua系统做了下面的几个操作:

Lua与C#交互初析_第9张图片

不管是GenLuaRegister还是GenWrap,里面都调用了核心方法GenOne,所有lua生成器都是在这边启动的。

通过LuaTemplate.Compile把模板文件.tpl.txt文件转成可执行的lua代码并加载进内存。

type_info_getter(type, type_info);设置lua代码要用到的参数。

LuaTemplate.Execute(template, type_info):执行lua代码进行文件生成。

生成流程就是:

1.Generator收集这种类型需要导出的对象。

2.通过LuaTemplate把对应的.tpl.txt文件转成可执行的lua代码。

3.在GenOne方法里给上一步生成的lua代码赋值全局变量。

4.执行lua代码生成wrap文件。

每个wrap文件都是对一个c#类的包装,在lua中,通过对wrap类中的函数调用,间接的对c#实例进行操作。

比如当我们在lua端调用以下代码时,实际上程序做了哪些事情?

local go = CS.UnityEngine.GameObject() local trans = go.transform

实际上我们可以追踪到LuaClassWrap.tpl.txt(LuaClassWrap:负责所有类的wrap的生成)中看到其实现(这中间的部分操作涉及到c++层)。

Lua与C#交互初析_第10张图片

同理,下面的

Utils.BeginClassRegister(type, L, __CreateInstance, <%=cls_field_count%>, <%=cls_getter_count%>, <%=cls_setter_count%>);

即代表给这个类的静态值创建元表。

于是在lua层就有了c#的这些静态值和非静态值,也就可以调用相关函数了。

那么我们又如何让lua端代码走c#的生命周期呢?答案就是在lua端通过调用AddComponent(typeof(LuaBehaviour))来绑定LuaBehaviour.cs并且在C#端走lua的Awake\Start\Update。

Lua与C#交互初析_第11张图片

注意:因为xlua的整个wrap流程测试下来其实挺耗费时间的,再加上项目启动的时候要一下子加载很多个c#类到lua端,所以大胆推测点击开始按钮时候的卡顿现象有极大概率和LuaCallCSharp这方面有关。

(如果不走xlua定义的标签方式,那就需要走反射机制,虽然反射万能,可以为任意C#结构体构建出lua元表,但是是性能最差的方式)

挖坑

之后会找个时间详细探讨lua和C#的交互代码优化。

你可能感兴趣的:(lua,c#,交互,unity,游戏,游戏引擎)