Are There Metamethods Or Alternative Methods For Listening To An Index Being 'Set'?

For example:

Listen = function(Table, Callback)
	setmetatable(Table, {
		__index = Table,
		__newindex = function(self, Index, Value)
			rawset(self, Index, Value)
			Callback(Index, Value)
		end,
	})
end

local TestTable = {}

Listen(TestTable, function(Index, Value)
	print(Index, Value)
end)

TestTable[1] = 10 -- Ouput: 1 10
TestTable[1] = 5 -- Does not trigger anything but i want it to

My goal is to trigger a callback function everytime an Index’s Value is changed in a Mixed Table. Like shown above.

I’ve looked almost everywhere, and the solutions some people have suggested are rather undesirable as they add a bunch of bloat. Surely there’s a simple trick or API to solve this issue?

Edit: After utilizing solutions in the replies, and experimenting with my own this seems to work perfectly for my needs. Thanks everyone!

Solution:

local function Listen(Table, Callback, Key:(string|number)?)

	--// A callback must be passed, otherwise listening is illogical
	if not Callback then
		warn("A callback function must be provided!")
		return
	end

	--// Copy Table
	local Proxy = {}

	for i,v in pairs(Table) do
		Proxy[i] = v
	end

	--// Set all to nil so non-empty tables will trigger
	for i,_ in pairs(Table) do
		Table[i] = nil
	end

	setmetatable(Table, {
		__index = Proxy,
		__newindex = function(self, Index, Value)
			--// Check to make sure the new value is different than the last one
			--// This is because an index's value being set to the exact same value it already has will trigger this again
			if Proxy[Index] == Value then return end
			Proxy[Index] = Value

			if Key and Key == Index then
				--// If Key is provided then it's indicative a specific item in the Table is meant to be listened to and thusly the callback executed
				Callback(Index, Value)
			elseif not Key then
				--// If Key is left out then callback will be run regardless of what item was changed
				Callback(Index, Value)
			end
		end,
	})
end

local TestTable = {[1] = 15} --// TestTable can be any table you want, empty, full, whatever 1=15 is just a test

Listen(TestTable, function(Index, Value)
	--// Run listener event call
	print(Index, Value)
end)

TestTable[1] = 10 --// Output: 1 10
TestTable[1] = 10 --// Output: 1 10
TestTable[1] = 5 --// Output: 1 5
TestTable[1] = nil --// Output: 1 nil
TestTable[1] = "Hello" --// Output: 1 "Hello"
print(TestTable[1]) --// Output: "Hello"

No API that I know of, but the solution is pretty simple. Create an empty proxy table linked to the original.

local original = {}

local listener = setmetatable({}, {
	__index = original;
	__newindex = function(self, i, v)
		print("Previous pair: ", i, original[i])
		original[i] = v
		return;
	end,
})

listener[1] = "one" --> Previous pair:  1 nil
listener[1] = true --> Previous pair:  1 one
print(listener[1]) --> true
1 Like

Played around with it, I don’t think this is quite what I am looking for. Unless you could somehow retrofit it into my code.

Thanks though!

After fiddling around a bit more I think I might’ve figured out some kind of prototype. Not sure if it’s halal but :man_shrugging: it’s 5 AM and haven’t slept.

Listen = function(Table, Callback)
	
	local Proxy = {unpack(Table)} --// Copy Table
	
	setmetatable(Table, {
		__index = function(tab,Index)
			return Proxy[Index]
		end,
		__newindex = function(self, Index, Value)
			rawset(self,Index,nil)
			Proxy[Index] = Value
			Callback(Index, Value)
		end,
	})
end

local TestTable = {}

Listen(TestTable, function(Index, Value)
	print(Index, Value)
end)

TestTable[1] = 10 --// Output: 1 10
TestTable[1] = 5 --// Output: 1 5
TestTable[1] = nil --// Output: 1 nil
TestTable[1] = "Hello" --// Output: 1 "Hello"
print(TestTable[1]) --// Output: "Hello"

--// Seems to work lol

Feel free to criticize or improve or completely shoot the idea down. :+1:

That’s it! Only one small change, you should empty the original table, because if the sent table is not empty and an index with a value is changed, the __newindex won’t be fired until the value is set to nil.

local TestTable = {[1] = 10}

local function Listen(Table, Callback)

	local Proxy = {unpack(Table)}
	
	for i in pairs(TestTable) do
		TestTable[i] = nil
	end

	setmetatable(Table, {
		__index = Proxy,
		__newindex = function(self, Index, Value)
			if Proxy[Index] ~= Value then
				Proxy[Index] = Value
				Callback(Index, Value)
			end
			return;
		end,
	})
end

Listen(TestTable, function(Index, Value)
	print(Index, Value)
end)

TestTable[1] = 10 -- not affected
TestTable[1] = 5 --// Output: 1 5
TestTable[1] = nil --// Output: 1 nil
TestTable[1] = "Hello" --// Output: 1 "Hello"
print(TestTable[1]) --// Output: "Hello"

PS: Get some sleep, mate!

1 Like

Here’s a script I thought up, it seems to work:

local Object = {}

setmetatable(Object, {
    __index = function(self, i)
        print("Get", i)
        return getmetatable(self)[i]
    end,
    __newindex = function(self, i, v)
        getmetatable(self)[i] = v
        print("Set", i, "to", v)
    end,
})

Object.One = "Two"  --> Set One to Two
print(Object.One)   --> Get One
                    --> Two
print(Object)       --> {}
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.