diff options
Diffstat (limited to 'script/vm/value.lua')
-rw-r--r-- | script/vm/value.lua | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/script/vm/value.lua b/script/vm/value.lua new file mode 100644 index 00000000..5de0d8e8 --- /dev/null +++ b/script/vm/value.lua @@ -0,0 +1,634 @@ +local libraryBuilder = require 'vm.library' +local library = require 'core.library' +local listMgr = require 'vm.list' +local config = require 'config' + +local Sort = 0 +local Watch = setmetatable({}, {__mode = 'kv'}) +local TypeLevel = { + ['table'] = 1.0, + ['function'] = 0.9, + ['string'] = 0.8, + ['integer'] = 0.7, + ['number'] = 0.6, +} + +---@class value +local mt = {} +mt.__index = mt +mt.type = 'value' +mt.uri = '' +mt._global = false + +local function create (tp, source, literal) + if tp == '...' then + error('Value type cant be ...') + end + if not source then + error('No source') + end + local id = source.id + if not id then + error('Not instanted source') + end + local self = setmetatable({ + source = id, + _type = {}, + _literal = literal, + _info = {}, + }, mt) + if type(tp) == 'table' then + for i = 1, #tp do + self:setType(tp[i], 1.0 / #tp) + end + else + self:setType(tp, 1.0) + end + Watch[self] = true + return self +end + +function mt:setType(tp, rate) + if type(tp) == 'table' then + for _, ctp in ipairs(tp) do + self:setType(ctp, rate) + end + return + end + if tp == '...' then + error('Value type cant be ...') + end + if not tp then + tp = 'nil' + end + if tp == 'any' or tp == 'nil' then + rate = 0.0 + end + if tp == 'integer' then + local version = config.config.runtime.version + if version ~= 'Lua 5.3' and version ~= 'Lua 5.4' then + tp = 'number' + end + end + local current = self._type[tp] or 0.0 + if rate > current then + self._type[tp] = rate + end +end + +function mt:getType() + if self:getEmmy() then + return self:getEmmy():getType(), 1.0 + end + if not self._type then + return 'nil', 0.0 + end + local mRate = 0.0 + local mType + for tp, rate in pairs(self._type) do + if rate > mRate then + mRate = rate + mType = tp + elseif rate == mRate then + local level1 = TypeLevel[tp] or 0.0 + local level2 = TypeLevel[mType] or 0.0 + if level1 > level2 then + mRate = rate + mType = tp + end + end + end + return mType or 'any', mRate +end + +function mt:rawSet(index, value, source) + if index == nil then + return + end + if not self._child then + self._child = {} + end + if self._child[index] then + if self._global then + self._child[index]:mergeValue(value) + else + self._child[index]:mergeType(value) + self._child[index]:mergeInfo(value) + end + self._child[index] = value + else + self._child[index] = value + end + self:addInfo('set child', source, index, self._child[index]) + if self._global then + self._child[index]:markGlobal() + end +end + +function mt:rawGet(index) + if not self._child then + return nil + end + self:flushChild() + local child = self._child[index] + if not child then + return nil + end + return child +end + +function mt:setChild(index, value, source) + if index == nil then + return + end + self:setType('table', 0.5) + self:rawSet(index, value, source) + return value +end + +function mt:getLibChild(index) + local tp = self:getType() + local lib = library.object[tp] + if lib then + local childs = libraryBuilder.child(lib) + return childs[index] + end + return nil +end + +function mt:eachLibChild(callback) + local tp = self:getType() + local lib = library.object[tp] + if lib then + local childs = libraryBuilder.child(lib) + for k, v in pairs(childs) do + callback(k, v) + end + end +end + +function mt:getChild(index, source) + self:setType('table', 0.5) + local parent = self + local value + -- 最多检查3层 __index + for _ = 1, 3 do + value = parent:rawGet(index) + if value then + break + end + local method = parent:getMetaMethod('__index') + if not method then + value = parent:getLibChild(index) + break + end + parent = method + end + if not value and source then + local emmy = self:getEmmy() + if emmy then + if emmy.type == 'emmy.arrayType' then + if type(index) == 'number' then + value = create(emmy:getName(), source) + end + elseif emmy.type == 'emmy.tableType' then + value = create(emmy:getValueType():getType(), source) + end + end + if not value then + value = create('any', source) + end + self:setChild(index, value) + value.uri = self.uri + end + return value +end + +function mt:setMetaTable(metatable) + local source = metatable:getSource() + if not source then + return + end + source:bindMetatable(metatable) + self._meta = metatable.source +end + +function mt:getMetaTable() + if not self._meta then + return nil + end + local metaSource = listMgr.get(self._meta) + if not metaSource then + self._meta = nil + return nil + end + return metaSource:bindMetatable() +end + +function mt:getMetaMethod(name) + local meta = self:getMetaTable() + if not meta then + return nil + end + return meta:rawGet(name) +end + +function mt:flushChild() + if not self._child then + return nil + end + -- 非全局值不会出现dead child + if not self._global then + return + end + local listVersion = listMgr.getVersion() + if self._flushVersion == listVersion then + return + end + self._flushVersion = listVersion + local alived = {} + local infos = self._info + local count = 0 + for srcId, info in pairs(infos) do + local src = listMgr.get(srcId) + if src then + if info.type == 'set child' or info.type == 'get child' then + if info[1] then + alived[info[1]] = true + end + end + count = count + 1 + else + infos[srcId] = nil + end + end + infos._count = count + infos._limit = count * 1.1 + 10 + infos._version = listMgr.getVersion() + for index in pairs(self._child) do + if not alived[index] then + self._child[index] = nil + end + end +end + +function mt:rawEach(callback, mark) + if not self._child then + return nil + end + self:flushChild() + for index, value in pairs(self._child) do + if mark then + if mark[index] then + goto CONTINUE + end + mark[index] = true + end + local res = callback(index, value) + if res ~= nil then + return res + end + ::CONTINUE:: + end + return nil +end + +function mt:eachChild(callback) + local mark = {} + local parent = self + -- 最多检查3层 __index + for _ = 1, 3 do + local res = parent:rawEach(callback, mark) + if res ~= nil then + return res + end + local method = parent:getMetaMethod('__index') + if not method then + return parent:eachLibChild(callback) + end + parent = method + end +end + +function mt:mergeType(value) + if self == value then + return + end + if not value then + return + end + if self._emmy and not value._emmy then + value._emmy = self._emmy + return + elseif not self._emmy and value._emmy then + self._emmy = value._emmy + return + end + if value._type then + for tp, rate in pairs(value._type) do + self:setType(tp, rate) + end + end + value._type = self._type +end + +function mt:mergeInfo(value) + if self == value then + return + end + if not value then + return + end + local infos = self._info + for srcId, info in pairs(value._info) do + local src = listMgr.get(srcId) + if src and not infos[srcId] then + infos[srcId] = info + infos._count = (infos._count or 0) + 1 + end + end + value._info = infos +end + +function mt:mergeValue(value) + if self == value then + return + end + if not value then + return + end + local list = {self, value} + local pos = 1 + while true do + local a, b = list[pos], list[pos+1] + if not a then + break + end + pos = pos + 2 + list[a] = true + list[b] = true + a:mergeType(b) + a:mergeInfo(b) + + a:flushChild() + b:flushChild() + local global = a._global or b._global + if b._child then + if not a._child then + a._child = {} + end + for k, bc in pairs(b._child) do + local ac = a._child[k] + if ac and ac ~= bc and global then + if list[ac] and list[bc] then + else + list[#list+1] = ac + list[#list+1] = bc + end + end + a._child[k] = bc + end + end + b._child = a._child + if global then + a:markGlobal() + b:markGlobal() + end + + if b._meta then + a._meta = b._meta + end + if b._func then + a._func = b._func + end + if b._lib then + a._lib = b._lib + end + if b.uri then + a.uri = b.uri + end + end +end + +function mt:addInfo(tp, source, ...) + if not source then + return + end + if not source.start then + error('Miss start: ' .. table.dump(source)) + end + local id = source.id + if not id then + error('Not instanted source') + end + if not tp then + error('Miss info type') + end + + local infos = self._info + if infos[id] then + return + end + Sort = Sort + 1 + local info = { + type = tp, + source = id, + _sort = Sort, + ... + } + infos[id] = info + infos._count = (infos._count or 0) + 1 + local version = listMgr.getVersion() + -- 只有全局值需要压缩info + if self._global and infos._count > (infos._limit or 10) and infos._version ~= version then + local count = 0 + for srcId in pairs(infos) do + local src = listMgr.get(srcId) + if src then + count = count + 1 + else + infos[srcId] = nil + end + end + infos._count = count + infos._limit = count * 1.1 + 10 + infos._version = version + end +end + +function mt:eachInfo(callback) + local clock = os.clock() + local infos = self._info + local list = {} + for srcId, info in pairs(infos) do + local src = listMgr.get(srcId) + if src then + list[#list+1] = info + else + infos[srcId] = nil + end + end + infos._count = #list + infos._limit = infos._count * 1.1 + 10 + infos._version = listMgr.getVersion() + --local clock2 = os.clock() + --table.sort(list, function (a, b) + -- return a._sort < b._sort + --end) + local passed = os.clock() - clock + if passed > 0.1 then + log.warn(('eachInfo takes: [%.3f]sec, #list: %d'):format(passed, #list)) + end + for i = 1, #list do + local info = list[i] + local res = callback(info, listMgr.get(info.source)) + if res ~= nil then + return res + end + end + return nil +end + +function mt:setFunction(func) + self._func = func.id + if self._global then + func:markGlobal() + end +end + +function mt:getFunction() + local id = self._func + local func = listMgr.get(id) + if not func then + return nil + end + if func._removed then + return nil + end + if not func:getSource() then + func = nil + listMgr.clear(id) + end + return func +end + +function mt:setLib(lib) + self._lib = lib +end + +function mt:getLib() + return self._lib +end + +function mt:getLiteral() + return self._literal +end + +function mt:set(name, v) + if not self._flag then + self._flag = {} + end + self._flag[name] = v +end + +function mt:get(name) + if not self._flag then + return nil + end + return self._flag[name] +end + +function mt:getSource() + return listMgr.get(self.source) +end + +function mt:markGlobal() + if self._global then + return + end + self._global = true + self:rawEach(function (index, value) + value:markGlobal() + end) + local func = self:getFunction() + if func then + func:markGlobal() + end +end + +function mt:isGlobal() + return self._global +end + +function mt:setEmmy(emmy) + if not emmy then + return + end + if emmy.type == 'emmy.class' then + ---@type EmmyClass + local emmyClass = emmy + emmyClass:setValue(self) + emmyClass:eachChild(function (obj) + local value = obj:getValue() + if value then + value:mergeValue(self) + end + end) + emmyClass:eachField(function (field) + local name = field:getName() + local value = field:bindValue() + self:setChild(name, value, field:getSource()) + end) + elseif emmy.type == 'emmy.type' then + ---@type EmmyType + local emmyType = emmy + emmyType:setValue(self) + emmyType:eachClass(function (class) + if class then + self:mergeValue(class:getValue()) + end + end) + elseif emmy.type == 'emmy.arrayType' then + ---@type EmmyArrayType + local emmyArrayType = emmy + emmyArrayType:setValue(self) + elseif emmy.type == 'emmy.tableType' then + ---@type EmmyTableType + local emmyTableType = emmy + emmyTableType:setValue(self) + elseif emmy.type == 'emmy.functionType' then + ---@type EmmyFunctionType + local emmyFuncType = emmy + emmyFuncType:setValue(self) + self:setFunction(emmyFuncType:bindFunction()) + else + return + end + self._emmy = emmy + self:markGlobal() +end + +function mt:getEmmy() + if not self._emmy then + return nil + end + local source = self._emmy.source + if not listMgr.get(source) then + self._emmy = nil + return nil + end + return self._emmy +end + +function mt:setComment(comment) + self._comment = comment +end + +function mt:getComment(comment) + return self._comment +end + +return { + create = create, + watch = Watch, +} |