How I can detect value change of metatable?

Hello, I’m trying to learn how metatables work. But, when trying to link Frame object to it, and change value of metatable, I understood that IDK how I can reflect that change to frame:

local Object = {}
Object.__index = Object

function Object.new(Size, Position, Rotation...)
	local NewObject = {}
	NewObject.Size = Size
	NewObject.Position = Position
	NewObject.Rotation = Rotation
	Frame2D = Instance.new("Frame")
	Frame2D.Size = Size
	Frame2D.Position = Position
	Frame2D.Rotation = Rotation
	NewObject.Frame = Frame2D
	return setmetatable(NewObject, Object)
end

...

local function changer(Object, newValue)
	Object.Position = newValue
--  Object is METATABLE and not roblox instance.
--  I want change Frame2D position to changed metatable position.
end

How I can detect, when I try change smth in metatable, if that key already exists, without additional Object.Frame.Position = newValue?

There is a metamethod named __newindex which can be set to a function that fires whenever an index is changes to a particular value.

Object.__newindex = function(self, key, value)
    if key == "Position" then
        self.Frame.Position = value
    end
end
  • where self is the table (Object)
  • where key is the index changed
  • where value is the value set to key

This not working at all somewhy, even without if statement when trying to print any change.
Will __newindex fire if there’s already value existing?

Code?

local Object = {}
Object.__index = Object

function Object.new(Size, Position, Rotation, Anchored, Velocity, Shape, Color)
	Rotation = Rotation or 0
	Anchored = if Anchored == nil then true else Anchored
	Velocity = Velocity or Vector2.new(0, 0)
	Shape = Shape or "Block"
	Color = Color or "Black"
	local NewObject = {}
	NewObject.Size = Size
	NewObject.Position = Position
	NewObject.Rotation = Rotation
	NewObject.Anchored = Anchored
	NewObject.Velocity = Velocity
	NewObject.Shape = Shape
	NewObject.Color = Color
	local Frame2D
	if Shape == "Block" then
		Frame2D = Instance.new("Frame")
		Frame2D.BackgroundColor3 = ObjectColors[Color]
	elseif Shape == "Ball" then
		Frame2D = Instance.new("Frame")
		Frame2D.BackgroundColor3 = ObjectColors[Color]
		local Corners = Instance.new("UICorner")
		Corners.CornerRadius = UDim.new(1, 0)
		Corners.Parent = Frame2D
	elseif Shape == "Triangle" then
		Frame2D = Instance.new("ImageLabel")
		Frame2D.BackgroundTransparency = 1
		Frame2D.Image = "http://www.roblox.com/asset/?id=14248930926"
		Frame2D.ImageColor3 = ObjectColors[Color]
	end
	Frame2D.Size = UDim2.fromScale(Size.X, Size.Y)
	Frame2D.Position = UDim2.fromScale(Position.X, Position.Y)
	Frame2D.Rotation = Rotation
	Frame2D.Parent = GameScreen.Map
	NewObject.Object = Frame2D
	return setmetatable(NewObject, Object)
end

function Object:Destroy()
	self.Object:Destroy()
	self = nil
end

function Object:Print()
	print(self.Shape .. ":")
	print("Size: " .. tostring(self.Size) .. "; Position: " .. tostring(self.Position) .. "; Rotation: " .. tostring(self.Rotation))
	print("Color: " .. self.Color .. "; Anchored: " .. (if self.Anchored == true then "yes" else "no") .. "; Velocity: " .. tostring(self.Velocity))
end

Object.__newindex = function(table, key, value)
	print("Changing?")
end

function ObjectHolder.Hearbeat(delta, Objects)
	for i = 1, #Objects, 1 do
		local Object = Objects[i]
		if Object.Anchored == false then
			Object.Position = Object.Position + Object.Velocity * delta
			Object.Object.Position = UDim2.fromScale(Object.Position.X, Object.Position.Y)
		end
	end
	for a = 1, #Objects, 1 do
		local Object1 = Objects[a]
		if Object1.Anchored == false then
			for b = a + 1, #Objects, 1 do
				local Object2 = Objects[b]
				if Object2.Anchored == false then
					ObjectHolder.DetectCollisions(Object1, Object2)
					--Object.Object.Position = UDim2.fromScale(Object.Position.X, Object.Position.Y)
				end
			end
		end
	end
end

Heartbeat function calls every RenderStepped, and changes position of object if it’s Anchored property = false

So did a lil bit of digging and I realized that __newindex only fires when an index that is not present in the main table changes. Since, you set Position in NewObject table and assign the __newindex metamethod to that table, changes to Position won’t be fired as it is an exisiting key in the table. An ugly work-around is to set the Position property after setting the metatable.

local obj = setmetatable(NewObject, Object)
obj.Position = Position

return obj