在我们使用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. 从零开始学习CocoaPods安装和使用

    从零开始学习CocoaPods安装和使用   转载: Code4App原创:http://code4app.com/article/cocoapods-install-usage http://m.i ...

  2. JS中some(),every(),fiflter(),map()各种循环的区别理解

    1.some():返回一个Boolean,判断是否有元素符合func条件const arr = [1,2,3,4]; arr.some((item)=>{return item>1}) 打 ...

  3. 20181030noip模拟赛T1

    YY的矩阵 YY有一个大矩阵(N*M), 矩阵的每个格子里都有一个整数权值W[i,j](1<=i<=M,1<=j<=N) 对于这个矩阵YY会有P次询问,每次询问这个大矩阵的一个 ...

  4. springsource-tool-suite插件下载

    下载地址:    https://spring.io/tools3/sts/all/ 下载页面上的 update sites archives文件

  5. sql 优化的几种方法

    .对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. .应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而 ...

  6. LAMP+Varnish的实现

    基于Keepalived+Varnish+Nginx实现的高可用LAMP架构 注意:各节点的时间需要同步(ntpdate ntp1.aliyun.com),关闭firewalld(systemctl ...

  7. 《黑客攻防技术宝典Web实战篇@第2版》读书笔记1:了解Web应用程序

    读书笔记第一部分对应原书的第一章,主要介绍了Web应用程序的发展,功能,安全状况. Web应用程序的发展历程 早期的万维网仅由Web站点构成,只是包含静态文档的信息库,随后人们发明了Web浏览器用来检 ...

  8. Jupyter Notebook的魔法

    在腾讯云上搭建了一个Jupyter Notebook 还是很好用的, 这里总结了一些看到的小技巧及快捷键使得我们更加快捷的完成工作. Notebook 与 Vim 有些类似,有两种键盘输入模式,即命令 ...

  9. Active Job 基础

    开发中涉及到调用三方服务API,运行时间长,结果不需要实时反馈给用户这样的任务,都可以使用异步处理.常见的场景包括:发邮件和短信.图片处理.定时清理等.爬虫. 后端处理软件可以自行选择这里选择了sid ...

  10. rails应用无法读取kafka数据报错Kafka::Error: Failed to find group coordinator

    如果确保kafka中有数据,rails应用中却无法读取到,或报如下错误: Kafka::Error: Failed to find group coordinator   一般有两种情况,解决:   ...