加密 lua_三、Lua相关知识
一、lua語言基礎
1、metatable
2、pairs、ipairs、table.sort
3、table的內存(數組結構和哈希結構)
4、字符串緩存(字符串常量是共享的。這個5.3版本有調整,40字節一下的短字符串才是共享的,長字符串還是保持獨立內存)。所以配置文件中存在大量重復的字符串并不是很耗內存。
反而是配置中存在大量的數字或者是嵌套的table的時候,非常耗內存。
5、lua本身的協程不支持常用的 WaitForSeconds 的功能。xlua通過Coroutine_Runner.cs 這個文件實現了這個功能。
二、lua和C#如何進行交互
1、通過lua state堆棧進行交互。
2、C#通過 lua_pushnumber 、lua_pushboolean、lua_pushstring、lua_pushlstring等接口傳遞參數。然后通過lua_pcall 調用函數。
3、lua調用C#,在C#的wrap函數中,通過lua_tonumber、lua_tostring等接口獲取參數。執行后的結果可以通過lua_pushXXX 返回給lua。
4、lua_pushstring 傳遞一個字符串給lua。內部會使用strlen計算字符串長度。0結尾。
lua_pushlstring 傳遞一個buffer給lua。指定長度。
5、userdata。C#或者C++的類對象傳遞給lua,使用的是userdata。在XLua中ObjectTranslator就負責維護這些userdata。
6、C#怎么導出類型給lua的。
通過Registry (注冊表,LUA_REGISTRYINDEX)。這是一個全局table。只能被C代碼訪問。xlua把CS這個對象放在LUA_REGISTRYINDEX上,并通過生成的wrap文件,把所有C#類型都放在CS對象上。于是lua中就可以通過 http://CS.XXX訪問C#的對象了。比如CS.UnityEngine.GameObject或者CS.Actor。
7、C#怎么調用lua的函數。
函數應該通過字符串名字獲取到,或者通過lua使用參數傳遞過來。這個LuaFunction對象可以保存起來。
通過lua_getref把函數放到棧頂,通過lua_pushXXX把參數壓棧,然后通過lua_pcall執行函數。如果有返回值則通過lua_toXXX在-1(棧頂位置)獲取到lua的返回值,在xlua中返回的一般都是TResult對象。最后通過lua_settop恢復棧頂。
8、通過luaState.AddBuildin接口,可以添加C模塊或者自定義模塊。
三、lua面向對象
1、通過metatable實現class
2、lua訪問一個table會先看有沒有對應的字段(可以是變量或者函數),如果沒有的話,會查找metable的__index。這個索引可以是一個function,也可以是一個table。如果__index也無法取到對應的變量則返回nil。
另外一個是__newindex。它會在給table中不存在的字段賦值的時候調用。
3、class會返回一個table。它的metatable會指定為父類類型。這個返回的table就是一個lua中定義的類型了,我們會給它添加各種函數定義。
當new的時候,會返回一個新的table,它的metatable會設置為class返回的這個類型table。這樣當訪問一個函數的時候,就會從當前對象的table--》類型table--》父類的table,這樣的順序去查找函數。從而實現面向對象中常用的多態(override)。
4、另外需要注意,我們只有函數是放在類型table上的,這個是所有對象共用的。而變量字段是通過ctor的遞歸調用直接賦值到對象table上的。也就是說變量是歸對象的,函數是歸類型的。
這么設計符合直覺,有利于減少內存,也不用擔心父子類之間變量沖突。
5、調用父類的正確寫法。比如 Hero 繼承自 Player 繼承自 Animal
self.super.Move(self, x, y, z)。而不能直接 self.super:Move(x, y, z)。因為后者由于lua的語法糖,傳遞給Move的參數是super,而實際上這個時候我們期望傳遞的是self。
當有三層繼承的調用的時候,上述寫法也是有問題的。因為第二層調用依然是self.super.Move,會形成死循環。這個時候正確的調用方式是使用類型名調用。比如 Player.Move(self, x, y, z)。
四、實際項目經驗
1、使用luajit2.1的版本。luajit性能很高,比lua可能高5~10倍。但是也要小心極端情況下可能會有jit失效的問題。這個時候代碼無法jit,而又重復嘗試jit,反而會導致性能降低。
2、luajit對應的lua版本是5.1。支持少量5.2的功能。不支持5.3的int。不支持_Env。
3、luajit在iOS下沒有開啟jit功能。因為蘋果政策問題,關鍵api沒有權限調用。不過多數情況luajit依然要比lua快,所以在iOS下我們也是使用luajit。
4、ios下編譯的是64位bytecode。不兼容32位的cpu。所以iPad1,iPhone5及以下的機型要么舍棄掉。要么同時再編譯一份32位的bytecode,運行時根據機器cpu決定讀取哪個腳本文件。我們的選擇是放棄iPad1這樣的老舊設備。
5、編輯器下使用luajit,總是會導致編輯器崩潰。原因未知。換成lua5.1的版本就好了。
也就是說我們對外發布使用的是luajit,編輯器下使用的是lua5.1。
6、我們lua代碼做了幾層加密。luajit編譯為bytecode是一層,aes加密是一層,lz4壓縮是一層。
因為aes和lz4都是非常快的算法,尤其是在讀取的時候,所以性能上問題不大。
7、我們之前的lua代碼傾向是打包在固定的幾個文件的ab包內。不過后來隨著代碼量的逐漸增大,gui.ab、config.ab和logic.ab越來越大。更新一個版本補丁最小也要八九兆。后面這三個目錄還是按照子目錄打包。目的是減少更新補丁的大小。
8、隨著代碼量增大,我們打包的時候編譯lua可能要占用三分鐘時間。所以打包的時候維護了一個當前lua.bytes的緩存,記錄了每個lua文件的md5。如果md5一致的話,就不重新編譯。這樣節約了打包的時間。
五、Lua代碼熱加載(Hot Reload)
1、熱加載不同于熱更新,服務器可以用來不關服修正線上問題(動態更新),客戶端主要方便開發時不關閉游戲就重載更新后的代碼,提高開發效率。
2、檢測哪些文件發生改變(被修改了),這個原本使用的是C#的FileSystemWatcher,不過貌似很不穩定。經常導致Unity卡死,原因未知。
也考慮過做一個VSCode的插件,然后使用socket通知Unity,不過感覺方案比較復雜,別人使用起來也比較復雜。
最后的解決方案是使用Go(或其他語言)實現一個獨立的監控文件改變的進程。Unity開啟新進程監控文件變化。進程之間通過stdout的返回值進行通信。最終可以準確識別的文件的改變,也不會導致Unity卡死。
3、獲取到的變化文件列表,不要在子線程處理。統一丟到主線程通知lua處理。
4、package.loaded[xxx] = nil。先把已加載文件的緩存清空。
5、重新require文件,替換保存的upvalue。
6、最終達成的效果是,某個函數添加了日志或者修改了邏輯,會自動修改生效,而不用重啟游戲。
7、補充說明的是,只有全局變量有這么處理的必要。像我們的界面是local變量,那么只要關掉界面的時候把 package.loaded 置空,再次打開界面就可以重新加載修改后的文件。
六、Lua的調試
1、不同的IDE插件(VSCode+luaidelite,或者IDEA+EmmyLua),有不同的LuaDebug.lua的代碼。
但是本質上都是游戲運行時開啟一個socket,設置斷點的時候就直接sleep掉主線程。等插件繼續運行游戲的時候,就是socket通知游戲,取消sleep。
2、各種變量信息可以通過 debug.getinfo 獲取。
七、Lua的性能分析
1、推薦 Miku-LuaProfiler。提供了可視化的Unity窗口界面,看著非常直觀。
2、本質上是使用 debug.sethook 監控函數的執行,在開始和結束的位置打點,最后統計分析哪些是耗時函數。
3、內存分析
3.1、善用 collectgarbage("count"),獲取當前的lua內存。
3.2、做內存分析之前,先執行 collectgarbage("stop"),停止GC,否則運行過程中可能觸發gc導致數據不準確。
3.3、在切換場景或者其他必要情景,執行 collectgarbage("collect"),進行gc。
總結
以上是生活随笔為你收集整理的加密 lua_三、Lua相关知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bigdicmal除法精度设置_BigD
- 下一篇: python求最大值最小值_Python