unity3d性能优化(tolua篇)

tolua篇:
我们目前开发采用的是tolua,大家可能都知道c#到lua是需要导出接口给lua调用的,只要lua获取unity的对象,那么tolua都会根据id(唯一)保存在 LuaState中的 ObjectTranslator 中,每个lua变量都是唯一对应的,参考图
#define MISS_WARNING

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Text;
using UnityEngine;

namespace LuaInterface
{
    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; }


具体可以参考tolua中的类ObjectTranslator.cs,
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace LuaInterface
{
    public class ObjectTranslator
    {        
        private class DelayGC
        {
            public DelayGC(int id, UnityEngine.Object obj, float time)
            {
                this.id = id;
                this.time = time;
                this.obj = obj;
            }

            public int id;
            public UnityEngine.Object obj;
            public float time;
        }

        private class CompareObject : IEqualityComparer
        {
            public new bool Equals(object x, object y)
            {
                return object.ReferenceEquals(x, y);                
            }

            public int GetHashCode(object obj)
            {
                return RuntimeHelpers.GetHashCode(obj);                
            }
        }

        public bool LogGC { get; set; }
        public readonly Dictionary objectsBackMap = new Dictionary (new CompareObject());
        public readonly LuaObjectPool objects = new LuaObjectPool();
        private List gcList = new List();

#if !MULTI_STATE
        private static ObjectTranslator _translator = null;
#endif

        public ObjectTranslator()
        {
            LogGC = false;
#if !MULTI_STATE
            _translator = this;
#endif
        }

        public int AddObject(object obj)
        {
            int index = objects.Add(obj);

            if (!TypeChecker.IsValueType(obj.GetType()))
            {
                objectsBackMap[obj] = index;
            }

            return index;
        }

        public static ObjectTranslator Get(IntPtr L)
        {
#if !MULTI_STATE
                return _translator;
#else
                return LuaState.GetTranslator(L);
#endif
        }

        //fixed 枚举唯一性问题(对象唯一,没有实现__eq操作符)
        void RemoveObject(object o, int udata)
        {
            int index = -1;
            
            if (objectsBackMap.TryGetValue(o, out index) && index == udata)
            {
                objectsBackMap.Remove(o);
            }
        }

        //lua gc一个对象(lua 库不再引用,但不代表c#没使用)
        public void RemoveObject(int udata)
        {            
            //只有lua gc才能移除
            object o = objects.Remove(udata);

            if (o != null)
            {
                if (!TypeChecker.IsValueType(o.GetType()))
                {
                    RemoveObject(o, udata);
                }

                if (LogGC)
                {
                    Debugger.Log("gc object {0}, id {1}", o, udata);
                }
            }
        }

        public object GetObject(int udata)
        {
            return objects.TryGetValue(udata);         
        }

        //预删除,但不移除一个lua对象(移除id只能由gc完成)
        public void Destroy(int udata)
        {            
            object o = objects.Destroy(udata);

            if (o != null)
            {
                if (!TypeChecker.IsValueType(o.GetType()))
                {
                    RemoveObject(o, udata);
                }

                if (LogGC)
                {
                    Debugger.Log("destroy object {0}, id {1}", o, udata);
                }
            }
        }

        //Unity Object 延迟删除
        public void DelayDestroy(int id, float time)
        {
            UnityEngine.Object obj = (UnityEngine.Object)GetObject(id);

            if (obj != null)
            {
                gcList.Add(new DelayGC(id, obj, time));
            }            
        }

        public bool Getudata(object o, out int index)
        {
            index = -1;
            return objectsBackMap.TryGetValue(o, out index);
        }

        public void Destroyudata(int udata)
        {
            objects.Destroy(udata);
        }

        public void SetBack(int index, object o)
        {
            objects.Replace(index, o);            
        }

        bool RemoveFromGCList(int id)
        {
            int index = gcList.FindIndex((p) => { return p.id == id; });

            if (index >= 0)
            {
                gcList.RemoveAt(index);
                return true;                       
            }

            return false;
        }
        
        //延迟删除处理
        void DestroyUnityObject(int udata, UnityEngine.Object obj)
        {
            object o = objects.TryGetValue(udata);

            if (object.ReferenceEquals(o, obj))
            {
                RemoveObject(o, udata);
                //一定不能Remove, 因为GC还可能再来一次
                objects.Destroy(udata);     

                if (LogGC)
                {
                    Debugger.Log("destroy object {0}, id {1}", o, udata);
                }
            }

            UnityEngine.Object.Destroy(obj);
        }

        public void Collect()
        {
            if (gcList.Count == 0)
            {
                return;
            }

            float delta = Time.deltaTime;

            for (int i = gcList.Count - 1; i >= 0; i--)
            {
                float time = gcList[i].time - delta;

                if (time <= 0)
                {
                    DestroyUnityObject(gcList[i].id, gcList[i].obj);                    
                    gcList.RemoveAt(i);
                }
                else
                {
                    gcList[i].time = time;
                }
            }
        }

        public void Dispose()
        {
            objectsBackMap.Clear();
            objects.Clear();     
            
#if !MULTI_STATE
            _translator = null;
#endif
        }
    }
}
那么问题来了,当对应的c#对象被释放的时候,如果lua中还有引用,那么c#对象将释放不掉。目前大部分项目做法应该是用c#驱动lua,举一个简单的例子:
using UnityEngine;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using LuaInterface;

