Best way to detect Value changes in a table

Hello DevForum!

Lately I’ve been working on a number spinner module, and I’m running into an issue. Basically, I’ve been trying to find a way to detect value changes in a table, and I did find a workaround.

function Spinner.new(properties)
	local self = typeof(properties) == "table" and properties or {
		Frame = nil,
		Value = "0",
		Duration = 0.25,
		Alignment = "Right",
	}
	self.Value = self.Value and tostring(self.Value) or "0"
	
	setmetatable(self, Spinner)
	
	self:CreateDigits()
	
	return setmetatable({}, { -- my weird "proxy" method which works apparently
		__metatable = self,
		__index = function(_, index) return self[index] end,
		__newindex = function(e, index, value)
			if index == "Value" then self:Spin(value)
			else self[index] = value
			end
		end,
	})
end

It works fancy and all but here’s the issue: if I try to print the object it returns (the “proxy” metatable), it prints an empty table

local spinner = spinnerModule.new{
		Frame = frame,
		--Value = "0",
		Duration = 0.5,
		Alignment = align,
		Padding = 0,
		LabelProperties = {
			Font = Enum.Font.Gotham,
			TextColor3 = Color3.fromRGB(255, 255, 255)
		}
}

counter:GetPropertyChangedSignal("Value"):Connect(function() -- Counter is an IntValue in ReplicatedStorage
    spinner.Value = counter.Value
    print(spinner) -- prints {}
    print(spinner.Value) -- prints 5 because of the __index as expected
end)

So is there any way if i do print(spinner) it prints the self table? I’ve been researching but couldn’t find any help. I’m pretty sure that even the “proxy” method I’m using to detect a value change is inefficient (I came up with that myself after over two whole hours of experimenting :roll_eyes:), so if you’ve got any workarounds, please do suggest.

Oh yeah and I’m bad at explaining so if I’m not clear please tell.

Thank you in advance,
Vex

P.S: I’m trying to avoid BindableEvents to make the event on purpose.

1 Like

Why not setmetatable(self,{other metamethods})

You know if you do that, you can easily print self table.

1 Like

I’ve tried that already before, however there’s an issue:

It doesn’t detect the value change then.

1 Like

Haha, time to use something known as getters and setters. (Flashbacks to coding in Java / OOP langauges)

You might have to do

Spinner:SetAlignment()  --To set value
Spinner:GetAlignment() --To get value

It’s more work for you, but its more efficient and also allows you to run a function when someone is going to access or set a value

1 Like

No, you got it wrong lol
Basically, I have a function called module:Spin(), which the users can call by themselves too. However, I want to make it simpler to use just by making it so that they change spinner.Value to something, and the :Spin function is called automatically by listening to the change in value.

Like I mentioned,

print(spinner.Value)

works completely fine, so that’s not that big of an issue.

The only issue here is the empty table printing, instead I want it to print the self table.

1 Like

Here’s a fancy way, using call metamethod, yes, this is crazy…

local Spinner = setmetable({},{
    __metatable = self,
    __call = function(self,index,value)
            if not index then 
               return getmetatable(self) 
            end

           
          if index == "Value" the 
             getmetatable(Table)[index] = value
            Table:Spin()
          end
    end)
})


Spinner("Value","SomeValue") -- Runs :Spin()
print(Spinner())  -- Prints your self table

That should work out for you lol

1 Like

Why not trying adding another metamethod __tostring

{__tostring = function(_) return self end}

This could return the self table you created when you print the table (which internally uses tostring).

1 Like

You aren’t getting me ;-;
Basically I already have this :Spin function:

function Spinner.Spin(self, val)
	self.Duration = tonumber(self.Duration) or 0.5

	self:CreateDigits()
	
	self.Value = val and tostring(val) or tonumber(self.Value) or 0
	
	for i = 1, #self.Value do
		local f = self.Frame:FindFirstChild("_Digit_" .. i)
		if not f then continue end
		self:AlignSpacing()
		f.Canvas:TweenPosition(UDim2.new(0, 0, -tonumber(self.Value:sub(i, i)), 0), nil, nil, self.Duration, true)
	end
end

If the user wants they can just call spinner:Spin(value) but to make it easier, I’m doing it so that if they just do:

spinner.Value = value

The gui automatically updates.
My original method worked just fine with that but the thing is if i tried to:

print(spinner)

, it prints {}, because its not the real self, its the proxy metatable.

I think just a better way of listening to value changes inside a table would be the solution

@Razor_IB , your method gives this error:
image
(the error that pops up when you try to change the value of a table without rawset)

1 Like

My bad for not noticing it, but a question what is your motive with printing spinner I don’t see any practical use of it.

1 Like

I have this habit of printing an output table to see its properties and stuff, and I’m pretty sure many others do too (or I’m living under a rock). That’s why I’m just doing it so that my module is more user friendly.

It’s fine if I don’t get a solution really, my module is functioning perfectly fine :slight_smile:

1 Like

I would just embed an int/string/whatever value into your table.

self.Duration= Instance.new(“NumberValue”)

Just make sure to clean it up afterwards. You can use a maid to do this.

2 Likes

Well since its just for visualizing, inside __tostring metamethod you could just loop through the Clone table create a string with its value and return the string.

2 Likes

Looking up to instances was the last thing I wanted to do but I’ll go ahead thanks!

Yeah probably thats better so I’ll mark your post as the solution :slight_smile:

2 Likes