在我们使用xLua作为Unity中lua集成的解决方案时,遇到了一个问题,就是当我们使用在lua中把UI中的某个控件绑定相应的事件(如按钮的onClick事件),xLua绑定这个事件是用委托实现的,具体代码可以查看xLua的代码。而在程序退出的时候xLua会检查对应的委托有没有被正确的释放掉,如果没有释放掉的话就会抛出异常。代码如表所示:

 1 public virtual void Dispose(bool dispose)
2 {
3 #if THREAD_SAFE || HOTFIX_ENABLE
4 lock (luaEnvLock)
5   {
6 #endif
7 if (disposed) return;
8 Tick();
9
10 if (!translator.AllDelegateBridgeReleased())
11 {
12 throw new InvalidOperationException("try to dispose a LuaEnv with C# callback!");
13 }
14
15 LuaAPI.lua_close(L);
16
17 ObjectTranslatorPool.Instance.Remove(L);
18 translator = null;
19
20 rawL = IntPtr.Zero;
21
22 disposed = true;
23 #if THREAD_SAFE || HOTFIX_ENABLE
24   }
25 #endif
26 }

  

这说明我们并没有把对应的委托给释放掉。所以我们需要确保在程序退出之前所有的委托要正确地释放掉。方案大体如下,每一个UI都对应一个实例,这样在绑定控件的时候创建一个匿名函数,这个函数用于控件把这个控件绑定的事件清除掉,同时把这个匿名函数放到一个数组里面去,在这个UI销毁的时候调用一个函数(比如我们叫做Destroy),这个函数的作用就是负责一些清理工作,其中就包括遍历前面提到的匿名函数的数组并挨个调用。这样就把xLua生成的委托的引用去掉了。在程序退出并触发GC的时候就会把这个委托释放掉,这样xLua检查就没有问题了。

 
 1 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc)
2 aButton.onClick.AddListener(
3 function ()
4 aFunc(aUIInstance)
5 end)
6
7 // 将闭包添加到一个table中用于后面调用
8 table.insert(aUIInstance.unregisterWidgetClousures,
9 function()
10 aButton.onClick:RemoveAllListeners()
11 end)
12
13 end

可能到这里你觉得问题已经解决了,可是如果到这的话就不会有这篇文章了。问题是这样调用了以后在程序退出的时候还是会抛出异常。按正常来说这样做了就可以了,经过一番实验发现只要这个控件没有被触碰过那么就可以正常退出,如果触碰了就会抛出异常。一开始怀疑是xLua的问题但经过看代码确定不是它的问题。这个时候想到了可能Unity对这个委托做了缓存,虽然我上面把它清除掉了,但是Unity内部可能是做了缓存的。最开始没有去关注这个问题,而是想了另外一个办法直接把控件对应的事件给黑窑了。示例代码如下所示:

 1 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc)
2 aButton.onClick.AddListener(
3 function ()
4 aFunc(aUIInstance)
5 end)
6
7 // 将闭包添加到一个table中用于后面调用
8 table.insert(aUIInstance.unregisterWidgetClousures,
9 function()
10 aButton.onClick = nil
11 end)
12
13 end

