ulua、tolua原理解析


 

在聊ulua、tolua之前,我們先來看看Unity熱更新相關知識。

什么是熱更新

舉例來說: 游戲上線后,玩家下載第一個版本(70M左右或者更大),在運營的過程中,如果需要更換UI顯示,或者修改游戲的邏輯,這個時候,如果不使用熱更新,就需要重新打包,然后讓玩家重新下載(浪費流量和時間,體驗不好)。 熱更新可以在不重新下載客戶端的情況下,更新游戲的內容。 熱更新一般應用在手機網游上。

為什么要用lua做熱更新

其實C#本身的反射機制可以實現熱更新,但是在ios平台上:

System.Reflection.Assembly.Load
System.Reflection.Emit
System.CodeDom.Compiler

無法使用,而動態載入dll或者cs的方法就這幾個,因此在ios下不能動態載入dll或者cs文件(已經編譯進去的沒事),就把傳統dotnet動態路徑封死了。

所以,只能通過把lua腳本打進ab包,玩家通過解壓ab包來更新游戲邏輯和游戲界面。

lua熱更技術

  • ulua & tolua
  • xlua
  • slua

lua熱更新流程

原理

使用assetbundle進行資源的更新,而由於lua運行時才編譯的特性,所以lua文件也可以被看成是一種資源文件(與fbx、Image等一樣)可以打進ab包中。

流程

  1. 對比files清單文件
  2. 更新文件
  3. 解壓AB包中的資源
  4. 初始化

游戲運行時從服務器下載files.txt清單文件,與本地的files.txt清單文件進行對比。如果新下載的files里面的md5值與本地files的md5值不一樣,或者本地清單里根本沒有這個文件就從服務器下載這個ab包到PersistentDataPath文件夾(可讀寫)中。下載完畢后解開AB包中的資源,然后完成初始化。

ulua&tolua原理解析

既然使用了lua作為熱更腳本,那肯定避免不了lua和C#之間的交互。

C#調用lua

C#調用lua的原理是lua的虛擬機,具體步驟可參見我的博客

也可以看看簡單的示例:

private string script = @"
            function luaFunc(message)
                print(message)
                return 42
            end
        ";
void Start () {
        LuaState l = new LuaState();
        l.DoString(script);
        LuaFunction f = l.GetFunction("luaFunc");
        object[] r = f.Call("I called a lua function!");
        print(r[0]);
}

lua調用C#

反射

舊版本的ulua中lua調用C#是基於C#的反射。

C#中的反射使用Assembly定義和加載程序集,加載在程序集清單中列出模塊,以及從此程序集中查找類型並創建該類型的實例。

反射用到的命名空間:

System.Reflection
System.Type
System.Reflection.Assembly

反射用到的主要類:

  • System.Type 類-通過這個類可以訪問任何給定數據類型的信息。
  • System.Reflection.Assembly類-它可以用於訪問給定程序集的信息,或者把這個程序集加載到程序中。

ulua反射調用C#示例:

 private string script = @"
            luanet.load_assembly('UnityEngine') 
            luanet.load_assembly('Assembly-CSharp')
           GameObject = luanet.import_type('UnityEngine.GameObject')        
           ParticleSystem = luanet.import_type('UnityEngine.ParticleSystem')         
   
            local newGameObj = GameObject('NewObj')
            newGameObj:AddComponent(luanet.ctype(ParticleSystem))
        ";

//反射調用
void Start () {
        	LuaState lua = new LuaState();
        	lua.DoString(script);
        }

可看到通過反射(System.Reflection.Assembly)把UnityEngine程序集加入到lua代碼中,通過反射(System.Type)把Unity.GameObject和Unity.ParticleSystem類型加入到lua代碼中,這樣我們便可以在lua中像在C#里一樣調用Unity定義的類。

去反射

現版本的ulua(tolua)中lua調用C#是基於去反射。

去反射的意思是:

