现在项目的框架是在2015年设计的,那时候Ulua还处于1.03版本,现在回头再看,Ulua已经迭代到1.25版本,中间引入带有wraper的cstolua,而后转向现有的toLua#版本。
随着版本更新性能提升了很多(当然也看人为因素),代码的清晰度也越来越清晰,但是引入的项目本身可能不需要的东西越来越多,比如Gameobject、vector等等,这些并不在我们现有框架需求的范围内。所以在这次优化过程中并没有直接拿最新的toLua#来替换现在的Lua交互模块,而是将后续版本中能够改善现有性能的部分逐步添加进来,保证框架本身的稳定性。下文主要介绍cstolua版本优化之后的实现方式以及与当前1.03对比。
C#调用 Lua Function--缓存机制
每个版本中对Lua table的均支持通过"."分割操作。
/*
* Navigates a table in the top of the stack, returning
* the value of the specified field
*/
internal object getObject(string[] remainingPath)
{
object returnValue = null;
for (int i = 0; i < remainingPath.Length; i++)
{
LuaDLL.lua_pushstring(L, remainingPath[i]);
LuaDLL.lua_gettable(L, -2);
returnValue = translator.getObject(L, -1);
if (returnValue == null) break;
}
return returnValue;
}
如果你使用的Lua函数层级比较深,同时还要处理一些堆栈操作和校验,获取效率是没有保证的,当然也可以通过保存需要的LuaFunction避免这个问题。在后续版本中增加了LuaTable和LuaFunction缓冲的功能,对使用多的函数或者table保存下来,避免后续重复的查找。
public LuaFunction GetLuaFunction(string name)
{
LuaBase func = null;
if (!dict.TryGetValue(name, out func))
{
IntPtr L = lua.L;
int oldTop = LuaDLL.lua_gettop(L);
if (PushLuaFunction(L, name))
{
int reference = LuaDLL.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);
func = new LuaFunction(reference, lua);
func.name = name;
dict.Add(name, func);
}
else
{
Debugger.LogError("Lua function {0} not exists", name);
}
LuaDLL.lua_settop(L, oldTop);
}
else
{
func.AddRef();
}
return func as LuaFunction;
}
LuaTable获取采用类似的方案,代码不在贴出。
Lua调用C# Function -- 干掉反射
Lua1.03版本中主要使用反射机制实现Lua对函数的调用,主要流程:
- Lua使用时通过luanet.import_type注册需要的对象类型,在C#层通过反射获得Assembly对象,并记录下来
public static int loadAssembly(IntPtr luaState)
{
ObjectTranslator translator = ObjectTranslator.FromState(luaState);
string assemblyName = LuaDLL.lua_tostring(luaState, 1);
Assembly assembly = null;
assembly = Assembly.Load(assemblyName);
if (assembly != null && !translator.assemblies.Contains(assembly))
{
translator.assemblies.Add(assembly);
}
}
return 0;
}
- 在使用到注册对象的类型时,通过反射获取该方法并执行 ,具体可以查看 MetaFunctions.getMember()函数的实现。
C#的反射机制为什么慢,可考:C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题,慢可以接受,但还有一点是产生一堆临时对象,也就是会有GC存在。如果在框架层次严格的控制Lua和C#的交互时机和接口标准,完全也可以满足游戏需求。但是在多人开发中慢慢的就会走向另外一个方向,接口变多,标准不在。对于在大量在Lua层操作Unity对象的框架,在加上快速开发中的不关注,后期游戏的交互的性能估计更严重。
Ulua的后续版本中就增加了可以避免取消反射机制,即采用Lua全局注册的方式使用C#方法。具体流程:
- 定义需要导出的类
public static BindType[] binds = new BindType[]
{
_GT(typeof(DebugExport)),
}
- 通过自动化工具生成需要的注册文件(wraper file),注册文件中实现每个类方法与Lua堆栈的交互过程;类似与下面:
//注册方法,关键在LuaScriptMgr.RegisterLib
public static void Register(IntPtr L)
{
LuaMethod[] regs = new LuaMethod[]
{
new LuaMethod("RegisterLog", RegisterLog),
new LuaMethod("LogError", LogError),
new LuaMethod("LogWarning", LogWarning),
new LuaMethod("Log", Log),
new LuaMethod("ClearLog", ClearLog),
};
LuaField[] fields = new LuaField[]
{
};
LuaScriptMgr.RegisterLib(L, "DebugExport", regs,1);
}
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int LogError(IntPtr L)
{
LuaScriptMgr.CheckArgsCount(L, 1); //检测参数个数
string arg0 = LuaScriptMgr.GetLuaString(L, 1); //获取第一个参数
DebugExport.LogError(arg0); //调用Unity 方法
return 0;
}
- 游戏启动时首先将注册文件中的方法全部注册到Lua全局表中(也就是调用Wraper中Register),这样就后面在Lua中可以直接使用这些方法。
对象 -- 尽量避免开箱和装箱操作
不管是C#调用Lua还是Lua调用C#,不可避免的都会涉及到参数问题。
类型转换 c#中不可绕过的就是对象转换的代价,参考文章:深入理解C#的装箱和拆箱 , OK,频繁的开箱和装箱操作会额外耗费CPU和内存资源,所以在1.03后续版本中引入重载,对不同的数据类型提供不同的接口,同时在函数调用层次也提供一些优化方式来避免,比如讲函数调用开放给编码的人,比如C#层调用Lua层Update函数,带有两个参数,可以这样修改:
void CallUpdate(LuaFunction updateFunc, float deltaTime, float unscaledDeltaTime)
{
int top = updateFunc.BeginPCall();
IntPtr L = updateFunc.GetLuaState();
//带有重载
LuaScriptMgr.Push(L, deltaTime);
//带有重载
LuaScriptMgr.Push(L, unscaledDeltaTime);
updateFunc.PCall(top, 2);
updateFunc.EndPCall(top);
}
当然对于带有返回数据的函数也会存在这样的问题,优化方式类似,提供足够的重载函数来满足需求。当然对于要使用Gameobject、自定义类、委托等等的肯定还是避免不了.
对象生命周期管理方式
tolua#中 对象的生命周期管理和垃圾回收策略做了很大的优化,不过鉴于当前的框架中并没有对象作为参数,所有没有做进一步的修改。
结束语
后续版本中做的优化可能不止这些,但是通过上面的改善以及在逻辑层次注意和规范,应该可以满足现有要求,前提是在现有框架下。得益于现有框架对接口的定义,不存在使用GameObject等Unity内置类型或者委托之类的情况,所以避免了很多比如对象生命周期等问题。
参考:
[1]用好lua+unity,让性能飞起来——lua与c#交互篇 :http://www.cnblogs.com/zwywilliam/p/5999924.html
[2]:ulua中lua代码使用反射调用c#详解 http://blog.csdn.net/wdsdsdsds/article/details/54409936?utm_source=itdadao&utm_medium=referral