在游戲開發中,需要用到大量且更新不頻繁的配置資料,而把業務拆分到多個服務后,各個服務可能只用到其中的少部分資料,此時每個服務加載所有資料會浪費大量記憶體,sharedata模塊就是為了解決這種需求設計的,其原理是:將共享lua資料存放到一個c結構里,所有服務都共享這個c結構的記憶體塊,各個服務可以獲取這個共享記憶體物件,然后就可以像讀取普通lua表一樣讀取資料,
1. 創建共享資料
呼叫sharedata.new(name, value) api創建共享資料物件,主要2個引數:name,名字;v,可以是一張lua table,也可以是lua文本代碼,也可以是lua檔案,最終都會轉化成一個table,然后呼叫c層介面(第15行)回傳一個c結構共享記憶體,共享記憶體用參考計數管理其生命周期,
-- lualib/skynet/sharedata.lua
function sharedata.new(name, v, ...)
skynet.call(service, "lua", "new", name, v, ...)
end
function CMD.new(name, t, ...)
local dt = type(t)
local value
...
newobj(name, value)
end
local function newobj(name, tbl)
assert(pool[name] == nil)
local cobj = sharedata.host.new(tbl)
sharedata.host.incref(cobj)
local v = { value = tbl , obj = cobj, watch = {} }
objmap[cobj] = v
pool[name] = v
pool_count[name] = { n = 0, threshold = 16 }
end
主要資料結構如下:struct table,是回傳給lua層的c結構,
// lualib-src/lua-sharedata.c
struct table { //單個共享物件結構,供lua層操作
int sizearray; //一維資料長度
int sizehash; //hash資料長度
uint8_t *arraytype; //一維資料型別,integer、boolean、string or table
union value * array; //一維資料值
struct node * hash; //hash資料值,是一個陣列,每個元素是一個hash資料資訊
lua_State * L; //lua虛擬堆疊,可獲取共享的lua table資料
};
struct node { //table里一個key-value值對應的資料資訊
union value v;
int key; // integer key or index of string table
int next; // next slot index
uint32_t keyhash;
uint8_t keytype; // key type must be integer or string
uint8_t valuetype; // value type can be number/string/boolean/table
uint8_t nocolliding; // 0 means colliding slot
};
共享資料創建完后,各個服務通過sharedata.query(name)查詢共享物件,物件參考計數加一,使用者可以向讀取lua表一樣讀取資料,但其實際上是一個userdata(c結構),所以在corelib里定義了userdata的元表,元表中包含__index、__len、__pairs等方法,比如,__index方法會呼叫到c層的介面獲取指定key對應的value(第5行),
-- lualib/skynet/sharedata/corelib.lua
local index = core.index
function meta:__index(key)
local obj = getcobj(self)
local v = index(obj, key)
...
return v
end
2. 更新共享物件
當需要更新共享物件時,呼叫sharedata.update(name, v, ...) api,其原理是:創建一個新的共享物件(第17行),然后把舊物件標記為dirty(第20行),
-- lualib/skynet/sharedata.lua
function sharedata.update(name, v, ...)
skynet.call(service, "lua", "update", name, v, ...)
end
function CMD.update(name, t, ...)
local v = pool[name]
local watch, oldcobj
if v then
watch = v.watch
oldcobj = v.obj
objmap[oldcobj] = true
sharedata.host.decref(oldcobj)
pool[name] = nil
pool_count[name] = nil
end
CMD.new(name, t, ...)
local newobj = pool[name].obj
if watch then
sharedata.host.markdirty(oldcobj)
for _,response in pairs(watch) do
response(true, newobj)
end
end
collect10sec() -- collect in 10 sec
end
此時,已參考該物件的服務并不會馬上更新,而是等到下一次使用該共享物件才會判斷是否要重繪,即惰性更新,
第4行,如果物件標記為dirty,說明需要更新,呼叫c庫介面進行更新,
-- lualib/skynet/sharedata/corelib.lua
local function getcobj(self)
local obj = self.__obj
if isdirty(obj) then
local newobj, newtbl = needupdate(self.__gcobj)
if newobj then
local newgcobj = newtbl.__gcobj
local root = findroot(self)
update(root, newobj, newgcobj)
if obj == self.__obj then
error ("The key [" .. genkey(self) .. "] doesn't exist after update")
end
obj = self.__obj
end
end
return obj
end
對于舊物件,如果沒有服務參考(即參考計數為0時),在下一次collectobj時會刪掉它并清理記憶體(第12行),
-- service/sharedatad.lua
local function collectobj()
while true do
skynet.sleep(100) -- sleep 1s
if collect_tick <= 0 then
collect_tick = 600 -- reset tick count to 600 sec
collectgarbage()
for obj, v in pairs(objmap) do
if v == true then
if sharedata.host.getref(obj) <= 0 then
objmap[obj] = nil
sharedata.host.delete(obj)
end
end
end
else
collect_tick = collect_tick - 1
end
end
end
呼叫sharedata.delete(name) 將共享物件的參考計算減一,
本篇文章就寫到這,感興趣的話可以看看我其他關于skynet的文章
在2021年1月13/14號我會開一個四小時玩轉skynet訓練營,也就是兩個禮拜之后,現在已經開放報名,對游戲開發感興趣的諸位同好可以訂閱一下,
訓練營內容大概如下:
1. 多核并發編程
2. 訊息佇列,執行緒池
3. actor訊息調度
4. 網路模塊實作
5. 時間輪定時器實作
6. lua/c介面編程
7. skynet編程精要
8. demo演示actor編程思維
期待與諸位同好共襄技術盛舉
憑借報名截圖可以進群973961276領取上一期skynet訓練營的錄播以及這期的預習資料哦!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/245752.html
標籤:其他
上一篇:輕應用命令列工具