这样就解决了问题。但是后面发现我们要重用UI的时候由于我们重用的规则所致(UI的C#对象没有回收但是会回收,但是lua对象会回收),上面的这个地方就出问题了。当我们下次再要重新使用这个UI的时候,因为上面被置空了,接下来使用就有问题了。我们也想过其它的方法来解决,但总感觉破坏了原有简单的结构。这样做不太好。这个时候就想看看Unity到底哪里出了问题了,不过幸运的是很快就发现了问题。我们使用ILSpy打开UnityEngine.dll查看了一下UnityEvent的代码,发现在它的基类里面做了一个简单的优化,就是这个优化导致了上面问题的发生。我们来看下代码片断:

1 public abstract class UnityEventBase : ISerializationCallbackReceiver
2 {
3 private InvokableCallList m_Calls;
4 }

Unity用这个来保存需要调用函数,我们再来看看它的具体实现片段:

 1 namespace UnityEngine.Events
2 {
3 internal class InvokableCallList
4 {
5 private readonly List<BaseInvokableCall> m_PersistentCalls = new List<BaseInvokableCall>();
6
7 private readonly List<BaseInvokableCall> m_RuntimeCalls = new List<BaseInvokableCall>();
8
9 private readonly List<BaseInvokableCall> m_ExecutingCalls = new List<BaseInvokableCall>();
10
11 private bool m_NeedsUpdate = true;
12
13 public void AddListener(BaseInvokableCall call)
14 {
15 this.m_RuntimeCalls.Add(call);
16 this.m_NeedsUpdate = true;
17 }
18
19 public void RemoveListener(object targetObj, MethodInfo method)
20 {
21 List<BaseInvokableCall> list = new List<BaseInvokableCall>();
22 for (int i = 0; i < this.m_RuntimeCalls.Count; i++)
23 {
24 if (this.m_RuntimeCalls[i].Find(targetObj, method))
25 {
26 list.Add(this.m_RuntimeCalls[i]);
27 }
28 }
29 this.m_RuntimeCalls.RemoveAll(new Predicate<BaseInvokableCall>(list.Contains));
30 this.m_NeedsUpdate = true;
31 }
32
33 public void Clear()
34 {
35 this.m_RuntimeCalls.Clear();
36 this.m_NeedsUpdate = true;
37 }
38
39 public void Invoke(object[] parameters)
40 {
41 if (this.m_NeedsUpdate)
42 {
43 this.m_ExecutingCalls.Clear();
44 this.m_ExecutingCalls.AddRange(this.m_PersistentCalls);
45 this.m_ExecutingCalls.AddRange(this.m_RuntimeCalls);
46 this.m_NeedsUpdate = false;
47 }
48 for (int i = 0; i < this.m_ExecutingCalls.Count; i++)
49 {
50 this.m_ExecutingCalls[i].Invoke(parameters);
51 }
52 }
53 }
54 }
我们看到有m_RuntimeCalls这个变量,它是用来做什么的呢?就是为了做一个优化的,为了只在添加或移除了Listener之后才更新它做的一个优化。对于原来Unity本身的设计来说,是没有问题的。但是,我们看一下不论是RemoveListener或者Clear的时候都没有清掉m_RuntimeCalls里面的值,按理说它在Clear()的时候是应该清掉的。所以就有了我们前面提到的问题。知道了原因,这里就有了两个解决方法:
  1. 直接必UnityEngine.dll的代码,因为我们没有源码,所以只能通过一些工作来改。但这带来一个问题,就是需要给开发组的每个人替换修改后的dll,另外一个问题就是如果升级Unity的话又会带来不必要的麻烦。所以这个方案就放弃了。
  2. 我们可以看到虽然Clear()没有调用m_ExecutingCalls.Clear(),但是我们可以再调用一次Invoke()函数,这个时候它就会把m_ExecutingCalls的内容清掉了,这个时候就没有对象引用着xLua生成的委托了。这个方案目前来说还是比较好的。因为毕竟多调用一次的开销是可以接受的。

  于是代码变成了如下代码示例的样子:

 1 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc)
2 aButton.onClick.AddListener(
3 function ()
4 aFunc(aUIInstance)
5 end)
6
7 // 将闭包添加到一个table中用于后面调用
8 table.insert(aUIInstance.unregisterWidgetClousures,
9 function()
10 aButton.onClick:RemoveAllListeners()
11 aButton.onClick()
12 end)
13
14 end

好的,到这里问题已经完美解决了。当然我们也可以简单的把抛异常的地方注释掉,但这肯定不是解决问题的正确方法。当然如果你也遇到这个问题并且有更好的方案也可以一起讨论。

 

