unity3d性能优化(lua委托事件)_skybeauty_新浪博客

ugui中常用的监听事件的组件大致有,button,ScrollRect,toggle,InputField,Slider
在使用lua写逻辑功能的时候避免不了要有点击相关的交互,而这些交互的函数在c#中其实是通过委托实现的,下面就以常用的button为例介绍一下:

local button = UnityEngine.UI.Button()

local btnHandler = function() print("test click") end
button.onClick:AddListener(btnHandler)

以上代码可能是大家比较熟悉的,那么AddListener究竟做了哪些工作呢,我们看一下:
public class UnityEngine_UI_Button_ButtonClickedEventWrap
{
public static void Register(LuaState L)
{
L.BeginClass(typeof(UnityEngine.UI.Button.ButtonClickedEvent), typeof(UnityEngine.Events.UnityEvent));
L.RegFunction("New", _CreateUnityEngine_UI_Button_ButtonClickedEvent);
L.RegFunction("__tostring", ToLua.op_ToString);
L.EndClass();
}


在看一下UnityEngine_Events_UnityEventWrap这个类
public class UnityEngine_Events_UnityEventWrap
{
public static void Register(LuaState L)
{
L.BeginClass(typeof(UnityEngine.Events.UnityEvent), typeof(UnityEngine.Events.UnityEventBase));
L.RegFunction("AddListener", AddListener);
L.RegFunction("RemoveListener", RemoveListener);
L.RegFunction("Invoke", Invoke);
L.RegFunction("New", _CreateUnityEngine_Events_UnityEvent);
L.RegFunction("__tostring", ToLua.op_ToString);
L.EndClass();
}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int AddListener(IntPtr L)
{
try
{
ToLua.CheckArgsCount(L, 2);
UnityEngine.Events.UnityEvent obj = (UnityEngine.Events.UnityEvent)ToLua.CheckObject(L, 1);
UnityEngine.Events.UnityAction arg0 = (UnityEngine.Events.UnityAction)ToLua.CheckDelegate(L, 2);
obj.AddListener(arg0);
return 0;
}
catch (Exception e)
{
return LuaDLL.toluaL_exception(L, e);
}
}


继续往下看:
public static Delegate CheckDelegate(IntPtr L, int stackPos)
        {
            LuaTypes luatype = LuaDLL.lua_type(L, stackPos);

            switch (luatype)
            {
                case LuaTypes.LUA_TNIL:
                    return null;
                case LuaTypes.LUA_TFUNCTION:
                    LuaFunction func = ToLua.ToLuaFunction(L, stackPos);
                    return DelegateTraits.Create(func);
                case LuaTypes.LUA_TUSERDATA:
                    return (Delegate)ToLua.CheckObject(L, stackPos, typeof(T));
                default:
                    LuaDLL.luaL_typerror(L, stackPos, TypeTraits.GetTypeName());
                    return null;
            }
        }


class UnityEngine_Events_UnityAction_Event : LuaDelegate
{
public UnityEngine_Events_UnityAction_Event(LuaFunction func) : base(func) { }
public UnityEngine_Events_UnityAction_Event(LuaFunction func, LuaTable self) : base(func, self) { }

public void Call()
{
func.Call();
}

public void CallWithSelf()
{
func.BeginPCall();
func.Push(self);
func.PCall();
func.EndPCall();
}
}

接着就会走到这里:
public class LuaDelegate
    {
        public LuaFunction func = null;
        public LuaTable self = null;
        public MethodInfo method = null; 

        public LuaDelegate(LuaFunction func)
        {
            this.func = func;
        }

        public LuaDelegate(LuaFunction func, LuaTable self)
        {
            this.func = func;
            this.self = self;
        }

        //如果count不是1,说明还有其他人引用,只能等待gc来处理
        public virtual void Dispose()
        {
            method = null;

            if (func != null)
            {
                func.Dispose(true);
                func = null;
            }

            if (self != null)
            {
                self.Dispose(true);
                self = null;
            }
        }

 从上面看下来如果当button被删除之后如果onClick也就是UnityEngine.UI.Button.ButtonClickedEvent这个委托最后没有被删除的话,那么之前AddListener放进去的luafunc就永远不会删除
 那么他会存在哪里呢,看如下代码:
 public static LuaFunction ToLuaFunction(IntPtr L, int stackPos)
        {
            LuaTypes type = LuaDLL.lua_type(L, stackPos);

            if (type == LuaTypes.LUA_TNIL)
            {
                return null;
            }

            stackPos = LuaDLL.abs_index(L, stackPos);
            LuaDLL.lua_pushvalue(L, stackPos);
            int reference = LuaDLL.toluaL_ref(L);
            return LuaStatic.GetFunction(L, reference);
        }