public class LuaComponent : MonoBehaviour
{

    ///
    /// Lua文件的路径 //
    ///
    public string luaFilePath;


    ///
    /// 对应的LuaTable //
    ///
    [HideInInspector]
    public LuaTable luaTable;

    [HideInInspector]
    public LuaFunction luaFunction_Awake;


    public LuaTable GetLuaComponent()
    {
        return luaTable;
    }

    void Awake()
    {
        luaTable = LuaState.Require(luaFilePath);
        luaFunction_Awake = luaTable.GetLuaFunction("Awake");
        CallLuaFunction(luaFunction_Awake)
    }

    void OnDestroy()
    {
        ClearLua();
    } 

    ///
    /// 彻底清理lua
    ///
    protected void ClearLua()
    {
    
        if (luaFunction_Awake != null)
        {
            luaFunction_Awake.Dispose();
            luaFunction_Awake = null;
        }
        
        if (luaTable != null)
        {
            luaTable.Dispose();
            luaTable = null;
        }
    }

    public void CallLuaFunction(LuaFunction luaFunction_Temp)
    {
        if(luaFunction_Temp != null)
        {
            luaFunction_Temp.BeginPCall();
            luaFunction_Temp.Push(luaTable);
            luaFunction_Temp.PCall();
            luaFunction_Temp.EndPCall();
        }
    }
}

如果这个脚本挂在一个物体gameobject上,那么上面挂在的lua对象就会被创建,c#中可以看出引用了lua文件中的function,当gameobject被destroy以后,如果这里面的lua对象没有被释放的话,那么lua对象中如果引用了gameObject对象的话,那么gameobject就不会被完全从内存中释放,所以在写框架的时候一定要考虑释放。

下面要说的就是button这一类的监听事件,eg:self.btn_login.onClick:AddListener(function() self:DoSomething() end)了解lua闭包的可能都知道,一旦这个临时function不能被释放,那么可以知道里面的self这个lua对象也将不会被释放,进而这个lua对象中引用的unity对象,比如上面提到的button这个gameObject也将不会被释放的,所以建议这样:
 self. btnHandler = function() end
self.btn_login.onClick:AddListener(self.btnHandler)
当物体被销毁的时候调用self.btn_login.onClick:RemoveListener(self. btnHandler )做后期清理(建议写框架的人写一个统一的方法封装一下)eg:
local Test = {}

function Test:AddListener(btn,btnHandle)
    self.btnMap [btn] = btnHandle
    btn.onClick:AddListener(btnHandle)
end

function Test:ClearBtnListener(  )
    for btn,listener in pairs(self.btnMap) do
        btn.onClick:RemoveListener(listener) 
    end
end
所以这一点在写框架的时候一定要注意,还有一点就是,c#和lua之间的交互,大家知道lua和c#只要交互就会有gc(gc的话c#会遍历所有object,并且清理掉不被引用的object)发生,严重的情况会导致游戏很卡的,开发过程中,个人感觉交互比较多的就是加载资源和网络通信。我之前的做法就是每次发协议或者加载资源都传一个luafunction作为回调,这样的话,每次资源加载完成,都会有luafunction造成的gc,可以注册一个luafunction作为统一的回调,每次资源加载完成只需要统一调用回调即可,所有资源加载的对应关系可以放在lua层处理,这样可以减少很多gc,网络协议亦是如此。总之遵守一个原则,少交互,少gc。

还有一种情况就是在lua中一旦引用unity的object,可以做缓存,省的每次都屌用Find去查找,这也是另外一个地方消耗的地方,大家在使用的时候也是要特别注意的。在做优化的时候,有同事提到要不要把require的lua文件也及时的unload掉,大家都知道lua的require只会执行一次读操写作,然后就常驻内存,这个也是要看文件大小,比如一个配置文件太大,那么可以考虑在不使用的时候及时的unload,其他的逻辑代码,我个人建议不需要每次都清理,这样反而会带来很多gc,会一定程度上影响性能的,所以个人感觉没必要。


你可能感兴趣的:(unity3d性能优化(tolua篇))