Proxy Listeners

Here’s my first forum post. I’ll be sharing with you what I’ve called a “Proxy Listener”. Its core features are :

  • Event based updates to a table.

  • A key path for modified nested tables.

It’s come to my attention that this sounds like a “Shadow Table”, however the proxy listener permits a bit more usability and supports nested tables.

Here is the magic sauce :

local function getProxyListener(t, accessSignal : BindableEvent, keyNamePath : string?)
    if not (accessSignal and accessSignal.Fire) then return error("Did not provide access signaler") end

    keyNamePath = keyNamePath or "Base"

    local prox = newproxy(true)
    local proxMeta = getmetatable(prox)

    proxMeta.__index = function(self, k)
        local val = t[k]
        
        if val == nil then
            local interpretedKey = k:gsub("_", "")
            
            if interpretedKey == "true" then
                return t
            else
                return t[interpretedKey]
            end
        end        
        
        if type(val) == "table" then
            return getProxyListener(val, accessSignal, keyNamePath .. "." .. k)
        else
            return val
        end
    end

    proxMeta.__newindex = function(self, k, v)
        if (type(v) == "table") or (type(v) == "function") then error("Cannot assign a table or function") end

        t[k] = v

        accessSignal:Fire(keyNamePath, k, v)
    end

    return prox
end   

Example usage :

local _realTable = {Nest = { Nest = {Test = 0} } }
local tAccessSignal = Instance.new("BindableEvent")


local UsedTable = getProxyListener(_realTable, tAccessSignal)

-- Sometimes it is necessary to retrieve the "true" version of the table (for example, in loops):

for k, v in pairs(UsedTable._true) do
-- Code
end

tAccessSignal.Event:Connect(function(dataPath, key, value)
 -- Fire event to the client (player, dataPath, key, value)
end)

I realize that creating a signal explicitly beforehand may be a bit uncomfortable. Please do share if you take the time to further condense this method of listening to table modifications.

The most useful thing in my opinion through my limited experience with using it is the ability to replicate changes from one machine to another without losing your references to an old table. You can use the parameters passed to easily and directly modify the specific nested key that was modified. This can be done with the following method :

local MyTable = {}

SomeService.TableUpdated:Connect(function(path : string, nodeKey, value : any)
	local terminalNode = MyTable
		
	for _, node in ipairs(path:split(".")) do
		terminalNode = terminalNode[node]
	end
		
	terminalNode[nodeKey] = value
end)

Note: depending on your use, you may need to drop the “Base” key (can be modified on initialization) when parsing the path.

You’ll find that any reference to the table (and indeed, references to a nested table within MyTable) are perfectly usable and have no need to be updated with a :GetData() method - it’s all ready to go at any time !

I hope the readers may find this useful and please comment any concerns or suggestions !

7 Likes