xLua是目前Unity热更新中用的比较多的方案之一,花了些时间看了下代码,并对其中的Lua和C#中对象的传递与内存管理有了一些个人理解,可能理解有误,欢迎批评指正

C#对象传递到Lua

Unity中C#对象可以大致分成几类:继承自primitive类型、string类型、struct类型、enum类型和类对象,这几种类型的对象有不同的处理逻辑,分为如何将对象传递到lua中,当从lua中传递回C#时如何找到对应的C#对象,以及何时释放对象的引用避免内存泄漏几个方面

  1. primitive类型
    primitive类型不参与GC,所以直接调用 lua_pushxxx函数,Lua传递到C#时也是直接调用 lua_toxxx即可

  2. string类型
    string类型和primitive一样,直接调用 lua_pushstring函数,Lua传递到C#时直接调用 lua_tostring即可

  3. 类对象
    类对象会参与GC,会把Lua查找C#对象的映射关系存储到objects对象中,映射关系存储到reverseMap中,这样后续可以直接复用之前的对象

    因为回存在对象回收的问题,所以会对在Lua中创建的相应关系的LuaTable注册名为 __gc的metatable,这样在Lua对象被GC的时候,可以完全清除掉objects对象中对C#对象的引用,避免内存泄漏,同时objects对象中也是一个会扩容的对象池,清除掉引用之后就也可以复用直接的id,避免无限扩容

    对于继承自 UnityEngine.Object的对象因为重写了 ==运算符还会有特殊逻辑,需要使用者在合适的时机(一般来讲是 Update方法中间隔固定时间)调用 LuaEnv上的 Tick方法,这样可以在 UnityEngine.Object对象被销毁后及时将objects中Lua查找C#的映射关系单向清理掉,避免内存泄漏,反向的映射关系还是需要等到上面提到的 __gc方法中清除,避免使用者将lua对象再次传回C#中后因为复用查找到不同的对象出现错误

  4. enum类型
    枚举类型是固定不变的,不需要参与GC,会把通过Lua查找C#的映射关系存储到objects对象中,C#到Lua的映射关系存储到enumMap对象中,这样在后续的使用中都会直接复用之前的对象

  5. struct类型
    因为c#中struct为值类型,所以会出现当一个struct对象传递到Lua后再传递回C#就不会是同一个对象了,如果只是使用例如Color或者Vector等struct是没有问题的,但是对于一些内部带有状态的struct是会出现问题的,这点需要额外注意

Lua对象传递到C#

Lua中常用的会传递到C#中的类型有几种:boolean、number、string、table、function,其中boolean、number、string都是用是直接值传递方式

  1. function
    function一般来讲是对应C#中的delegate,但是因为lua为弱类型,C#为强类型,所以需要一个比较复杂的包装处理,首先会包装成 DelegateBridge对象,然后使用 getDelegate函数根据需要的delegate类型做一层转换包装处理,返回 DelegateBridge对象上一个具体的需要的类型的成员函数,并将lua对象在C#中的映射关系存储到delegate_bridges中,这样在下次将这个函数从lua中传递到C#中时可以直接复用已有对象
    此外,还有个类叫做 LuaFunction也可以实现用于将lua对象传递到C#中使用的功能
  2. table
    table的情况比较复杂,常见的分成几种情况:
    1. table转换为C#中struct,这种情况就是直接构造了一个struct对象然后队成员变量赋值
    2. table转换为interface,这种情况需要xLua生成一个继承自对应interface的一个实现类,并对里面所有的抽象方法利用Lua的C API生成对应的具体实现
    3. 直接将table封装为 LuaTable对象,等到具体使用的时候直接调用 LuaTable对应方法获取上面的函数或变量

DelegateBridge、实现interface、LuaTable等方式都需要注意,他们都会对对应的table产生一次引用,而且他们(包括其他类似类也是一样)都继承自 LuaBase,会在析构函数中解除对table的引用,避免内存泄漏