HomeServer/lualib/skynet/datasheet/builder.lua

201 lines
4.4 KiB
Lua
Raw Normal View History

2024-11-20 15:41:09 +08:00
local skynet = require "skynet"
local dump = require "skynet.datasheet.dump"
local core = require "skynet.datasheet.core"
local service = require "skynet.service"
local builder = {}
local cache = {}
local dataset = {}
local address
local unique_id = 0
local function unique_string(str)
unique_id = unique_id + 1
return str .. tostring(unique_id)
end
local function monitor(pointer)
skynet.fork(function()
skynet.call(address, "lua", "collect", pointer)
for k,v in pairs(cache) do
if v == pointer then
cache[k] = nil
return
end
end
end)
end
local function dumpsheet(v)
if type(v) == "string" then
return v
else
return dump.dump(v)
end
end
function builder.new(name, v)
assert(dataset[name] == nil)
local datastring = unique_string(dumpsheet(v))
local pointer = core.stringpointer(datastring)
skynet.call(address, "lua", "update", name, pointer)
cache[datastring] = pointer
dataset[name] = datastring
monitor(pointer)
end
function builder.update(name, v)
local lastversion = assert(dataset[name])
local newversion = dumpsheet(v)
local diff = unique_string(dump.diff(lastversion, newversion))
local pointer = core.stringpointer(diff)
skynet.call(address, "lua", "update", name, pointer)
cache[diff] = pointer
local lp = assert(cache[lastversion])
skynet.send(address, "lua", "release", lp)
dataset[name] = diff
monitor(pointer)
end
function builder.compile(v)
return dump.dump(v)
end
local function datasheet_service()
local skynet = require "skynet"
local datasheet = {}
local handles = {} -- handle:{ ref:count , name:name , collect:resp }
local dataset = {} -- name:{ handle:handle, monitor:{monitors queue} }
local customers = {} -- source: { handle:true }
setmetatable(customers, { __index = function(c, source)
local v = {}
c[source] = v
return v
end } )
local function releasehandle(source, handle)
local h = handles[handle]
h.ref = h.ref - 1
if h.ref == 0 and h.collect then
h.collect(true)
h.collect = nil
handles[handle] = nil
end
local t=dataset[h.name]
t.monitor[source]=nil
end
-- from builder, create or update handle
function datasheet.update(source, name, handle)
local t = dataset[name]
if not t then
-- new datasheet
t = { handle = handle, monitor = {} }
dataset[name] = t
handles[handle] = { ref = 1, name = name }
else
-- report update to customers
handles[handle] = { ref = handles[t.handle].ref, name = name }
t.handle = handle
for k,v in pairs(t.monitor) do
v(true, handle)
t.monitor[k] = nil
end
end
skynet.ret()
end
-- from customers
function datasheet.query(source, name)
local t = assert(dataset[name], "create data first")
local handle = t.handle
local h = handles[handle]
h.ref = h.ref + 1
customers[source][handle] = true
skynet.ret(skynet.pack(handle))
end
-- from customers, monitor handle change
function datasheet.monitor(source, handle)
local h = assert(handles[handle], "Invalid data handle")
local t = dataset[h.name]
if t.handle ~= handle then -- already changes
customers[source][t.handle] = true
skynet.ret(skynet.pack(t.handle))
else
assert(not t.monitor[source])
local resp = skynet.response()
t.monitor[source]= function(ok, handle)
if ok then
customers[source][handle] = true
end
resp(ok, handle)
end
end
end
-- from customers, release handle , ref count - 1
function datasheet.release(source, handle)
-- send message, don't ret
customers[source][handle] = nil
releasehandle(source, handle)
end
-- customer closed, clear all handles it queried
function datasheet.close(source)
for handle in pairs(customers[source]) do
releasehandle(source, handle)
end
customers[source] = nil
end
-- from builder, monitor handle release
function datasheet.collect(source, handle)
local h = assert(handles[handle], "Invalid data handle")
if h.ref == 0 then
handles[handle] = nil
skynet.ret()
else
assert(h.collect == nil, "Only one collect allows")
h.collect = skynet.response()
end
end
skynet.dispatch("lua", function(_,source,cmd,...)
datasheet[cmd](source,...)
end)
skynet.info_func(function()
local info = {}
local tmp = {}
for k,v in pairs(handles) do
tmp[k] = v
end
for k,v in pairs(dataset) do
local h = handles[v.handle]
tmp[v.handle] = nil
info[k] = {
handle = v.handle,
monitors = h.ref,
}
end
for k,v in pairs(tmp) do
info[k] = v.ref
end
return info
end)
end
skynet.init(function()
address=service.new("datasheet", datasheet_service)
end)
return builder