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,会一定程度上影响性能的,所以个人感觉没必要。