【转】Unity 使用xLua遇到的坑的更多相关文章

  1. Unity 使用xLua遇到的坑

    在我们使用xLua作为Unity中lua集成的解决方案时,遇到了一个问题,就是当我们使用在lua中把UI中的某个控件绑定相应的事件(如按钮的onClick事件),xLua绑定这个事件是用委托实现的,具 ...

  2. Unity下XLua方案的各值类型GC优化深度剖析

    转自:http://gad.qq.com/article/detail/25645 前言 Unity下的C#GC Alloc(下面简称gc)是个大问题,而嵌入一个动态类型的Lua后,它们之间的交互很容 ...

  3. 现学现卖】IntelliJ+EmmyLua 开发调试Unity中Xlua

    http://blog.csdn.net/u010019717/article/details/77510066?ref=myread http://blog.csdn.NET/u010019717 ...

  4. unity, Shader.Find的一个坑

    所以对于没有被任何东西引用,只靠在游戏运行时使用Shader.Find换上去的shader,为了双保险,可以首先放到resources文件夹里,另外,再在ProjectSettings->Gra ...

  5. unity, Collider2D.bounds的一个坑

    Note that this will be an empty bounding box if the collider is disabled or the game object is inact ...

  6. Unity 游戏框架搭建 (十) QFramework v0.0.2小结

    从框架搭建系列的第一篇文章开始到现在有四个多月时间了,这段时间对自己来说有很多的收获,好多小伙伴和前辈不管是在评论区还是私下里给出的建议非常有参考性,在此先谢过各位. 说到是一篇小节,先列出框架的概要 ...

  7. Unity Dotween build error

    unity这东西感觉挺坑 在mac上build的时候遇到error IL2CPP error for method 'System.Void DG.Tweening.DOTweenPath::DORe ...

  8. xLua热更新插件

    一.xLua插件下载安装 1.从GitHub上搜索并下载插件 2.将文件复制到unity中 3.检查是否有错误 二.在unity中调用lua 1.简单调用 在c#脚本中使用LuaEnv类可以运行lua ...

  9. Unity——Js和Unity互相调用

    Unity项目可以打包成WebGl,打包后的项目文件: Build中是打包后的Js代码: Index.html是web项目的入口,里面可以调整web的自适应,也可以拿去嵌套: TemplateData ...

随机推荐

  1. SVN文件自动加锁-Win7

    在Win7操作系统上 打开目录C:\Users\Administrator\AppData\Roaming\Subversion 用记事本打开config文件 将enable-auto-props = ...

  2. 点击HTML页面问号出现提示框

    本demo的功能:点击页面按钮在其边缘出现提示信息,点击页面任何一处则消失. 如下图: 1.所需插件: jquery插件: layer插件: 2.HTML内容: ==注意==: class=" ...

  3. python3爬虫编码问题

    使用爬虫爬取网页经常遇到各种编码问题,因此产生乱码今天折腾了一天,全部总结一遍环境:win10,pycharm,python3.41.首先先来网页编码是utf-8的:以百度首页为例:使用request ...

  4. jdk8新特性之双冒号 :: 用法及详解

    jdk8的新特性有很多,最亮眼的当属函数式编程的语法糖,本文主要讲解下双冒号::的用法. 概念 类名::方法名,相当于对这个方法闭包的引用,类似js中的一个function.比如: Function& ...

  5. DB数据源之SpringBoot+MyBatis踏坑过程(五)手动使用Hikari连接池

    DB数据源之SpringBoot+MyBatis踏坑过程(五)手动使用Hikari连接池 liuyuhang原创,未经允许禁止转载  系列目录连接 DB数据源之SpringBoot+Mybatis踏坑 ...

  6. es6 数组扩展方法

    1.扩展运算符 含义: 扩展运算符,三个点(...),将一个数组转为用逗号分隔的参数顺序. 例如: console.log([1,2,3]); console.log(...[1,2,3]);   结 ...

  7. POJ 3528--Ultimate Weapon(三维凸包)

    Ultimate Weapon Time Limit: 2000MS   Memory Limit: 131072K Total Submissions: 2430   Accepted: 1173 ...

  8. 关于如何去Apple.cn下载Xcode以及模拟器包

    前言:对于一个懒惰的iOS开发,Xcode的更新我是迟迟没有去下载.有人或许会说:你并不是一个合格的iOS开发者! T3T 我承认自己缺少拓新精神,Apple的尿性是:坑死第一批体验者不偿命~表示本人 ...

  9. webpack 优化代码 让代码加载速度更快

    一,如何优化webpack构建 (1),缩小文件搜索范围, 优化Loader配置 module.exports = { module: { rules: [ { test:/\.js$/, use:[ ...

  10. 能够还原jQuery1.8的toggle的功能的插件

    下面这个jQuery插件能够还原1.8的toggle的功能,如果你需要,可以直接把下面这段代码拷贝到你的jQuery里面,然后跟平时一样使用toggle的功能即可. //toggle plugin f ...