交互优化


用好Lua+Unity,让性能飞起来--Lua与C#交互篇

Lua 和 C# 是两种语言,无法直接持有另一方的对象(暂且不讨论指针)。主流的 Lua 框架都是把 C# 的 Object 存放到一个 Dictionary 中,用 ID 作为 Key,而 Lua 端则会构造一个 userdata 来作为 C# Object 的代理,C# Object 的相关成员方法和变量会以元表的形式实现。以 ToLua 为例,当 Lua 想要执行类似于 self.gameObject.transform 的调用时,会通过 userdata 的元表调用胶水代码(Wrap 类),然后利用 ID 找 C# Object 并执行相关逻辑。相对应的,如果 C# 想要引用 Lua 对象,也需要把 Lua 放到一个 Table 中,然后把 Lua 对象在 Table 中的索引传给 C#,并在 C# 中构造一个 Lua 对象的代理(比如 LuaTable、LuaFunction 等等)。

这样做有不少问题。第一个问题是 . 操作符并没有我们想象中的那么简单,因为我们在 Lua 中持有的仅仅是 C# 对象的代理,看似简单的写法实际上会涉及到 C# 与 Lua 的交互以及可能的 GC:

http://cdn.fantasticmiao.cn/image/post/Lua优化笔记/01.png

每当涉及到 Object 的访问时,都需要通过 ID 找到 C# 的 Object,也就是说看似简单的 gameObject.transform 其实伴随着大量的 GC 以及堆栈交互。另外,如果 Lua 仅仅只是临时引用一下对象(比如只是想修改一下位置),那么随着 Lua 释放了临时引用,userdata 和 C# Dictionary 中对应的引用也会被释放掉,下次再获取相同对象时又需要进行上述的准备工作。更多的细节这里就不提了,之后会考虑专门写一篇笔记分析 Wrap 结构。ToLua 的 Object 管理在 ObjectTranslator 类,相关的交互操作可以查看对应的 Wrap 类。

第二个问题是循环引用导致 C# 对象无法释放。例如,如果在 Lua 端直接引用一个 Button,就有可能出现循环引用:

self.button = gameObject:GetComponent(typeof(Button))
self.button.onClick:AddListener(function()
    self.isClicked = true
end)