add cache and rename some files
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
local type = type
|
||||
local pairs = pairs
|
||||
local error = error
|
||||
local next = next
|
||||
local load = load
|
||||
local setmt = setmetatable
|
||||
local rawset = rawset
|
||||
local smatch = string.match
|
||||
local sformat = string.format
|
||||
local tconcat = table.concat
|
||||
|
||||
_ENV = nil
|
||||
|
||||
---@class lazytable.builder
|
||||
---@field source table
|
||||
---@field codeMap table<integer, string>
|
||||
---@field dumpMark table<table, integer>
|
||||
---@field excludes table<table, true>
|
||||
---@field refMap table<any, integer>
|
||||
---@field instMap table<integer, table|function|thread|userdata>
|
||||
local mt = {}
|
||||
mt.__index = mt
|
||||
mt.tableID = 1
|
||||
mt.keyID = 1
|
||||
|
||||
local DUMMY = function() end
|
||||
|
||||
local RESERVED = {
|
||||
['and'] = true,
|
||||
['break'] = true,
|
||||
['do'] = true,
|
||||
['else'] = true,
|
||||
['elseif'] = true,
|
||||
['end'] = true,
|
||||
['false'] = true,
|
||||
['for'] = true,
|
||||
['function'] = true,
|
||||
['if'] = true,
|
||||
['in'] = true,
|
||||
['local'] = true,
|
||||
['nil'] = true,
|
||||
['not'] = true,
|
||||
['or'] = true,
|
||||
['repeat'] = true,
|
||||
['return'] = true,
|
||||
['then'] = true,
|
||||
['true'] = true,
|
||||
['until'] = true,
|
||||
['while'] = true,
|
||||
['goto'] = true
|
||||
}
|
||||
|
||||
---@param k string|integer
|
||||
---@return string
|
||||
local function formatKey(k)
|
||||
if type(k) == 'string' then
|
||||
if not RESERVED[k] and smatch(k, '^[%a_][%w_]*$') then
|
||||
return k
|
||||
else
|
||||
return sformat('[%q]', k)
|
||||
end
|
||||
end
|
||||
if type(k) == 'number' then
|
||||
return sformat('[%q]', k)
|
||||
end
|
||||
error('invalid key type: ' .. type(k))
|
||||
end
|
||||
|
||||
---@param v string|number|boolean
|
||||
local function formatValue(v)
|
||||
return sformat('%q', v)
|
||||
end
|
||||
|
||||
---@param info {[1]: table, [2]: integer, [3]: table?}
|
||||
---@return string
|
||||
local function dump(info)
|
||||
local codeBuf = {}
|
||||
|
||||
codeBuf[#codeBuf + 1] = 'return{{'
|
||||
local hasFields
|
||||
for k, v in pairs(info[1]) do
|
||||
if hasFields then
|
||||
codeBuf[#codeBuf + 1] = ','
|
||||
else
|
||||
hasFields = true
|
||||
end
|
||||
codeBuf[#codeBuf+1] = sformat('%s=%s'
|
||||
, formatKey(k)
|
||||
, formatValue(v)
|
||||
)
|
||||
end
|
||||
codeBuf[#codeBuf+1] = '}'
|
||||
|
||||
codeBuf[#codeBuf+1] = sformat(',%d', formatValue(info[2]))
|
||||
|
||||
if info[3] then
|
||||
codeBuf[#codeBuf+1] = ',{'
|
||||
hasFields = false
|
||||
for k, v in pairs(info[3]) do
|
||||
if hasFields then
|
||||
codeBuf[#codeBuf+1] = ','
|
||||
else
|
||||
hasFields = true
|
||||
end
|
||||
codeBuf[#codeBuf+1] = sformat('%s=%s'
|
||||
, formatKey(k)
|
||||
, formatValue(v)
|
||||
)
|
||||
end
|
||||
codeBuf[#codeBuf+1] = '}'
|
||||
end
|
||||
|
||||
codeBuf[#codeBuf + 1] = '}'
|
||||
|
||||
return tconcat(codeBuf)
|
||||
end
|
||||
|
||||
---@param obj table|function|userdata|thread
|
||||
---@return integer
|
||||
function mt:getObjectID(obj)
|
||||
if self.dumpMark[obj] then
|
||||
return self.dumpMark[obj]
|
||||
end
|
||||
local id = self.tableID
|
||||
self.tableID = self.tableID + 1
|
||||
self.dumpMark[obj] = id
|
||||
if self.excludes[obj] or type(obj) ~= 'table' then
|
||||
self.refMap[obj] = id
|
||||
self.instMap[id] = obj
|
||||
return id
|
||||
end
|
||||
|
||||
if not next(obj) then
|
||||
self.codeMap[id] = nil
|
||||
return id
|
||||
end
|
||||
|
||||
local fields = {}
|
||||
local objs
|
||||
for k, v in pairs(obj) do
|
||||
local tp = type(v)
|
||||
if tp == 'string' or tp == 'number' or tp == 'boolean' then
|
||||
fields[k] = v
|
||||
else
|
||||
if not objs then
|
||||
objs = {}
|
||||
end
|
||||
objs[k] = self:getObjectID(v)
|
||||
end
|
||||
end
|
||||
|
||||
local code = dump({fields, #obj, objs})
|
||||
|
||||
self.codeMap[id] = code
|
||||
return id
|
||||
end
|
||||
|
||||
---@param writter fun(id: integer, code: string): boolean
|
||||
---@param reader fun(id: integer): string?
|
||||
function mt:bind(writter, reader)
|
||||
setmt(self.codeMap, {
|
||||
__newindex = function (t, id, code)
|
||||
local suc = writter(id, code)
|
||||
if not suc then
|
||||
rawset(t, id, code)
|
||||
end
|
||||
end,
|
||||
__index = function (_, id)
|
||||
return reader(id)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
---@param t table
|
||||
function mt:exclude(t)
|
||||
self.excludes[t] = true
|
||||
return self
|
||||
end
|
||||
|
||||
---@return table
|
||||
function mt:entry()
|
||||
local entryID = self:getObjectID(self.source)
|
||||
|
||||
local codeMap = self.codeMap
|
||||
local refMap = self.refMap
|
||||
local instMap = self.instMap
|
||||
local tableID = self.tableID
|
||||
---@type table<table, integer>
|
||||
local idMap = {}
|
||||
---@type table<table, table[]>
|
||||
local infoMap = setmt({}, {
|
||||
__mode = 'v',
|
||||
__index = function (map, t)
|
||||
local id = idMap[t]
|
||||
local code = codeMap[id]
|
||||
if not code then
|
||||
return nil
|
||||
end
|
||||
local f = load(code)
|
||||
if not f then
|
||||
return nil
|
||||
end
|
||||
--if sbyte(code, 1, 1) ~= 27 then
|
||||
-- codeMap[id] = sdump(f, true)
|
||||
--end
|
||||
local info = f()
|
||||
map[t] = info
|
||||
return info
|
||||
end
|
||||
})
|
||||
|
||||
local lazyload = {
|
||||
ref = refMap,
|
||||
__index = function(t, k)
|
||||
local info = infoMap[t]
|
||||
if not info then
|
||||
return nil
|
||||
end
|
||||
local fields = info[1]
|
||||
|
||||
local keyID = k
|
||||
|
||||
local v = fields[keyID]
|
||||
if v ~= nil then
|
||||
return v
|
||||
end
|
||||
|
||||
local refs = info[3]
|
||||
if not refs then
|
||||
return nil
|
||||
end
|
||||
|
||||
local ref = refs[keyID]
|
||||
if not ref then
|
||||
return nil
|
||||
end
|
||||
|
||||
return instMap[ref]
|
||||
end,
|
||||
__newindex = function(t, k, v)
|
||||
local info = infoMap[t]
|
||||
local fields = info and info[1] or {}
|
||||
local len = info and info[2] or 0
|
||||
local objs = info and info[3]
|
||||
fields[k] = nil
|
||||
if objs then
|
||||
objs[k] = nil
|
||||
end
|
||||
if v ~= nil then
|
||||
local tp = type(v)
|
||||
if tp == 'string' or tp == 'number' or tp == 'boolean' then
|
||||
fields[k] = v
|
||||
else
|
||||
if not objs then
|
||||
objs = {}
|
||||
end
|
||||
local id = refMap[v] or idMap[v]
|
||||
if not id then
|
||||
id = tableID
|
||||
refMap[v] = id -- 新赋值的对象一定会被引用住
|
||||
instMap[id] = v
|
||||
tableID = tableID + 1
|
||||
end
|
||||
objs[k] = id
|
||||
end
|
||||
end
|
||||
info = { fields, len, objs }
|
||||
local id = idMap[t]
|
||||
local code = dump(info)
|
||||
infoMap[id] = nil
|
||||
codeMap[id] = nil
|
||||
codeMap[id] = code
|
||||
end,
|
||||
__len = function (t)
|
||||
local info = infoMap[t]
|
||||
if not info then
|
||||
return 0
|
||||
end
|
||||
return info[2]
|
||||
end,
|
||||
__pairs = function (t)
|
||||
local info = infoMap[t]
|
||||
if not info then
|
||||
return DUMMY
|
||||
end
|
||||
local fields = info[1]
|
||||
local objs = info[3]
|
||||
local keys = {}
|
||||
for k in pairs(fields) do
|
||||
keys[#keys+1] = k
|
||||
end
|
||||
if objs then
|
||||
for k in pairs(objs) do
|
||||
keys[#keys+1] = k
|
||||
end
|
||||
end
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
local k = keys[i]
|
||||
return k, t[k]
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
setmt(idMap, { __mode = 'k' })
|
||||
|
||||
setmt(instMap, {
|
||||
__mode = 'v',
|
||||
__index = function (map, id)
|
||||
local inst = {}
|
||||
idMap[inst] = id
|
||||
map[id] = inst
|
||||
|
||||
return setmt(inst, lazyload)
|
||||
end,
|
||||
})
|
||||
|
||||
local entry = instMap[entryID] --[[@as table]]
|
||||
|
||||
self.source = nil
|
||||
self.dumpMark = nil
|
||||
|
||||
return entry
|
||||
end
|
||||
|
||||
---@class lazytable
|
||||
local m = {}
|
||||
|
||||
---@param t table
|
||||
---@param writter? fun(id: integer, code: string): boolean
|
||||
---@param reader? fun(id: integer): string?
|
||||
---@return lazytable.builder
|
||||
function m.build(t, writter, reader)
|
||||
local builder = setmt({
|
||||
source = t,
|
||||
codeMap = {},
|
||||
refMap = {},
|
||||
instMap = {},
|
||||
dumpMark = {},
|
||||
excludes = setmt({}, { __mode = 'k' }),
|
||||
}, mt)
|
||||
|
||||
if writter and reader then
|
||||
builder:bind(writter, reader)
|
||||
end
|
||||
|
||||
return builder
|
||||
end
|
||||
|
||||
return m
|
||||
Reference in New Issue
Block a user