 public static LuaFunction GetFunction(IntPtr L, int reference)
        {
            LuaState state = LuaState.Get(L);
            return state.GetFunction(reference);
        }


 public LuaFunction GetFunction(int reference)
        {
            LuaFunction func = TryGetLuaRef(reference) as LuaFunction;

            if (func == null)
            {                
                func = new LuaFunction(reference, this);
                funcRefMap.Add(reference, new WeakReference(func));
                if (LogGC) Debugger.Log("Alloc LuaFunction name , id {0}", reference);      
            }

            RemoveFromGCList(reference);
            return func;
        }

这样就很清晰的看到这个func被放到了funcRefMap中,而这个funcRefMap的话是在LuaState中,具体代码如下:
public class LuaState : LuaStatePtr, IDisposable
    {
        public ObjectTranslator translator = new ObjectTranslator();
        public LuaReflection reflection = new LuaReflection();

        public int ArrayMetatable { get; private set; }
        public int DelegateMetatable { get; private set; }
        public int TypeMetatable { get; private set; }
        public int EnumMetatable { get; private set; }
        public int IterMetatable { get; private set; }        
        public int EventMetatable { get; private set; }

        //function ref                
        public int PackBounds { get; private set; }
        public int UnpackBounds { get; private set; }
        public int PackRay { get; private set; }
        public int UnpackRay { get; private set; }
        public int PackRaycastHit { get; private set; }        
        public int PackTouch { get; private set; }

        public bool LogGC 
        {
            get
            {
                return beLogGC;
            }

            set
            {
                beLogGC = value;
                translator.LogGC = value;
            }
        }

        public Action OnDestroy = delegate { };
        
        Dictionary funcMap = new Dictionary();
        Dictionary funcRefMap = new Dictionary();
        Dictionary delegateMap = new Dictionary();

然而他们的释放却是在LuaState的Collect方法中:
protected void Collect(int reference, string name, bool beThread)
        {
            if (beThread)
            {
                WeakReference weak = null;

                if (name != null)
                {
                    funcMap.TryGetValue(name, out weak);

                    if (weak != null && !weak.IsAlive)
                    {
                        funcMap.Remove(name);
                        weak = null;
                    }
                }
                
                funcRefMap.TryGetValue(reference, out weak);

                if (weak != null && !weak.IsAlive)
                {
                    ToLuaUnRef(reference);
                    funcRefMap.Remove(reference);
                    delegateMap.Remove(reference);

                    if (LogGC)
                    {
                        string str = name == null ? "null" : name;
                        Debugger.Log("collect lua reference name {0}, id {1} in thread", str, reference);
                    }
                }
            }
            else
            {
                if (name != null)
                {
                    WeakReference weak = null;
                    funcMap.TryGetValue(name, out weak);
                    
                    if (weak != null && weak.IsAlive)
                    {
                        LuaBaseRef lbr = (LuaBaseRef)weak.self;

                        if (reference == lbr.GetReference())
                        {
                            funcMap.Remove(name);
                        }
                    }
                }

                ToLuaUnRef(reference);
                funcRefMap.Remove(reference);
                delegateMap.Remove(reference);

                if (LogGC)
                {
                    string str = name == null ? "null" : name;
                    Debugger.Log("collect lua reference name {0}, id {1} in main", str, reference);
                }
            }
        }
当然了,如果不满足gc条件那么这些func都会一直在内存中的,所以当使用完委托的时候一定要注意释放,不然如果luafun中包含其他的lua对象(lua闭包),那么都不会被释放掉的,当界面关闭的时候就要清理委托事件,如下:
button.onClick:RemoveAllListeners(),从逻辑上讲这就可以了,如果按钮不被点击,那么这是ok的,但是的我们在项目中发现了ugui有个bug,如果按钮被点击过,也就是说Invoke被触发了,那么这个委托怎么删除都删除不了,后来通过反编译工具看到UnityEventBase源码才发现,在执行invoke的时候委托会被缓存,只有下次再执行invoke的时候才被删除,具体源码如下:

namespace UnityEngine.Events
{
  internal class InvokableCallList
  {
    private readonly List m_PersistentCalls = new List();
    private readonly List m_RuntimeCalls = new List();
    private readonly List m_ExecutingCalls = new List();
    private bool m_NeedsUpdate = true;

    public int Count
    {
      get
      {
        return this.m_PersistentCalls.Count + this.m_RuntimeCalls.Count;
      }
    }

    public void AddPersistentInvokableCall(BaseInvokableCall call)
    {
      this.m_PersistentCalls.Add(call);
      this.m_NeedsUpdate = true;
    }

    public void AddListener(BaseInvokableCall call)
    {
      this.m_RuntimeCalls.Add(call);
      this.m_NeedsUpdate = true;
    }

