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

 
         public virtual void Dispose(bool dispose)
{
#if THREAD_SAFE || HOTFIX_ENABLE
lock (luaEnvLock)
{
#endif
if (disposed) return;
Tick(); if (!translator.AllDelegateBridgeReleased())
{
throw new InvalidOperationException("try to dispose a LuaEnv with C# callback!");
} LuaAPI.lua_close(L); ObjectTranslatorPool.Instance.Remove(L);
translator = null; rawL = IntPtr.Zero; disposed = true;
#if THREAD_SAFE || HOTFIX_ENABLE
}
#endif
}

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

 
 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc)
aButton.onClick.AddListener(
function ()
aFunc(aUIInstance)
end) // 将闭包添加到一个table中用于后面调用
table.insert(aUIInstance.unregisterWidgetClousures,
function()
aButton.onClick:RemoveAllListeners()
end) end

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

 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc)
aButton.onClick.AddListener(
function ()
aFunc(aUIInstance)
end) // 将闭包添加到一个table中用于后面调用
table.insert(aUIInstance.unregisterWidgetClousures,
function()
aButton.onClick = nil
end) end

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

 public abstract class UnityEventBase : ISerializationCallbackReceiver
{
private InvokableCallList m_Calls;
}

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

 namespace UnityEngine.Events
{
internal class InvokableCallList
{
private readonly List<BaseInvokableCall> m_PersistentCalls = new List<BaseInvokableCall>(); private readonly List<BaseInvokableCall> m_RuntimeCalls = new List<BaseInvokableCall>(); private readonly List<BaseInvokableCall> m_ExecutingCalls = new List<BaseInvokableCall>(); private bool m_NeedsUpdate = true; public void AddListener(BaseInvokableCall call)
{
this.m_RuntimeCalls.Add(call);
this.m_NeedsUpdate = true;
} public void RemoveListener(object targetObj, MethodInfo method)
{
List<BaseInvokableCall> list = new List<BaseInvokableCall>();
for (int i = ; i < this.m_RuntimeCalls.Count; i++)
{
if (this.m_RuntimeCalls[i].Find(targetObj, method))
{
list.Add(this.m_RuntimeCalls[i]);
}
}
this.m_RuntimeCalls.RemoveAll(new Predicate<BaseInvokableCall>(list.Contains));
this.m_NeedsUpdate = true;
} public void Clear()
{
this.m_RuntimeCalls.Clear();
this.m_NeedsUpdate = true;
} public void Invoke(object[] parameters)
{
if (this.m_NeedsUpdate)
{
this.m_ExecutingCalls.Clear();
this.m_ExecutingCalls.AddRange(this.m_PersistentCalls);
this.m_ExecutingCalls.AddRange(this.m_RuntimeCalls);
this.m_NeedsUpdate = false;
}
for (int i = ; i < this.m_ExecutingCalls.Count; i++)
{
this.m_ExecutingCalls[i].Invoke(parameters);
}
}
}
}
我们看到有m_RuntimeCalls这个变量,它是用来做什么的呢?就是为了做一个优化的,为了只在添加或移除了Listener之后才更新它做的一个优化。对于原来Unity本身的设计来说,是没有问题的。但是,我们看一下不论是RemoveListener或者Clear的时候都没有清掉m_RuntimeCalls里面的值,按理说它在Clear()的时候是应该清掉的。所以就有了我们前面提到的问题。知道了原因,这里就有了两个解决方法:
  1. 直接必UnityEngine.dll的代码,因为我们没有源码,所以只能通过一些工作来改。但这带来一个问题,就是需要给开发组的每个人替换修改后的dll,另外一个问题就是如果升级Unity的话又会带来不必要的麻烦。所以这个方案就放弃了。
  2. 我们可以看到虽然Clear()没有调用m_ExecutingCalls.Clear(),但是我们可以再调用一次Invoke()函数,这个时候它就会把m_ExecutingCalls的内容清掉了,这个时候就没有对象引用着xLua生成的委托了。这个方案目前来说还是比较好的。因为毕竟多调用一次的开销是可以接受的。

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

 function UIUtils:AddButtonOnClick(aUIInstance, aButton, aFunc)
aButton.onClick.AddListener(
function ()
aFunc(aUIInstance)
end) // 将闭包添加到一个table中用于后面调用
table.insert(aUIInstance.unregisterWidgetClousures,
function()
aButton.onClick:RemoveAllListeners()
aButton.onClick()
end) 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. JavaScript深入浅出补充——(一)数据类型,表达式和运算符

    项目基本做完,在进行下一阶段学习之前先看视频学习回顾一下JavaScript 一.数据类型 JavaScript中有五种原始类型和一种对象类型 JavaScript弱类型语言中隐式转换 num-0 字 ...

  2. JQuery实战——页面进度条效果

    今早逛阮一峰大神的博客 ECMAScript 6 入门 时候看到页面顶部有个进度条显示当前浏览的进度,如图: 顶部进度条会根据当前页面高度进行宽度调整,实战一番,视觉使用animated方法实现.下面 ...

  3. mybatis学习笔记(一)-- 简单入门(附测试Demo详细过程)

    写在最前 MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生M ...

  4. mysql5.6 无法使用64odbc连接

    win10 64位,mysql5.6 无法使用64 连接odbc,处理:装32位odbc, 使用C:\Windows\SysWOW64\odbcad32.exe 配置.

  5. 大数据平台常见异常-zookeeper

    本文主要阐述大数据平台环境zookeeper常见异常和解决方案 1.Connection reset by peer异常 异常说明 我们现在项目有个任务OneMinuteDataSync是用spark ...

  6. IE事件处理

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. PhysicsBasedAnimation学习记录

    一.前言 1.概述 Google I/O'17推出了许多新的特性,在动画这一块又有新的API供开发者使用,在动画API中引入了DynamicAnimation,开发者可以使用新的API创建更加动态化的 ...

  8. PCI_Making Recommendations

    协作性过滤 简单理解从众多用户中先搜索出与目标用户'品味'相似的部分人,然后考察这部分人的偏爱,根据偏爱结果为用户做推荐.这个过程也成为基于用户的协作性过滤(user_based collaborat ...

  9. STL—vector空间的动态增长

    vector空间的动态增长     当添加元素时,如果vector空间大小不足,则会以原大小的两倍另外配置一块较大的新空间,然后将原空间内容拷贝过来,在新空间的内容末尾添加元素,并释放原空间.vect ...

  10. jvm003 类加载的过程

    类加载的过程 一.加载 在加载阶段虚拟机需要完成以下三件事: 通过一个类的全限定名称来获取此类的二进制字节流 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 在内存中生成一个代表这个类的 ...