Connect to Variable changes in tables with ProxyTable!

BEFORE GETTING THE MODULE


Ensure you have stravant’s GoodSignal Module in your game set up to add it into the Module!
And add it at the start of the ProxyTable Module like this:

local GoodSignal = require(game.ReplicatedStorage.GoodSignal)

Because this module uses GoodSignal, that means you can safely yield inside connection.

USAGE EXAMPLE


local ProxyTable = require(game.ReplicatedStorage.Libraries.ProxyTable)

-- create the proxyTable

local Values = {
	Money = 0,
	Health = 100,
}

local PlayerData = ProxyTable.new(Values)

PlayerData:Changed('Money'):Connect(function()
	print(PlayerData.Money) 
end)

PlayerData:Changed('Health'):Connect(function()
	if PlayerData.Health < 1 then
		print('Player has died!')
	end
end)

PlayerData.Money = 50 -- will print 50
PlayerData.Money = 10 -- will print 10

PlayerData.Health -= 100 -- will print Player has died!

MISC


You don’t have to put already created table to create ProxyTable, you can leave arguments blank.

In times where you don’t want to fire an connection when variable changes, use ProxyTable:GetRaw() method and use the table you get.

It supports metatables! So now you can create objects that you can listen for changes!
You just need to wrap self like this before returning it:

local ProxyTable = require(game.ReplicatedStorage.Libraries.ProxyTable)

local Object = {}
Object.__index = Object

function Object.new()
	local self = setmetatable({}, Object)
	
	self.Variable = 5
	
	return ProxyTable.new(self)
end

function Object:Add(amount)
	self.Variable += amount
end

local CreatedObject = Object.new()

CreatedObject:Changed('Variable'):Connect(function()
	print('Variable has changed to', tostring(CreatedObject.Variable))
end)

CreatedObject:Add(10) -- will print Variable has changed to 15

You can also make ProxyTable give you old variable automatically by giving a second argument when creating it:

local ProxyTable = require(game.ReplicatedStorage.Libraries.ProxyTable)

local Table = ProxyTable.new(nil, true)

Table.Stuff = 10

Table:Changed('Stuff'):Connect(function(oldVariable)
	print('Stuff changed from', tostring(oldVariable), 'to', tostring(Table.Stuff))
end)

Table.Stuff += 5 -- will print Stuff changed from 10 to 15
Table.Stuff += 5 -- will print Stuff changed from 15 to 20
Table.Stuff += 5 -- will print Stuff changed from 20 to 25
Table.Stuff += 5 -- will print Stuff changed from 25 to 30

THE MODULE


-- Created by ArtemVoronin0 05/09/2024
-- ProxyTable, an easy module that let you watch over changed values in table.

local GoodSignal -- require good signal implementation there!

export type ProxyTable = {
	Changed : (self : ProxyTable, index : any) -> nil, -- GoodSignal.Signal (change to it if you have own Signal type)
    GetRaw : (self : ProxyTable) -> {[any] : any},
}


local ProxyTable = {}
ProxyTable.__index = ProxyTable

local signalMeta = {
	__index = function(T, K)
		T[K] = GoodSignal.new()

		return T[K]
	end,
}

local proxyMeta = {
	__index = function(T, K : any)

		if not T.__proxy[K] then return ProxyTable[K] end

		return T.__proxy[K]
	end,

	__newindex = function(T, K : any, V : any)

		if V == T.__proxy[K] then return end

		T.__proxy[K] = V

		if rawget(T.__signals, K) then
			T.__signals[K]:Fire()
		end
	end,
	
	__iter = function(T)
		return next, T.__proxy
	end,
	
	__len = function(T)
		return #T.__proxy
	end,
}

local proxyMetaWithSave = table.clone(proxyMeta)

proxyMetaWithSave.__newindex = function(T, K : any, V : any)
	if V == T.__proxy[K] then return end

	local oldValue : any = T[K]

	T.__proxy[K] = V

	if rawget(T.__signals, K) then
		T.__signals[K]:Fire(oldValue)
	end
end

function ProxyTable.new(t : {[any] : any}?, passOldValue : boolean?) : ProxyTable

	if not t then t = {} end

	local __signals = setmetatable({}, signalMeta)
	local __proxy = t

	local self = setmetatable({__signals = __signals, __proxy = __proxy}, if passOldValue then proxyMetaWithSave else proxyMeta)

	return self
end

function ProxyTable:Changed(index : any)
	return self.__signals[index]
end

function ProxyTable:GetRaw()
	return self.__proxy
end

return ProxyTable
4 Likes

can you use this to detect changes in modules

yes if that module returns a table

This is also known as a Getter/Setter for anyone interested while also coercing the variable into a Bindable/Signal of sorts in the background

Still, pretty nice, but what if you had an option to lock variables?

Woah this is so cool! Well done man

it didnt work when i tried it though

Sorry, I changed the code after publishing it after I noticed a bug, copy a new version of the module, it should work.

1 Like

I’m trying to detect when a connection has been inserted into masterTable.connections, however the changed function does not fire when I add a connection into the table. It does fire when the money value is updated.

How can I detect when a connection is added?

local masterTable = {
	connections = {},
	money = 0,
}

local PlayerData = proxyTable.new(masterTable)

PlayerData:Changed("connections"):Connect(function()
	print('connection changed')
end)

PlayerData:Changed("money"):Connect(function()
	print('money changed')
end)

table.insert(PlayerData.connections, workspace.ChildAdded:Connect(function()
	
end))

PlayerData.money = 1

If connection changes from table to anything else it’ll fire, to see changes in connection, instead of table, connection should hold a special object that will let you connect to changes. Soon I’ll send a new version of ProxyTable, comfortably uploaded to github with some tweaks that will up perfomance and will clean signals that are not connected by anything, If that’s even possible, but I’ll try. I would even add an ability to option ProxyTable, like being able to connect when variable is added, removed, changed (changed is whachu want). I’m making this optional because all this behavior is slowing down stuff, I benchmarked and if you add a ability to connect removed, changed, added with old value passing, it’ll slow ProxyTable proficiency nearly twice, like 70-100%.

1 Like