把所有的c#類的public成員變量、成員函數,都導出到一個相對應的Wrap類中,而這些成員函數通過特殊的標記,映射到lua的虛擬機中,當在lua中調用相對應的函數時候,直接調用映射進去的c# wrap函數,然后再調用到實際的c#類,完成調用過程。

具體調用過程可參考: Unity3d ulua c#與lua交互+wrap文件理解

因為反射在效率上存在不足,所以通過wrap來提升性能。但是因為wrap需要自己去wrap,所以在大版本更新是可以用到的,小版本更新還是使用反射。

C#與Lua數據交互(lua虛擬棧)

C#與lua的數據交互是基於一個Lua先進后出的虛擬棧:image

(1)若Lua虛擬機堆棧里有N個元素,則可以用 1 ~ N 從棧底向上索引,也可以用 -1 ~ -N 從棧頂向下索引,一般后者更加常用。

(2)堆棧的每個元素可以為任意復雜的Lua數據類型(包括table、function等),堆棧中沒有元素的空位,隱含為包含一個“空”類型數據

(3)TValue stack[max_stack_len] // 定義在 lstate.c 的stack_init函數

關於Lua虛擬棧入棧的具體操作做可以見下圖:image

更詳細的可見: Lua初學者(四)–Lua調用原理展示(lua的堆棧)

C#與Lua通信(P/Invoke)

  • 所有的通信都是基於P/Invoke模式(性能低)類似JNI
  • P/Invoke:公共語言運行庫(CLR)的interop功能(稱為平台調用(P/Invoke))
  • 命名空間:System.Runtime.InteropServices

示例:

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr luaL_newstate();

P/Invoke 要求方法被聲明為 static。

P/Invoke性能:image

為啥P/Invoke看起來這么慢?

(1)尋址方式:調用時指定了CharSet=CharSet.Ansi 那么CLR首先在非托管的DLL中尋找,若找不到,就用帶A后綴的函數進行搜索造成開銷,可將ExactSpelling的值設為true防止CLR通過修改入口名稱進行搜索。

(2)類型轉換:在Managed Code和Native Code間傳遞參數和返回值的過程成為marshalling。托管函數每次調用非托管函數時,都要求執行以下幾項操作:

  • 將函數調用參數從CLR封送到本機類型。
  • 執行托管到非托管形式轉換。
  • 調用非托管函數(使用參數的本機版本)
  • Interop在進行封送時候,對bittable可以不進行拷貝,而是直接內存錨定。
  • 將返回類型及任何“out”或“in,out”參數從本機類型封送到 CLR 類型。

(3)VC++ 提供自己的互操作性支持,這稱為 C++ Interop。 C++ Interop 優於 P/Invoke,因為 P/Invoke 不具有類型安全性,參數傳遞還需要做類型檢查。

Bittable類型(byte,int,uint)與非Bittable類型(char, boolean,array,class)

參考書: NET互操作 P_Invoke,C++Interop和COM Interop.pdf

ulua的優化方式匯總

  • BinderLua太多wrap很慢(反射與去反射共存)
  • Lua代碼打入AssetBundle為了繞過蘋果檢測
  • 動態注冊Wrap文件到Lua虛擬機(tolua延伸)
  • ToLuaExport. memberFilter的函數過濾
  • 盡量減少c#調用lua的次數來做主題優化思想
  • 盡量使用lua中的容器table取代c#中的所有容器
  • 例子CallLuaFunction_02里附帶了no gc alloc調用方式
  • Lua的bytecode模式性能要低於Lua源碼執行
  • 取消動態參數:打開LuaFunction.cs文件,找到函數聲明:
public object[] Call(params object[] args){
    return call(args, null);
}

取消動態參數args,可用較笨方法,就是定義6-7個默認參數,不夠再加。

  • 安卓平台如果使用luajit的話,記得在lua最開始執行的地方請開啟 jit.off(),性能會提升N倍。
  • 記得安卓平台上在加上jit.opt.start(3),相當於c++程序-O3,可選范圍0-3,性能還會提升。Luajit作者建議-O2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM