原文:http://www.sohu.com/a/123334175_355140sql
做者|車雄生編程
編輯|木環json
騰訊最近在開源方面的動做不斷:先是微信跨平臺基礎組件Mars宣佈開源,騰訊手遊又於近期開源了Unity3D下Lua編程解決方案——xLua。xLua,何方神聖?有哪些技術細節能夠說道說道?c#
寫在前面api
xLua是Unity3D下Lua編程解決方案,自2016年初推廣以來,已經應用於十多款騰訊自研遊戲,因其良好性能、易用性、擴展性而廣受好評。如今 騰訊已經將xLua開源到GitHub。數組
2016年12月末,xLua剛剛實現新的突破:全平臺支持用Lua修復C#代碼bug。目前Unity下的Lua熱更新方案大多都是要求要熱更新的部分一開始就要用Lua語言實現,不足之處在於:性能優化
接入成本高,有的項目已經用C#寫完了,這時要接入須要把須要熱更的地方用Lua從新實現;微信
即便一開始就接入了,也存在同時用兩種語言開發難度較大的問題;框架
Lua性能不如C#;異步
xLua熱補丁技術支持在運行時把一個C#實現(函數,操做符,屬性,事件,或者整個類)替換成Lua實現,意味着你能夠:
平時用C#開發;
運行也是C#,性能秒殺Lua;
有bug的地方下發個Lua腳本fix了,下次總體更新時能夠把Lua的實現換回正確的C#實現,更新時甚至能夠作到不重啓遊戲;
這個新特性iOS,Android,Windows,Mac都測試經過了,目前在作一些易用性優化。那麼,騰訊開源的xLua到底是怎樣的技術?它是爲什麼如此設計的?更使人關心的是,xLua的性能如何?帶着這些問題,InfoQ對其做者進行了採訪並將內容整理成文。
技術背景
騰訊自研手遊,就我瞭解的項目來講,大多數遊戲引擎都是Unity3D,少數用coco2d。
xLua這個插件具體用到了哪些遊戲中?雖然說xLua是2015年3月就完成了第一個版本,但因爲當時項目組熱更的意識並無很廣泛,需求不是很強烈,xLua的開發資源都調到更緊急的項目了。直到15年年末正式集成到咱們的apollo手遊開發框架,才迎來xLua的第一個項目。到目前爲止,咱們已知的應用了xLua的項目有十多個,其中不乏一些重量級IP,或者按星級標準打造的產品。
在xLua以前,面對iOS沒法熱更新的問題,有用ulua的,有用slua的,也有項目用自研的腳本語言,不過當時用人更新的項目也很少。
熱更新流程
手遊的熱更新流程很簡單,只是啓動時檢測下是否有新版本文件,有的話就下載覆蓋老文件,而後啓動。
下載的文件若是是圖片,模型這些是沒問題的,但若是是Unity原生的代碼邏輯,不管是之前的Mono AOT或者後來的il2cpp,都是編譯成native code,iOS下是跑不了的。解決辦法就一個,別用native code,別用jit,解析執行就能夠了。包括xLua在內的全部熱更新支持方案都是經過「解析執行」來實現代碼邏輯熱更新。
來自xLua的 Hello world
三行代碼跑lua腳本
一個完整的例子僅需3行代碼:
下載xLua後解壓到Unity工程Assets目錄下,建一個MonoBehaviour拖到場景,在Start裏頭加上這麼三行:
XLua.LuaEnv luaenv = new XLua.LuaEnv();luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");luaenv.Dispose();
運行就能夠看到Console打印的hello world。
第一和第三行分別LuaEnv的建立以及銷燬,所謂LuaEnv能夠理解爲lua虛擬機,每每整個工程一個虛擬機便可:
DoString裏頭能夠是任意合法的lua代碼,例子中調用了UnityEngine.Debug.Log接口打印了一個log(C#的靜態函數在CS下直接可用);
C#調用lua系統函數math.max
xLua支持把一個Lua函數綁定到C# delegate。
咱們先聲明一個delegate,併爲它加上CSharpCallLua標籤:
[XLua.CSharpCallLua]public delegate double LuaMax(double a, double b);
而後在上面那例子加上這麼兩行(luaenv銷燬前):
var max = luaenv.Global.GetInPath<LuaMax>("math.max");Debug.Log("max:" + max(32, 12));
就那麼簡單,把lua的math.max綁定到C#的max變量後,調用就和一個C#函數調用差很少了,並且,最最重要的是,執行了「XLua/Generate Code」後,max(32, 12)調用是不產生(C#)gc alloc的,既優雅,又高效!(更詳細的能夠看XLuaDoc下的文檔。)
xLua全局觀
易用性:編輯器下無需生成代碼支持全部特性
xLua的易用不只僅體如今編程,還體如今方方面面的細節考慮,甚至考慮到團隊配合工做流。xLua僅有兩個菜單選擇,分別是生成代碼和清除生成代碼。在菜單以外,甚至只須要在build手機版本前執行一下「Generate Code」便可(這也有API可集成到項目的自動化打包流程)。
這就是xLua的特點功能之一:編輯器下無需生成代碼支持全部特性。之因此作這個功能,是由於有的項目反饋,「生成代碼」對於策劃美術太過遙遠,教了好久仍是老忘;還有個大項目反饋說因爲代碼不少,每次生成代碼後,Unity3D都要轉好久。
擴展性:授之以魚,不如授之以漁
開發中咱們每每要用到不少東西,好比用PB和後臺交互,解析json格式的配置文件等等。雖然說咱們均可以在C#那找到相應的庫,而後經過xLua去使用這些庫,但這效率不高,最好能有相應Lua的庫。
很多方案是直接集成一些經常使用的Lua庫,但這帶來些新問題:這些庫不必定用到,卻增大安裝包;集成的庫也不必定符合項目習慣:json解析有人喜歡rapidjson,有人愛用cjson,所謂衆口難調;對於某些項目,這些庫仍是不夠,仍是得本身去想辦法加;
騰訊團隊的設計原則是授之以魚,不如授之以漁,所以xLua:
提供了接口、教程,在不修改xLua代碼的狀況下,開發者能夠根據我的喜愛加入庫;
經過cmake實現跨平臺編譯,能夠選擇伴隨xLua一塊兒編譯,修改一個makefile文件,搞定各平臺編譯。
除了很方便加入第三方Lua插件,xLua的生成引擎支持二次開發,能夠編寫生成插件,生成本身所需的一些代碼以及配置。
性能的保證
遊戲的性能備受關注,所以任何模塊的變化都須要儘量不下降甚至調優遊戲總體的性能。xLua設計原則是在保證運行效率的前提下,儘可能的保證開發效率。
對於性能這塊,有幾個相當重要的版本:
第一個版本1.0.0在05年3月份發佈,當時delegate,interface做爲最主要的C#訪問Lua的設定,從接口層面避免了boxing、unboxing、gc alloc,這是一個良好的起點。作一個通用組件的都知道,接口一開始設計不合理致使的問題很難解決,別人已經用了,甚至已經養成習慣了,很難糾正。
ps:提及這習慣,有的從別的lua插件轉爲使用xLua的童鞋,一開始習慣用LuaFunction.Call去調用lua(xLua也保留了這接口,可用於性能要求不高的場合),他們後期就痛苦了,還得一個個地方的改回來。
第二個很重要的版本是2.0.0(06年3月發佈),這版本主要目標就性能優化,由於當時有個對性能要求極其嚴苛的項目想用lua,嚴苛到什麼程度呢?他們以爲C#性能都不放心,戰鬥系統打算用C++寫。那版本咱們把虛擬機切換到luajit,加入了lazyload技術,逐行語句的優化,甚相當鍵地方不用C#提供的容器,本身寫專用的(比Dictionary實測性能高4倍)。。。能夠認爲咱們重作了一個xLua。最終他們的選型測試結論是選xLua。
後來和一些項目的交流發現,項目組很關注gc alloc這指標,甚至比lua和C#間的互調性能指標還要看重。因而有了2.1.0版本(06年7月發佈),這版本主要目標是gc優化,咱們重寫了反射,反射調用的gc減小到原來的幾分之一,性能提升了3倍左右。咱們設計了一個全新的複雜值類型支持方案,該方案支持的類型更多(只要struct的字段都是值類型便可),包括用戶自定義的struct(別的方案都不支持),也更省內存(Vector3爲例,內存佔用只有別的方案的30%)。
但也有劣勢的地方,好比你調用Vector3上的一些方法,會比ulua、slua要差,由於後面兩個把Vector3用lua從新實現了,這類耗時不大的運算相比lua和C#直接的適配成本小太多了,直接在lua作更划算,不過這差距僅限於那幾個ulua、slua徹底從新實現的類。
上面只是三個重大節點,咱們以爲性能是一個須要持續關注的點:平時想到一個好點子,就會改改,測試下,有提高就加入;創建性能基線,防止某個新功能的加入,某個bug的修改把性能給改壞了。
xLua內置Lua代碼profiler;支持真機調試。目前lua profiler只是一個小工具,因此沒有作圖形化界面,典型的一個報告以下:
網上也有相似的工具,咱們這個的優點是對C#函數的支持以及luajit下更爲準確。
真機調試支持各lua插件都同樣,就是把ZeroBraneStudio調試須要用到的luasocket庫預先編譯進去而已,沒什麼值得介紹的地方。
技術實現的細節
泛型
泛型類型除了運行時動態實例化以外都支持,而運行時動態實例化須要jit的支持,iOS下行不通。舉個例子,若是你配了對Dictionary 生成代碼,那這個類型是能夠用的,但若是你新更新的lua代碼,想用一個Dictionary ,這個類型以前沒生成代碼,並且C#裏頭也沒任何地方使用過,這就不支持。靜態實例化的泛型,其實和非泛型類型處理上沒區別。
委託事件的封裝
委託封裝是根據委託的接口生成一段操做lua棧的代碼做爲委託的實現。舉個例子就很好懂了。好比對於委託:delegate double Add(double a, double b),咱們生成以下代碼:
public double SystemDouble(double a, double b){ RealStatePtr L = luaEnv.L; int err_func =LuaAPI.load_error_func(L, errorFuncRef); LuaAPI.lua_getref(L, luaReference); LuaAPI.lua_pushnumber(L, a); LuaAPI.lua_pushnumber(L, b); int __gen_error = LuaAPI.lua_pcall(L, 2, 1, err_func);if (__gen_error != 0) luaEnv.ThrowExceptionFromError(err_func - 1); double __gen_ret = LuaAPI.lua_tonumber(L, err_func + 1); LuaAPI.lua_settop(L, err_func - 1); return __gen_ret;}
這代碼把調用轉給lua函數,調用委託就是調用這函數。
其它方案都有delegate的支持,通常僅用於在lua側主動傳遞/設置一個lua函數到C#,而xLua支持更爲完整,好比:
支持C#主動用delegate來引用一個lua函數。用delegate代替相似object[] Call(params object[] args)的接口調用lua最大的好處是能夠避免值類型傳遞時的boxing/unboxing,還有參數數組,返回值數組的gc alloc;
支持返回delegate的delegate,可對應到lua的高階函數;
做爲這技術的一個延伸,xLua支持用一個c# interface引用一個lua table,這個特性和一些IOC框架配合能夠實現C#和Lua間無感知(模塊間都經過interface耦合,而後由框架去組裝)。
無縫支持生成代碼及反射
生成代碼當然重要,已然是各大主流方案的標配。
反射有的方案明確不支持,但從項目的反饋來講,也是相當重要的:有的項目代碼不少,已經接近蘋果的80M Text段的限制,對他們來講,代碼量大小關乎到可否發佈,反射方式性能不如生成代碼,但對安裝包影響小。
這的無縫有兩個含義:
二者在支持的特性以及特性的使用方式都是一致的,二者方式間切換,業務邏輯代碼不用修改,改改配置就能夠了;
二者無縫配合,好比一個繼承鏈上,任意一個類均可以選擇生成代碼或者反射,好比子類選擇生成代碼,父類因爲不經常使用選擇了反射,仍是能夠在子類對象上調用父類的方法;
對於il2cpp的stripping,xLua也考慮到了,只要你對一個類配置了ReflectionUse,會自動生成Unity的link.xml配置文件,將該類型列爲不剪裁。
其餘Lua插件一覽
在xLua以外,還有其餘的Lua插件,如 uLua、SLua、C#light等。
(1) ulua應用項目是最多的,因爲開源得早,名氣也最大,這是它很大的優點。騰訊也有項目用ulua,反饋比較多的問題是它版本的先後兼容問題:
ulua最先是一個叫LuaInterface開源庫的Unity移植,在2015年初換成cs2lua,又在2016年初換成tolua c#,只因此說「換」,是由於這從API角度看可認爲三個不一樣的產品,它們間很難升級,並且是每換一次,以前的版本就完全不維護了,這給項目帶來很大的困擾。
ulua的第一個版本純反射,並不實用,已經淡出市場,現存應用用後兩個版本居多。cstolua版本接口比較混亂:它保留了初版ulua接口之餘,搞了一套新接口,這兩套接口之間並不正交,也不是後者徹底替代前者,讓人有點無所適從。到了tolua c#版本,這問題解決了,但同時也把反射特性(老接口)給廢了。不過整體來講,ulua在向好的方向走。
(2) slua代碼質量比cstolua好不少(不少人當時選slua的理由),部分支持反射。性能按咱們的測試用例總體比tolua c#略低,另外代碼質量對比tolua c#已經造成不了明顯優點。
(3) C#light,我的以爲主要有兩個不足:
按其實現原理來講,性能不會靠譜,到不了手機上實用的地步;
因爲不完整支持C#,本質上只是另外一種叫C#light的語言(C# like?名字倒很貼切),這二者代碼配合起來也複雜,甚至它能作到比C#和lua配合更復雜些
事實也證實了,C# light基本淡出市場,能夠忽略不計了。
(4) LSharp是C# light做者的後續做品,卻是能夠期盼些,從il層面執行,這兩個問題有望改善,惋惜後面沒了下文(不維護了)。
相比之下,騰訊在設計xLua時,實現的功能更全,這「全」體如今C#的特性支持得更全些,lua虛擬機版本支持更全;更易用些,好比編輯器下不用生成代碼;另外,性能也不比它們差。
說到功能更全,可能有人抱怨並無pb,json,sqlite等等功能。其實稍熟悉lua的人都知道,那只是把一些現成lua擴展編譯進去而已,算不上是它作了這些功能。預集成好處是方便,壞處是沒選擇的餘地,用不上的東西會佔空間,用得上的東西也不必定是你喜歡的庫。
xLua的lua庫基於cmake編譯,要加這些庫門檻很低,有教程,改一個Makefile搞定各平臺編譯。在C#測也提供了api來初始化這些庫。總而言之,xLua的原則是授之以漁。
xLua的靈感來源
xLua立項當初,考察了當時能找到的全部方案,並分析各方案優劣,定出第一個版本的特性,大致是基於NLua基礎上加上代碼生成。介紹下NLua,NLua的做者就是LuaInterface的做者,NLua能夠認爲是LuaInterface的升級版,而前面也說了,初版uLua是LuaInterface的Unity移植版本,也不能算原創。
由於是「站在」生成代碼當時有看過cstolua的實現(那時還沒掛ulua的牌),以爲它經過硬編碼字符串拼接的方式維護性不太好,就用模版來作。感受這步是走對了,後續生成代碼調整起來比較簡單,這對性能調優頗有好處。
通過十多個版本的迭代,優化,如今NLua的影子比較淡了(NLua僅支持反射,而xLua的反射在2.1.0版本已經徹底重寫),就剩下C#引用類型對象在lua的表達的思路沒變。
此外,遇到須要調整較大的bug,咱們也會先看同類插件是否是已經解決了,對比他們的修改方案和咱們的,選更適合的。
xLua背後的研發與團隊
xLua目前迭代了十多個版本,從第一個項目開始,平均一個月一個版本。研發團隊人員目前有一個全職開發。測試使用的是騰訊互娛的公有資源,很規範:有一套不斷補充的功能自動化用例,性能測試也創建了基線,確保不會由於功能迭代而影響性能。騰訊互娛有專門的客戶端兼容性測試實驗室,至少中版本號以上的變更咱們會提交給他們針對top 100的機型進行兼容性測試。
至於lua,luajit的更新跟進,先說luajit吧,luajit變更不大,我第一次用luajit是11年,那時支持到lua5.1,如今也仍是lua5.1,中間只是一些bug的修復,性能優化,或者新平臺支持等,咱們要作事情很少。而lua中版本間差異仍是蠻大的,但中版本變更並不頻繁,從5.1到5.2用了6年,從5.2到5.3用了3年,5.3是2015年初發布的,我我的以爲到下一次中版本變更會好久,不亞於甚至大於5.1到5.2的時間跨度(5.2我的認爲只是一個過渡版本)。
小版本通常改改bug,等穩定後直接升級就能夠了,不須要作不少事情,目前xLua的lua版本用的是lua的最新版本5.3.3。
聊聊C#,談談Lua
C#在開發效率和運行效率平衡得很好,語言特性也比較全,我的以爲是很優秀的一門語言。在Unity3D上的缺憾主要是其mono版本過低,一些很古老的bug,好比著名的foreach性能問題不少個版本都沒解決,新的特性,好比await又不支持。
另外在手機平臺iOS不容許應用下載native code運行,jit,恰好把mono應用的熱更新給堵死了,要是mono虛擬機可以作到像luajit那樣,jit走不通就用interpret模式,其實就沒lua或者其它熱更新方案什麼事了。
而lua被稱爲遊戲腳本之王,在遊戲領域應用比較普遍,它設計之初就考慮到嵌入式領域,好比相對它提供的特性來講,它體積很是小,啓動一個vm佔資源也很少,性能也是腳本里頭的佼佼者。
lua相對C#而言,首先是它支持解析執行,進而支持熱更新。而免編譯對開發效率提高也是蠻大的,特別是較大的項目。
lua的動態類型有利有弊,好的是沒有編譯期的類型檢查,快速開發比較有優點,特別在需求三天兩頭就變的遊戲領域。缺點是要作出健壯的軟件得有大量的測試來保證,還有因爲要作運行期檢查,性能會比靜態類型語言低。
lua的一大特點是語言級的協程(coroutine)的支持,比Unity3D基於generator模擬的協程要好不少,對於複雜異步業務邏輯編寫頗有幫助,xLua的配套例子有範例(ps一下,Unity3D的mono版本升級到支持await的話,是更理想的異步方案)。
至於C#和lua間如何配合,可能每一個人都有不一樣的見解,但至少有一點是肯定的:需求變動大,預計極可能須要熱更的地方,用lua。固然,也能夠嘗試最新的開發模式,全C#開發,lua fix bug。
寫在最後
xLua應該還有不足,咱們會在發現的第一時間去修改。騰訊xLua團隊極度歡迎你們在發現不足以後提出反饋。