    public void RemoveListener(object selfObj, MethodInfo method)
    {
      List baseInvokableCallList = new List();
      for (int index = 0; index < this.m_RuntimeCalls.Count; ++index)
      {
        if (this.m_RuntimeCalls[index].Find(selfObj, method))
          baseInvokableCallList.Add(this.m_RuntimeCalls[index]);
      }
      this.m_RuntimeCalls.RemoveAll(new Predicate(baseInvokableCallList.Contains));
      this.m_NeedsUpdate = true;
    }

    public void Clear()
    {
      this.m_RuntimeCalls.Clear();
      this.m_NeedsUpdate = true;
    }

    public void ClearPersistent()
    {
      this.m_PersistentCalls.Clear();
      this.m_NeedsUpdate = true;
    }

    public void Invoke(object[] parameters)
    {
      if (this.m_NeedsUpdate)
      {
        this.m_ExecutingCalls.Clear();
        this.m_ExecutingCalls.AddRange((IEnumerable) this.m_PersistentCalls);
        this.m_ExecutingCalls.AddRange((IEnumerable) this.m_RuntimeCalls);
        this.m_NeedsUpdate = false;
      }
      for (int index = 0; index < this.m_ExecutingCalls.Count; ++index)
        this.m_ExecutingCalls[index].Invoke(parameters);
    }
  }
}

从上面代码中可以看出m_ExecutingCalls从来没有被清楚过,除非再次调用invoke,于是我们就想出了一个方案就是,在执行完button.onClick:RemoveAllListeners()之后再调用一下button.onClick:Invoke(),这样可以完美解决,上面的bug导致的内存泄漏,后来笔者又发现了一个方法也是可以达到同样的效果:

监听的时候:
button.onClick:AddListener(UnityEngine.Events.UnityAction( btnHandler))

销毁的时候:
button.onClick:RemoveAllListeners()

剩下的其他组件也是有以上两种方法处理,我们项目是这样处理的,具体代码如下:

--清理按钮监听事件
   if self.__btnList then 
       for k,v in pairs(self.__btnList) do 
          if not tolua.isnull(v) then 
             v.onClick:RemoveAllListeners()
             v.onClick:Invoke()
          end
       end
   end

   --清理toggle相关事件
   if self.__toggleList then 
       for k,v in pairs(self.__toggleList) do 
          if not tolua.isnull(v) then 
             v.onValueChanged:RemoveAllListeners()
             v.onValueChanged:Invoke(false)
          end
       end
   end

   --清理scrollRect相关事件
   if self.__scrollList then 
       for k,v in pairs(self.__scrollList) do 
          if not tolua.isnull(v) then 
             v.onValueChanged:RemoveAllListeners()
             v.onValueChanged:Invoke(UnityEngine.Vector2.zero)
          end
       end
   end

   --清理InputField相关事件
   if self.__inputFieldList then 
       for k,v in pairs(self.__inputFieldList) do 
          if not tolua.isnull(v) then 
             v.onEndEdit:RemoveAllListeners()
             v.onValueChanged:RemoveAllListeners()
             v.onEndEdit:Invoke("")
             v.onValueChanged:Invoke("")
          end
       end
   end

   --清理slider相关事件
   if self.__sliderList then 
       for k,v in pairs(self.__sliderList) do 
          if not tolua.isnull(v) then 
             v.onValueChanged:RemoveAllListeners()
             v.onValueChanged:Invoke(0)
          end
       end
   end

最后再说一种情况,就是自定义的比如:
public Action onPointerClickEvent;

那么这一类属于系统的delegate,需要这样处理,
local luaClick= onPointerClickEvent

添加事件:
luaClick = btnHandler

销毁的时候:
luaClick:Destroy()

目前为止已经写完了,所有以上都可以通过打印验证,具体代码如下:

foreach (var item in funcMap)
            {
                LuaBaseRef temp = item.Value.Target as LuaBaseRef;
                if (temp != null)
                    Debugger.LogError("funcMap::::"+item.Key+"-----------"+ (temp.name != null ? temp.name : "null"));
            }

            foreach (var item in funcRefMap)
            {
                LuaBaseRef temp = item.Value.Target as LuaBaseRef;
                if (temp != null)
                    Debugger.LogError("funcRefMap::::" + item.Key + "-----------" + (temp.name != null ? temp.name : "null"));
            }

            foreach (var item in delegateMap)
            {
                LuaBaseRef temp = item.Value.Target as LuaBaseRef;
                if (temp != null)
                {
                    Debugger.LogError("delegateMap::::" + item.Key + "-----------" + (temp.name != null ? temp.name : "null"));
                }
            }








  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值