First thing to take a note of is that __newindex and __index metamethods are only triggered when they handle nil value in target table. If you want to track every single change, you can't just use __newindex, because once you write a value, subsequent calls won't do anything. Instead, you need to use a proxy table.
Another important thing to consider is tracking paths of accessed members.
Last, but not least, you need to remember to use raw access functions like e.g. rawget when implementing your handlers. Otherwise, you may encounter stack overflows or other weird behaviour.
Let's have a trivial example to illustrate the problems:
local mt = {}
function mt.__newindex (t, key, value)
if type(value) == "table" then
rawset(t, key, setmetatable(value, mt)) -- Set the metatable for nested table
-- Using `t[key] = setmetatable(value, mt)` here would cause an overflow.
else
print(t, key, "=", value) -- We expect to see output in stdout for each write
rawset(t, key, value)
end
end
local root = setmetatable({}, mt)
root.first = 1 -- table: 0xa40c30 first = 1
root.second = 2 -- table: 0xa40c30 second = 2
root.nested_table = {} -- /nothing/
root.nested_table.another = 4 -- table: 0xa403a0 another = 4
root.first = 5 -- /nothing/
Now, we need to deal with them. Let's start with a way to create a proxy table:
local
function make_proxy (data)
local proxy = {}
local metatable = {
__index = function (_, key) return rawget(data, key) end,
__newindex = function (_, key, value)
if type(value) == "table" then
rawset(data, key, make_proxy(value))
else
print(data, key, "=", value) -- Or your save function here!
rawset(data, key, value)
end
end
}
return setmetatable(proxy, metatable) -- setmetatable() simply returns `proxy`
end
This way you have three tables: proxy, metatable and data. User accesses proxy, but because it's completely empty on each access either __index or __newindex metamethods from metatable are called. Those handlers access data table to retrieve or set the actual values that user is interested in.
Run this in the same way as previously and you will get an improvement:
local root = make_proxy{}
root.first = 1 -- table: 0xa40c30 first = 1
root.second = 2 -- table: 0xa40c30 second = 2
root.nested_table = {} -- /nothing/
root.nested_table.another = 4 -- table: 0xa403a0 another = 4
root.first = 5 -- table: 0xa40c30 first = 5
This should give you an overview on why you should use a proxy table here and how to handle metamethods for it.
What's left is how to identify the path of the field that you are accessing. That part is covered in another answer to another question. I don't see a reason to duplicate it.
MyTable.Value = value2invokeMySaveFunctionagain? - Egor Skriptunoff