├── test.lua └── syncobj.lua /test.lua: -------------------------------------------------------------------------------- 1 | local syncobj = require "syncobj" 2 | 3 | local source = syncobj.source() 4 | local clone = syncobj.clone() 5 | 6 | local obj = source:new { x = 1, y = 2 } 7 | 8 | local function print_table(obj, prefix) 9 | print(prefix) 10 | if obj then 11 | for k,v in pairs(obj) do 12 | print("",k,v) 13 | end 14 | end 15 | end 16 | 17 | print_table(obj, "obj") 18 | 19 | local diff = source:diff(obj) 20 | 21 | local cobj = clone:patch(diff) 22 | 23 | print_table(cobj, "clone") 24 | 25 | obj.x = 3 26 | obj.y = nil 27 | 28 | print_table(obj, "obj") 29 | 30 | local diff = source:diff(obj) 31 | 32 | local cobj = clone:patch(diff) 33 | 34 | print_table(cobj, "clone") 35 | 36 | local diff = source:reset(obj) 37 | 38 | local cobj = clone:patch(diff) 39 | 40 | print_table(cobj, "clone") 41 | 42 | local rlist = source:collect() 43 | print_table(rlist, "remove") 44 | clone:collect(rlist) 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /syncobj.lua: -------------------------------------------------------------------------------- 1 | local tinsert = table.insert 2 | local pairs = pairs 3 | local setmetatable = setmetatable 4 | local next = next 5 | 6 | local syncobj = {} 7 | local weak_meta = { __mode = "kv" } 8 | 9 | local source = {} ; source.__index = source 10 | 11 | local function object_newindex(self, key, value) 12 | self.__real[key] = value 13 | self.__change[key] = value 14 | end 15 | 16 | local function object_pairs(self) 17 | return next, self.__real, nil 18 | end 19 | 20 | function source:new(init) 21 | local obj = { 22 | __real = {}, 23 | __last = {}, 24 | __change = {}, 25 | __changeset = { self.id }, 26 | } 27 | self.id_list[self.id] = self.id_mark 28 | self.cache[self.id] = obj 29 | self.id = self.id + 1 30 | setmetatable(obj, { 31 | __index = obj.__real, 32 | __pairs = object_pairs, 33 | __newindex = object_newindex, 34 | }) 35 | if init then 36 | for k,v in pairs(init) do 37 | obj[k] = v 38 | end 39 | end 40 | return obj 41 | end 42 | 43 | function source:diff(obj) 44 | local i = 2 45 | local last = obj.__last 46 | local changeset = obj.__changeset 47 | local change = obj.__change 48 | for k,v in pairs(change) do 49 | if last[k] ~= v then 50 | changeset[i] = k 51 | changeset[i+1] = v 52 | i = i + 2 53 | end 54 | change[k] = nil 55 | end 56 | local real = obj.__real 57 | local remove 58 | for k,v in pairs(last) do 59 | if real[k] == nil then 60 | remove = remove or {} 61 | tinsert(remove, k) 62 | end 63 | last[k] = nil 64 | end 65 | for k,v in pairs(real) do 66 | last[k] = real[k] 67 | end 68 | changeset[i] = remove 69 | for j = i+1, #changeset do 70 | changeset[j] = nil 71 | end 72 | return changeset 73 | end 74 | 75 | function source:reset(obj) 76 | obj.__last = {} 77 | obj.__change = {} 78 | local lastid = obj.__changeset[1] 79 | local changeset = { self.id } 80 | self.cache[lastid] = nil 81 | self.cache[self.id] = obj 82 | self.id = self.id + 1 83 | obj.__changeset = changeset 84 | for k,v in pairs(obj.__real) do 85 | tinsert(changeset, k) 86 | tinsert(changeset, v) 87 | end 88 | return changeset 89 | end 90 | 91 | function source:collect() 92 | local mark = not self.id_mark 93 | self.id_mark = mark 94 | local id_list = self.id_list 95 | for id in pairs(self.cache) do 96 | id_list[id] = mark 97 | end 98 | local remove_list 99 | for k,v in pairs(id_list) do 100 | if v ~= mark then 101 | remove_list = remove_list or {} 102 | tinsert(remove_list, k) 103 | id_list[k] = nil 104 | end 105 | end 106 | return remove_list 107 | end 108 | 109 | function syncobj.source() 110 | local channel = { 111 | id = 1, 112 | id_mark = true, 113 | id_list = {}, 114 | cache = setmetatable({}, weak_meta), 115 | } 116 | return setmetatable(channel, source) 117 | end 118 | 119 | local clone = {} ; clone.__index = clone 120 | 121 | function clone:patch(diff) 122 | local id = diff[1] 123 | local obj = self[id] 124 | if obj == nil then 125 | obj = {} 126 | self[id] = obj 127 | end 128 | local n = #diff 129 | for i = 2, n, 2 do 130 | obj[diff[i]] = diff[i+1] 131 | end 132 | if n % 2 == 0 then 133 | -- remove keys 134 | for _, v in ipairs(diff[n]) do 135 | obj[v] = nil 136 | end 137 | end 138 | return obj 139 | end 140 | 141 | function clone:collect(remove_set) 142 | if remove_set then 143 | for _, id in ipairs(remove_set) do 144 | self[id] = nil 145 | end 146 | end 147 | end 148 | 149 | function syncobj.clone() 150 | local channel = {} 151 | return setmetatable(channel, clone) 152 | end 153 | 154 | return syncobj 155 | --------------------------------------------------------------------------------