• Unity Ulua1.03优化记录


    现在项目的框架是在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对函数的调用,主要流程:

    1. 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; 
     }
    
    1. 在使用到注册对象的类型时,通过反射获取该方法并执行 ,具体可以查看 MetaFunctions.getMember()函数的实现。

    C#的反射机制为什么慢,可考:C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题,慢可以接受,但还有一点是产生一堆临时对象,也就是会有GC存在。如果在框架层次严格的控制Lua和C#的交互时机和接口标准,完全也可以满足游戏需求。但是在多人开发中慢慢的就会走向另外一个方向,接口变多,标准不在。对于在大量在Lua层操作Unity对象的框架,在加上快速开发中的不关注,后期游戏的交互的性能估计更严重。

    Ulua的后续版本中就增加了可以避免取消反射机制,即采用Lua全局注册的方式使用C#方法。具体流程:

    1. 定义需要导出的类
    public static BindType[] binds = new BindType[]
    {
         _GT(typeof(DebugExport)),
    }
    
    1. 通过自动化工具生成需要的注册文件(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;
    }
    
    
    1. 游戏启动时首先将注册文件中的方法全部注册到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

  • 相关阅读:
    [RxJS] sample
    [Whole Web] SQL INJECTION
    [RxJS] Using iif
    [React] Animate SVG Paths with Framer Motion
    bash 教程5 shell 流程控制 条件判断 重定向 read [MD]
    bash 教程4 shell 脚本 调试 环境 [MD]
    『前端算法』数组合并两个有序数组
    块设备读写测试
    IO栈整体认知
    MySQL [Warning] Can’t create test file xxx lowertest(转)
  • 原文地址:https://www.cnblogs.com/zsb517/p/6428832.html
Copyright © 2020-2023  润新知