##前言 在UWA學堂上線那天,我買了招文勇這篇Lua交互的課程,19塊還算值,可是前段時間太忙,一直沒空研究,他的demo是基於xlua的,今天終於花了大半天時間在tolua下跑起來了,記錄一下個人理解node
##性能,仍然是Lua中與C#混用的大坑c#
Lua跟C#交互的性能問題是老生常談的了,c#跟lua數據交互是經過lua虛擬棧,進行壓棧、出棧來傳遞的,一次調用就須要執行不少指令,性能會隨着調用次數的頻繁,函數參數的增多而變差。直接操做內存的方式,能夠在c#端修改lua內存,省去了操做虛擬棧,函數調用的大把指令,性能也就很高效了數組
騰訊的UnLua(給虛幻4用的)中也有相似的直接操做內存的交互方式,看來這種方式會漸漸成爲主流,畢竟性能擺在這呢函數
##Lua跟C#高效共享大量數據的一種方法 原理其實很簡單,在c#端定義好lua table的結構體,必須在內存中對齊lua端的table,而後在c#端拿到lua table的指針,讀寫這塊內存,就能讀寫這個lua table了。性能
是否是以爲很是簡單,哈哈哈哈。感受本身立刻就能弄出來了ui
##想要實現這套東西,還得搞懂幾個問題,下面開始一一講解lua
###Lua Table結構體是什麼樣的? 想在c#端寫一個lua table結構體,那就先看看lua端這個結構體是怎麼實現的吧。在tolua下,咱們使用的是luajit,jit的源碼跟lua是不同的,luajit又分32位跟64位。因此咱們這個table結構體也須要作多套才行.net
luajit中的GCTab就是Table的結構體了設計
typedef struct GCtab { GCHeader; uint8_t nomm; /* Negative cache for fast metamethods. */ int8_t colo; /* Array colocation. */ MRef array; /* Array part. */ GCRef gclist; GCRef metatable; /* Must be at same offset in GCudata. */ MRef node; /* Hash part. */ uint32_t asize; /* Size of array part (keys [0, asize-1]). */ uint32_t hmask; /* Hash part mask (size of hash part - 1). */ #if LJ_GC64 MRef freetop; /* Top of free elements. */ #endif } GCtab;
GCHeader是每個GC對象都要包含的一個宏,定義了這些屬性3d
#define GCHeader GCRef nextgc; uint8_t marked; uint8_t gct
lua的table支持數組、哈希表兩種用法,甚至能夠同時是數組又是哈希表。咱們主要處理數組的數據交互,結構體中的MRef array;就是這個table的全部數據存儲的地方了,而asize就等於這個數組的長度+1。因此咱們重點關注這2個字段的內存地址
###如何設計c#端的table結構體呢? 咱們把GCTab結構體展開成這樣看
GCRef nextgc; uint8_t marked; uint8_t gct; uint8_t nomm; int8_t colo; MRef array; GCRef gclist; GCRef metatable; MRef node; uint32_t asize; uint32_t hmask; MRef freetop;//這個是64位的纔會有
GCRef 跟 MRef 都是一個jit中封裝的指針類型,會自動根據宏展開爲32位跟64位。 GCRef 表示這是一個GC對象的指針 MRef 表示非GC對象的內存指針
在c#中均可以用IntPtr類型代替
uint8_t 是8字節的,咱們把4個8字節的放在一塊兒,能夠用一個int32位佔用
那麼轉換到c#中,結構體就變成了這樣
// GC64 version public struct LuaJitGCtabGC64 { IntPtr nextgc; UInt32 masks; IntPtr array; IntPtr gclist; IntPtr metatable; IntPtr node; UInt32 asize; UInt32 hmask; IntPtr freetop; // only valid for LJ_GC64 }
##指針array指向的數據是什麼? 在lj_tab.c中看tab的實現,咱們很快就能找到array裏存的是TValue結構,TValue實際上是一個聯合體。
聯合體是多個結構體能夠共享同一塊內存,訪問的時候能夠用不一樣的結構體方式去訪問。具體什麼是聯合體能夠自行百度哦
TValue源碼
/* Tagged value. */ typedef LJ_ALIGN(8) union TValue { uint64_t u64; /* 64 bit pattern overlaps number. */ lua_Number n; /* Number object overlaps split tag/value object. */ #if LJ_GC64 GCRef gcr; /* GCobj reference with tag. */ int64_t it64; struct { LJ_ENDIAN_LOHI( int32_t i; /* Integer value. */ , uint32_t it; /* Internal object tag. Must overlap MSW of number. */ ) }; #else struct { LJ_ENDIAN_LOHI( union { GCRef gcr; /* GCobj reference (if any). */ int32_t i; /* Integer value. */ }; , uint32_t it; /* Internal object tag. Must overlap MSW of number. */ ) }; #endif #if LJ_FR2 int64_t ftsz; /* Frame type and size of previous frame, or PC. */ #else struct { LJ_ENDIAN_LOHI( GCRef func; /* Function for next frame (or dummy L). */ , FrameLink tp; /* Link to previous frame. */ ) } fr; #endif struct { LJ_ENDIAN_LOHI( uint32_t lo; /* Lower 32 bits of number. */ , uint32_t hi; /* Upper 32 bits of number. */ ) } u32; } TValue;
這中間有不少宏,看着很亂,但其實咱們只須要用2種模式就好了,由於咱們只實現int跟double。做者給出的方式是以下這種
[StructLayout(LayoutKind.Explicit, Size = 8)] public struct LuaJitTValue { // uint64 [FieldOffset(0)] public UInt64 u64; // number [FieldOffset(0)] public double n; // integer value [FieldOffset(0)] public int i; // internal object tag for GC64 [FieldOffset(0)] public Int64 it64; // internal object tag [FieldOffset(4)] public UInt32 it; }
但這裏我有一些我還沒弄明白,由於我實際運行起來後,無論lua賦值的是整形,仍是浮點,int i始終沒有值,值都存在了double n中。那爲啥做者要弄一個int i跟UInt32 it; 這個it還偏移了4字節
在c#端咱們可使用**[StructLayout(LayoutKind.Explicit)]和[FieldOffset(0)]**來實現c語言中的聯合體,具體方式能夠看這篇文章 https://blog.csdn.net/wonengxing/article/details/44302661
##如何用unsafe模式讀寫結構體? 結構體都定義好了,接下來咱們看看怎麼讀寫一個double
LuaJitGCtab32* TableRawPtr; //須要拿到Lua端Table的指針 //賦值操做 TableRawPtr->array[index].n = val; //取值操做 TableRawPtr->array[index].n;
沒錯,就是這麼簡單。直接就能夠操做lua內存了。
##如何拿到lua端table的指針? 在lua端傳入一個table參數過來,咱們能夠在c#端操做虛擬棧轉成指針
System.IntPtr arg0 = LuaDLL.lua_topointer(L, 1);
看到這裏,相信大部分的謎團都已經解開了,真的本身能夠實現一套出來了。
##總結 做者提供的方案裏,只支持int、double。只支持array類型的table。還有luajit64位貌似沒支持好。因此若是真正要使用的話,還要改不少東西