__newindex metamethod not getting called/invoked

My objective is to make a note class for a rhythm game with all information I need to register note timings and other important variables using metatables instead of using regular roblox Instances with attributes.

The note class has a Position key on the metatable which is a UDim2 that just determines where the actual relative position of the ImageLabel that visually represents the note to the user (there’s obviously more properties to the metatable but I’m just using the Position key to not over complicate). I want to make the game run as smoothly as possible so instead of updating the ImageLabel’s position every frame to the Position key on the metatable (which is just a waste of resources if the position in the current frame is the exact same as the position in the last frame) I’m taking advantage of the metatable’s metamethods and using the __newindex metamethod to bind to a key change and updating the ImageLabel’s position on the spot, but for some reason the metamethod just never fires for the Position property.


Note ModuleScript (--[[...]]-- Indicates that a section of the script was trimmed out):

local Note = {}
Note.__index = Note
Note.__newindex = function(self, key, value)
	rawset(self,key,value)
    --[[...]]--
	if key == "Position" or key == "PositionOffsets" then --:find("Position")
		print("POSS")
		self._Position = self.Position
		for k,v in pairs(self.PositionOffsets) do self._Position += v end
		self._Frame.Position = UDim2.new(self._Position.X.Scale,self._Position.X.Offset,self._Position.Y.Scale,self._Position.Y.Offset)
	end
    --[[...]]--
end

--[[...]]--

function Note.new(...)
	local self = setmetatable({
		--[[...]]--
		
		Position = UDim2.fromScale(-1,-1);	-- MIDDLE POSITION!
		PositionOffsets = {};
		Parent = nil;
		
		--[[...]]--

		_Frame = nil;
		SpriteLabel = nil;
	}, Note)

    --[[...]]--

	return self
end

--[[...]]--

return Note

On my GamePlay LocalScript I have a function bind to the RenderStepped event that updates the note's positions:
game:GetService("RunService").RenderStepped:Connect(function(delta)
    --[[...]]
    --
    local songPos = shared.Conductor.songPosition * 1000
    local scrollSpeed = shared.ScrollSpeed
    local mania = shared.CurMatchInfo.Mania
    for idx, note in pairs(shared.Notes) do
        if note ~= nil and note._Alive then
            local strum = shared.StrumLines[note.NoteData + 1 + note.Player * mania]
            local strumX = strum._Position.X.Scale;
            local strumY = strum._Position.Y.Scale;
            local strumDirection = strum.Direction;

            --[[...]]--

            local distance = (0.45 * ((songPos - note.StrumTime) / 1000) * scrollSpeed * 1);

            --[[...]]--

            local strumNoteXTimeline = 0
            local strumNoteYTimeline = 0
            local strumNoteSpeedTimeline = 1

            --[[...]]--

            pcall(function() strumNoteXTimeline = strum.NoteXTimeline:CalculateValue(distance) / 100; end)
            pcall(function() strumNoteYTimeline = strum.NoteYTimeline:CalculateValue(distance) / 100; end)
            pcall(function() strumNoteSpeedTimeline = strum.NoteSpeedTimeline:CalculateValue(distance); end)

            --[[...]]--

            local directionCompensation = distance
            if strum.Downscroll then directionCompensation *= -1 end
            local angleDir = strumDirection * math.pi / -180;

            if note.CopyX then note.Position = UDim2.new(strumX + strumNoteXTimeline + math.cos(angleDir) * (directionCompensation * strumNoteSpeedTimeline), note.Position.X.Offset, note.Position.Y.Scale, note.Position.Y.Offset) end
            if note.CopyY then note.Position = UDim2.new(note.Position.X.Scale, note.Position.X.Offset, strumY + strumNoteYTimeline + math.sin(angleDir) * (directionCompensation * strumNoteSpeedTimeline), note.Position.Y.Offset) end

            --[[...]]--
        end
    end
end)




Post writing: after reviewing the code one more time, I think that the __newindex metamethod is not getting called because although I’m changing the value of that key to a different UDim2, I am not changing the UserData for said key, so even though the value is different it still counts as no change. If this is the case then how could I fix this?

Huge thanks viewing my post, I’m looking forward to a solution from anyone, and sorry for the length of this, I just want to leave everything as clear as possible.

Perhaps creating a “Setter” function instead of relying on __newindex would work? Rather than setting the note position directly, each object in your Note class would have a :SetPosition(newPosition) function and you would use that instead.

Not sure if this is what you’d want but it would be much simpler, and that way you would have complete control over when the position is changed.

Something like this:

function Note:SetPosition(newPosition)
    self._Position = newPosition
    for k, v in pairs(self.PositionOffsets) do self._Position += v end
    self._Frame.Position = UDim2.new(self._Position.X.Scale, self._Position.X.Offset, self._Position.Y.Scale, self._Position.Y.Offset)
end
1 Like

I mean, that would work but it’s not quite what I’m looking for

After some testing and sweaping through both roblox’s (luau) and lua’s api documentation about metamethods I found a method called __usedindex which fires when the key on a metatable is changed (meaning that table[key] ~= nil), but for some reason this metamethod doesn’t exist in luau, making what I’m trying to achieve impossible. I’ll keep this post open/unsolved in case someone finds a viable solution.

This thread might help you out:

im pretty sure the __newindex metamethod only runs when a index is indexed to a value that was previously nil

1 Like