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

-- -- 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~~~")