You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
655 lines
18 KiB
Lua
655 lines
18 KiB
Lua
-- -- https://github.com/ayongm2/TableMonitor
|
|
-- -- https://github.com/jojo59516/traceable
|
|
-- local next = next
|
|
-- local pairs = pairs
|
|
-- local ipairs = ipairs
|
|
-- local unpack = table.unpack
|
|
-- local load = load
|
|
-- local setmetatable = setmetatable
|
|
-- local assert = assert
|
|
-- local getmetatable = getmetatable
|
|
-- local type = type
|
|
-- local tonumber = tonumber
|
|
-- -----------------------------------------------------
|
|
-- local traceable = {}
|
|
-- local _M = traceable
|
|
-- local function merge(dst, src)
|
|
-- for k, v in next, src do
|
|
-- dst[k] = v
|
|
-- end
|
|
-- return dst
|
|
-- end
|
|
-- local function normalize_field_path(field_path)
|
|
-- return field_path:gsub("^([-?%d]+)", "[%1]"):gsub("%.([-?%d]+)", "[%1]"):gsub("^([^.%[%]]+)", "['%1']"):gsub(
|
|
-- "%.([^.%[%]]+)",
|
|
-- "['%1']"
|
|
-- )
|
|
-- end
|
|
-- local getter_template =
|
|
-- [[
|
|
-- return function(t)
|
|
-- local success, value = pcall(function (t)
|
|
-- return t%s
|
|
-- end, t)
|
|
-- if success then
|
|
-- return value
|
|
-- end
|
|
-- end
|
|
-- ]]
|
|
-- local setter_template =
|
|
-- [[
|
|
-- return function(t, v)
|
|
-- local success, err = pcall(function (t, v)
|
|
-- t%s = v
|
|
-- end, t, v)
|
|
-- end
|
|
-- ]]
|
|
-- local getter_cache = {}
|
|
-- local setter_cache = {}
|
|
-- local function compile_normalized_property(field_path)
|
|
-- local getter, setter = getter_cache[field_path], setter_cache[field_path]
|
|
-- if not getter then
|
|
-- getter = assert(load(getter_template:format(field_path)))()
|
|
-- getter_cache[field_path] = getter
|
|
-- setter = assert(load(setter_template:format(field_path)))()
|
|
-- setter_cache[field_path] = setter
|
|
-- end
|
|
-- return getter, setter
|
|
-- end
|
|
-- function _M.compile_property(field_path)
|
|
-- return compile_normalized_property(normalize_field_path(field_path))
|
|
-- end
|
|
-- local set
|
|
-- local function create()
|
|
-- local o = {
|
|
-- dirty = false,
|
|
-- ignored = false,
|
|
-- opaque = false,
|
|
-- _stage = {},
|
|
-- _traced = {},
|
|
-- _lastversion = {},
|
|
-- _parent = false
|
|
-- }
|
|
-- return setmetatable(
|
|
-- o,
|
|
-- {
|
|
-- __index = o._stage,
|
|
-- __newindex = set,
|
|
-- __len = _M.len,
|
|
-- __pairs = _M.pairs,
|
|
-- __ipairs = _M.ipairs
|
|
-- }
|
|
-- )
|
|
-- end
|
|
-- local function mark_dirty(t)
|
|
-- while t and not t.dirty do
|
|
-- t.dirty = true
|
|
-- t = t._parent
|
|
-- end
|
|
-- end
|
|
-- set = function(t, k, v, force)
|
|
-- local stage = t._stage
|
|
-- local u = stage[k]
|
|
-- if _M.is_traceable(v) then
|
|
-- if _M.is(u) then
|
|
-- for _ in next, u._stage do
|
|
-- if v[k] == nil then
|
|
-- u[k] = nil
|
|
-- end
|
|
-- end
|
|
-- else
|
|
-- u = create()
|
|
-- u._parent = t
|
|
-- stage[k] = u
|
|
-- end
|
|
-- merge(u, v)
|
|
-- else
|
|
-- local traced = t._traced
|
|
-- local lastverison = t._lastversion
|
|
-- if stage[k] ~= v or force then
|
|
-- if not traced[k] then
|
|
-- traced[k] = true
|
|
-- lastverison[k] = u
|
|
-- end
|
|
-- stage[k] = v
|
|
-- mark_dirty(t)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- function _M.is(t)
|
|
-- local mt = getmetatable(t)
|
|
-- return type(mt) == "table" and mt.__newindex == set
|
|
-- end
|
|
-- function _M.is_traceable(t)
|
|
-- if type(t) ~= "table" then
|
|
-- return false
|
|
-- end
|
|
-- local mt = getmetatable(t)
|
|
-- return mt == nil or mt.__newindex == set
|
|
-- end
|
|
-- function _M.new(t)
|
|
-- local o = create()
|
|
-- if t then
|
|
-- merge(o, t)
|
|
-- end
|
|
-- return o
|
|
-- end
|
|
-- function _M.mark_changed(t, k)
|
|
-- local v = t[k]
|
|
-- if _M.is(v) then
|
|
-- mark_dirty(v)
|
|
-- else
|
|
-- set(t, k, v, true)
|
|
-- end
|
|
-- end
|
|
-- function _M.set_ignored(t, ignored)
|
|
-- t.ignored = ignored
|
|
-- if not ignored and t.dirty then
|
|
-- mark_dirty(t._parent)
|
|
-- end
|
|
-- end
|
|
-- function _M.set_opaque(t, opaque)
|
|
-- t.opaque = opaque
|
|
-- end
|
|
-- local k_stub =
|
|
-- setmetatable(
|
|
-- {},
|
|
-- {
|
|
-- __tostring = function()
|
|
-- return "k_stub"
|
|
-- end
|
|
-- }
|
|
-- )
|
|
-- function _M.compile_map(src)
|
|
-- local o = {}
|
|
-- for i = 1, #src do
|
|
-- local v = src[i]
|
|
-- local argc = #v
|
|
-- -- v = {argc, action, arg1, arg2, ..., argn}
|
|
-- v = {argc, unpack(v)} -- copy
|
|
-- for j = 1, argc - 1 do
|
|
-- local k = j + 2
|
|
-- local arg = normalize_field_path(v[k])
|
|
-- v[k] = compile_normalized_property(arg)
|
|
-- local p, c = nil, o
|
|
-- for field in arg:gmatch('([^%[%]\'"]+)') do
|
|
-- field = tonumber(field) or field
|
|
-- p, c = c, c[field]
|
|
-- if not c then
|
|
-- c = {}
|
|
-- p[field] = c
|
|
-- end
|
|
-- end
|
|
-- local stub = c[k_stub]
|
|
-- if not stub then
|
|
-- stub = {[1] = 1}
|
|
-- c[k_stub] = stub
|
|
-- end
|
|
-- local n = stub[1] + 1
|
|
-- stub[1] = n
|
|
-- stub[n] = v
|
|
-- end
|
|
-- end
|
|
-- return o
|
|
-- end
|
|
-- local function get_map_stub(map, k)
|
|
-- local map_k = map[k]
|
|
-- return map_k and map_k[k_stub]
|
|
-- end
|
|
-- local argv = {}
|
|
-- local function do_map(t, mappings, mapped)
|
|
-- for i = 2, mappings[1] do
|
|
-- local mapping = mappings[i]
|
|
-- if not mapped[mapping] then
|
|
-- mapped[mapping] = true
|
|
-- local argc = mapping[1]
|
|
-- argv[1] = t
|
|
-- for i = 2, argc do
|
|
-- argv[i] = mapping[i + 1](t)
|
|
-- end
|
|
-- local action = mapping[2]
|
|
-- action(unpack(argv, 1, argc))
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- local function next_traced(t, k)
|
|
-- local k = next(t._traced, k)
|
|
-- return k, t._stage[k], t._lastversion[k]
|
|
-- end
|
|
-- local function _commit(keep_dirty, t, sub, changed, newestversion, lastversion, map, mapped)
|
|
-- if not sub.dirty then
|
|
-- return false
|
|
-- end
|
|
-- local traced = sub._traced
|
|
-- local trace = sub._lastversion
|
|
-- local has_changed = next(traced) ~= nil
|
|
-- for k, v in next, sub._stage do
|
|
-- if _M.is(v) and not v.ignored then
|
|
-- if v.opaque then
|
|
-- has_changed = _commit(keep_dirty, t, v)
|
|
-- if has_changed then
|
|
-- if changed then
|
|
-- changed[k] = true
|
|
-- end
|
|
-- end
|
|
-- else
|
|
-- local c, n, l = changed and {}, newestversion and {}, lastversion and {}
|
|
-- has_changed = _commit(keep_dirty, t, v, c, n, l, map and map[k], mapped)
|
|
-- if has_changed then
|
|
-- if changed then
|
|
-- changed[k] = c
|
|
-- end
|
|
-- if newestversion then
|
|
-- newestversion[k] = n
|
|
-- end
|
|
-- if lastversion then
|
|
-- lastversion[k] = l
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- if has_changed and map then
|
|
-- local stub = get_map_stub(map, k)
|
|
-- if stub then
|
|
-- do_map(t, stub, mapped)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- for k, v, u in next_traced, sub do
|
|
-- if not keep_dirty then
|
|
-- traced[k] = nil
|
|
-- trace[k] = nil
|
|
-- end
|
|
-- if changed then
|
|
-- changed[k] = true
|
|
-- end
|
|
-- if newestversion then
|
|
-- newestversion[k] = v
|
|
-- end
|
|
-- if lastversion then
|
|
-- lastversion[k] = u
|
|
-- end
|
|
-- if map then
|
|
-- local stub = get_map_stub(map, k)
|
|
-- if stub then
|
|
-- do_map(t, stub, mapped)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- if not keep_dirty then
|
|
-- sub.dirty = false
|
|
-- end
|
|
-- return has_changed
|
|
-- end
|
|
-- local flag_map = {
|
|
-- [("k"):byte()] = "changed",
|
|
-- [("v"):byte()] = "newestversion",
|
|
-- [("u"):byte()] = "lastversion"
|
|
-- }
|
|
-- local function commit(t, map, diff_flags, keep_dirty)
|
|
-- local has_changed, diff
|
|
-- if diff_flags then
|
|
-- diff = {}
|
|
-- for i = 1, #diff_flags do
|
|
-- local field_name = flag_map[diff_flags:byte(i)]
|
|
-- if field_name then
|
|
-- diff[field_name] = {}
|
|
-- end
|
|
-- end
|
|
-- has_changed = _commit(keep_dirty, t, t, diff.changed, diff.newestversion, diff.lastversion, map, map and {})
|
|
-- else
|
|
-- has_changed = _commit(keep_dirty, t, t, nil, nil, nil, map, map and {})
|
|
-- end
|
|
-- return has_changed, diff
|
|
-- end
|
|
-- function _M.commit(t, map, diff_flags)
|
|
-- if type(map) == "string" then
|
|
-- map, diff_flags = nil, map
|
|
-- end
|
|
-- return commit(t, map, diff_flags, false)
|
|
-- end
|
|
-- function _M.diff(t, map, diff_flags)
|
|
-- if type(map) == "string" then
|
|
-- map, diff_flags = nil, map
|
|
-- end
|
|
-- return commit(t, map, diff_flags, true)
|
|
-- end
|
|
-- local function do_maps(t, sub, map, mapped)
|
|
-- for k, map_k in next, map do
|
|
-- local stub = map_k[k_stub]
|
|
-- if stub then
|
|
-- do_map(t, stub, mapped)
|
|
-- end
|
|
-- local v = sub[k]
|
|
-- if type(v) == "table" then
|
|
-- do_maps(t, v, map_k, mapped)
|
|
-- end
|
|
-- end
|
|
-- end
|
|
-- function _M.map(t, map)
|
|
-- do_maps(t, t, map, map and {})
|
|
-- end
|
|
-- local function forward_to(field, func)
|
|
-- return function(t, ...)
|
|
-- return func(t[field], ...)
|
|
-- end
|
|
-- end
|
|
-- function _M.len(t)
|
|
-- return #(t._stage)
|
|
-- end
|
|
-- _M.next = forward_to("_stage", next)
|
|
-- _M.pairs = forward_to("_stage", pairs)
|
|
-- _M.ipairs = forward_to("_stage", ipairs)
|
|
-- _M.unpack = forward_to("_stage", unpack)
|
|
-- return _M
|
|
-- -- local traceable = require "traceable"
|
|
-- -- local mt = {
|
|
-- -- __tostring = function(t)
|
|
-- -- return "object"
|
|
-- -- end
|
|
-- -- }
|
|
-- -- local object = setmetatable({}, mt)
|
|
-- -- local raw_data = {
|
|
-- -- boolean_value = false,
|
|
-- -- number_value = 1,
|
|
-- -- string_value = "string",
|
|
-- -- list_value = {1, 2, 3},
|
|
-- -- nested_table_value = {
|
|
-- -- nested_table_value = {
|
|
-- -- stub_value = true
|
|
-- -- }
|
|
-- -- },
|
|
-- -- object_value = object
|
|
-- -- }
|
|
-- -- local data = traceable.new(raw_data)
|
|
-- -- data.list_value = {1, 2, 3}
|
|
-- -- data.list_value = {1, 2, 3, 4}
|
|
-- -- data.list_value = {1, 2, 3}
|
|
-- -- traceable.commit(data)
|
|
-- -- print("traceable data.dirty 00000 ", data.dirty)
|
|
-- -- table.insert(data.list_value, 4)
|
|
-- -- traceable.commit(data)
|
|
-- -- print("traceable data.dirty aaaa", data.dirty)
|
|
-- -- traceable.commit(data)
|
|
-- -- print("traceable data.dirty bbbb", data.dirty)
|
|
-- -- table.insert(data.list_value, 5)
|
|
-- -- table.remove(data.list_value, #data.list_value)
|
|
-- -- print("traceable data.dirty ccccc", data.dirty)
|
|
|
|
|
|
|
|
local string_find = string.find
|
|
local string_match = string.match
|
|
local string_format = string.format
|
|
local string_sub = string.sub
|
|
local table_insert = table.insert
|
|
local table_remove = table.remove
|
|
local ipairs = ipairs
|
|
local tonumber = tonumber
|
|
local tostring = tostring
|
|
local type = type
|
|
local setmetatable = setmetatable
|
|
local getmetatable = getmetatable
|
|
|
|
local string_split = function(input, delimiter)
|
|
input = tostring(input)
|
|
delimiter = tostring(delimiter)
|
|
if (delimiter == '') then
|
|
return false
|
|
end
|
|
local pos, arr = 0, {}
|
|
-- for each divider found
|
|
for st, sp in function()
|
|
return string_find(input, delimiter, pos, true)
|
|
end do
|
|
table_insert(arr, string_sub(input, pos, st - 1))
|
|
pos = sp + 1
|
|
end
|
|
table_insert(arr, string_sub(input, pos))
|
|
return arr
|
|
end
|
|
|
|
local table_removebyvalue = function(array, value, removeall)
|
|
local c, i, max = 0, 1, #array
|
|
while i <= max do
|
|
if array[i] == value then
|
|
table_remove(array, i)
|
|
c = c + 1
|
|
i = i - 1
|
|
max = max - 1
|
|
if not removeall then
|
|
break
|
|
end
|
|
end
|
|
i = i + 1
|
|
end
|
|
return c
|
|
end
|
|
|
|
local function getByPath(t, path)
|
|
if not path then
|
|
return t
|
|
elseif string_find(path, "%.") then
|
|
local paths = string_split(path, ".")
|
|
local cur = t
|
|
local hasN = string_find(path, "%[") ~= nil
|
|
for i, keyname in ipairs(paths) do
|
|
local v
|
|
if hasN then
|
|
local n = tonumber(string_match(keyname, "^%[(.+)%]$"))
|
|
if n then
|
|
v = cur[n]
|
|
end
|
|
end
|
|
if not v then
|
|
v = cur[keyname]
|
|
end
|
|
if v then
|
|
cur = v
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
return cur
|
|
else
|
|
return t[path]
|
|
end
|
|
end
|
|
|
|
local function setByPath(t, path, value)
|
|
if not path then
|
|
t = value
|
|
elseif string_find(path, "%.") then
|
|
local paths = string_split(path, ".")
|
|
local cur = t
|
|
local count = #paths
|
|
local hasN = string_find(path, "%[") ~= nil
|
|
for i = 1, count - 1 do
|
|
local keyname = paths[i]
|
|
local v
|
|
if hasN then
|
|
local n = tonumber(string_match(keyname, "^%[(.+)%]$"))
|
|
if n then
|
|
v = cur[n]
|
|
keyname = n
|
|
end
|
|
end
|
|
if not v then
|
|
v = cur[keyname]
|
|
end
|
|
if v then
|
|
cur = v
|
|
else
|
|
cur[keyname] = {}
|
|
cur = cur[keyname]
|
|
end
|
|
end
|
|
local lastKeyname = paths[count]
|
|
if hasN then
|
|
lastKeyname = tonumber(string_match(lastKeyname, "^%[(.+)%]$")) or lastKeyname
|
|
end
|
|
cur[lastKeyname] = value
|
|
else
|
|
t[path] = value
|
|
end
|
|
return t
|
|
end
|
|
|
|
|
|
local table_monitor_
|
|
|
|
function table_monitor_(tb, callback, path)
|
|
local data_ = tb or {}
|
|
local monitors_ = {}
|
|
local spath = path
|
|
local function createKey(subpath, key)
|
|
if subpath then
|
|
if "number" == type(key) then
|
|
return string_format("%s.[%s]", subpath, key)
|
|
else
|
|
return string_format("%s.%s", subpath, key)
|
|
end
|
|
else
|
|
if "number" == type(key) then
|
|
return string_format("[%s]", key)
|
|
else
|
|
return key
|
|
end
|
|
end
|
|
end
|
|
local mt = {
|
|
__index = function(_, k)
|
|
if "__ismonitor__" == k then
|
|
return true
|
|
end
|
|
if not data_ then
|
|
return
|
|
end
|
|
local result = data_[k]
|
|
if type(result) == "table" then
|
|
if not monitors_[k] then
|
|
monitors_[k] = table_monitor_(result, callback, createKey(spath, k))
|
|
end
|
|
return monitors_[k]
|
|
else
|
|
return result
|
|
end
|
|
end,
|
|
__newindex = function(_, k, v)
|
|
local oldValue
|
|
if data_ then
|
|
oldValue = data_[k]
|
|
end
|
|
if oldValue ~= v then
|
|
data_[k] = v
|
|
if callback then
|
|
local key = createKey(spath, k)
|
|
if type(v) == "table" then
|
|
monitors_[k] = table_monitor_(v, callback, key)
|
|
callback(key, monitors_[k], oldValue)
|
|
else
|
|
if monitors_[k] then
|
|
monitors_[k] = nil
|
|
end
|
|
callback(key, v, oldValue)
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
__call = function(t, k, v)
|
|
if "[path]" == k then
|
|
return spath
|
|
elseif "string" == type(k) then
|
|
if v then
|
|
setByPath(t, k, v)
|
|
else
|
|
return getByPath(t, k)
|
|
end
|
|
else
|
|
return data_
|
|
end
|
|
end,
|
|
}
|
|
return setmetatable({}, mt)
|
|
end
|
|
|
|
local M = {}
|
|
function M.new(tb, callback)
|
|
if tb.__ismonitor__ then
|
|
tb = tb()
|
|
end
|
|
local callbacks = {}
|
|
if callback then
|
|
callbacks[#callbacks + 1] = callback
|
|
end
|
|
local addCallback = function(self, cb)
|
|
for _, cc in ipairs(callbacks) do
|
|
if cc == cb then
|
|
return
|
|
end
|
|
end
|
|
callbacks[#callbacks + 1] = cb
|
|
end
|
|
local removeCallback = function(self, cb)
|
|
table_removebyvalue(callbacks, cb, true)
|
|
end
|
|
local removeAllCallbacks = function(self)
|
|
callbacks = {}
|
|
end
|
|
local cb = function(path, value, oldValue)
|
|
for _, listener in ipairs(callbacks) do
|
|
listener(path, value, oldValue)
|
|
end
|
|
end
|
|
local result = table_monitor_(tb, cb)
|
|
|
|
local mt = getmetatable(result)
|
|
local index_fn = mt.__index
|
|
mt.__index = function(t, k)
|
|
if k == "addCallback" then
|
|
return addCallback
|
|
elseif k == "removeCallback" then
|
|
return removeCallback
|
|
elseif k == "removeAllCallbacks" then
|
|
return removeAllCallbacks
|
|
else
|
|
return index_fn(t, k)
|
|
end
|
|
end
|
|
setmetatable(result, mt)
|
|
return result
|
|
end
|
|
|
|
return M
|
|
|
|
|
|
-- local traceable = require("traceable")
|
|
-- local a = {
|
|
-- a1 = 1,
|
|
-- a2 = { 1, 2, 3},
|
|
-- a3 = {
|
|
-- a31 = "a",
|
|
-- a32 = {
|
|
-- "b"
|
|
-- },
|
|
-- },
|
|
-- }
|
|
|
|
-- local ta = traceable.new(a, function ( path, value, oldValue )
|
|
-- print(string.format("table change at %s from ", path), oldValue, "to ", value)
|
|
-- end)
|
|
|
|
-- print("start~~~")
|
|
-- print(ta.a3.a32[1])
|
|
-- ta.a3.a32[2] = 2
|
|
-- ta.a3.a32 = {"c"}
|
|
-- ta.a3.a32.a = 3
|
|
-- ta.a3.a32 = 33
|
|
-- print("~~~", ta.a3.a32)
|
|
-- print(ta.a2[3])
|
|
-- ta.a3.a32 = {33}
|
|
-- print(ta.a3.a32[1])
|
|
-- ta.a3.a32 = nil
|
|
-- ta.a3.a32 = {5}
|
|
-- print(ta.a3.a32)
|
|
-- print("over~~~") |