Detect changes made to a deep table

Hey there!

I’m working on an ambitious new project and I’ve run into an issue. Server => Client communication. A significant amount of data must be transferred from the server to the client to keep the client updated on what’s going on.

My hypothesis for this was to detect when the players’ data table was modified, then run some checks and set some hard values in the players’ directory that the player could read. Now maybe hard values aren’t the way to go, but I believe it’s the most efficient for my application.

image

This is the current code I have. It successfully detects changes with the surface values of the players’ data table. Meaning if I made a change to a deep table, this callback would never fire. I know I can use recursion to accomplish this. I just need someone smarter than me to help me.

Personally, I would suggest using a method to explicitly tell you the data has changed, but if you want to do it using metatables something like the following should work.

local function listenToDeepChanges(t, onChange)
    local mt = {
        __index = t,
        __newindex = function (self, k, v)
            if type(v) == "table" then
                rawset(t, k, listenToDeepChanges(v, onChange))
            else
                rawset(t, k, v)
            end
            onChange(t, k, v)
        end
    }
    for k, v in pairs(t) do
        if type(v) == "table" then
            t[k] = listenToDeepChanges(v, onChange)
        end
    end
    return setmetatable({}, mt)
end

All you need to do then is call

local playerDataListening = listenToDeepChanges(playerData, function (updatedTable, key, newValue)
    -- Do something here
end)
7 Likes

When faced with this type of problem I have opted to making an explicit function call when a data change occurs, like @ComplexGeometry mentioned above me.

If this approach is taken, to reduce code duplication, the data being changed should be encapsulated in a function/object that owns the data.

1 Like

What about this?

Code

Code

local function UpdateChanges(_,...)
	print("UpdateChanges",...)
end

local MetaTable = {}
MetaTable.__index = MetaTable
function MetaTable:__newindex(Key,Value)
	print("__newindex",Key,Value)
	if typeof(Key) == "table" then
		Key = setmetatable(Key,MetaTable)
	elseif typeof(Value) == "table" then
		Value = setmetatable(Value,MetaTable)
	end
	rawset(self,Key,Value)
	UpdateChanges(self,Key,Value)
end

Table = setmetatable({},MetaTable)

Table.New = "Table.New 1"
print(Table.New)

Table.SubTable = {}
Table.SubTable.New = "Table.SubTable.New 2"
print(Table.SubTable.New)

Table[Table.SubTable] = {New = "Table[Table.SubTable].New 3"}
print(Table[Table.SubTable].New)

Output

__newindex New Table.New 1

UpdateChanges New Table.New 1

Table.New 1

__newindex SubTable table: 0xf979b7bda96bf573

UpdateChanges SubTable table: 0xf979b7bda96bf573

__newindex New Table.SubTable.New 2

UpdateChanges New Table.SubTable.New 2

Table.SubTable.New 2

__newindex table: 0xf979b7bda96bf573 table: 0x999b379c0245d1c3

UpdateChanges table: 0xf979b7bda96bf573 table: 0x999b379c0245d1c3

Table[Table.SubTable].New 3

1 Like

The provided code is throwing a stack overflow. Not sure why either.

Probably because of the index method? I’m taking a shot in the dark here, try replacing the index metamethod with a raw function.

__index = function(self, index)
    return rawget(self, index)
end
1 Like