Implementation of objects in lua not working as expected.

Hello again, I know i’m asking a lot of questions. I’ll lurk around here and try to restore the balance :v:

I’ve got a class implemented in lua, according to the tutorial here, shown below:

[lua]
HSetButton =
{
dims =
{ x = 96,
y = 15
},

pos =
{	x	=	0,
	y	=	0
},

colour =
{	back	=	Color(178,178,178,255),
	border	=	Color(128,128,128,255),
	font1	=	Color(0,0,0,255)
},


label = "def but"

}

function HSetButton:new(but)
but = but or {}
setmetatable(but, HSetButton)
self.__index = HSetButton
return but
end

function HSetButton.setPos(self, x, y)
self.pos.x = x
self.pos.y = y
end

function HSetButton.setLabel(self, l)
self.label = l
end[/lua]

I then create instances and attempt to call setLabel and setPos on these instances, as below:

[lua]
HSet =
{ basePos =
{ x = surface.ScreenWidth(),
y = surface.ScreenHeight()/2
}
}

HSet.buttons =
{ speed = HSetButton:new(),
dist = HSetButton:new(),
time = HSetButton:new(),
ballz = HSetButton:new()
}
HSet.buttons.speed:setLabel(“Speed”)
HSet.buttons.dist:setLabel(“Distance”)
HSet.buttons.time:setLabel(“Time”)

local i = 1
for k,v in pairs(HSet.buttons) do
x = HSet.basePos.x
y = HSet.basePos.y + i*HSetButton.dims.y
v:setPos(x, y)

	i = i+1

end[/lua]

I expected this to work. When I ran this code it modified the instance labels correctly (setLabel), but modified the pos table in the class template (HSetButton), instead of the appropriate instances. As the setLabel and setPos functions are implemented similarly I can’t figure out the problem.

After running this code, the following:
[lua]Msg("CLASS: x = “…HSetHeader.pos.x…”, y = “…HSetHeader.pos.y…”
")[/lua]
produces this output:


CLASS: x = 1440, y = 450

instead of the expected defaults (0, 0).

Help please?

aren’t you supposed to do HSetButton.__index = HSetButton instead of doing it on the object after you’ve set the metatable?

[editline]8th January 2011[/editline]

because doing it after you’ve set the metatable would make the object look up in the class table

It’s around the same as the tutorial does it, apparently it allows for class inheritance.

I’ve changed new() to be identical to the tutorial, same results;
[lua]function HSetButton:new(but)
but = {}
setmetatable(but, self)
self.__index = self
return but
end[/lua]

I’ll do it your way and post back with results :smile:

[editline]8th January 2011[/editline]

[lua]HSetButton.__index = HSetButton

function HSetButton:new(but)
but = but or {}
setmetatable(but, HSetButton)
return but
end[/lua]

produces the same result. This is what you meant?

This behaviour is actually to be expected.

Normally, if you want to make an OOP style system in Lua you would put all of the instance variables that you want each instance of a class to contain in the metatable and set the __index metamethod of the metatable to itself. This works if you want the instance to have a copy of each variable because when you index the instance and the key doesn’t exist on the table, it checks for that key in the metatable and when you set the instance variable it will no longer need to look in the metatable because it now exists on the instance table.

This works for normal variables (anything but tables), but for tables this will produce undesired results (like what you are having problems with). As you can see in your code, when you set the x and y position of the pos table:

[lua]function HSetButton.setPos(self, x, y)
self.pos.x = x
self.pos.y = y
end[/lua]

the pos table doesn’t actually exist on the instance table, it is accessed from the metatable. This means that it is the x and y of the pos table on the metatable that is changed and a pos table isn’t actually created for the instance table like what would happen for non-table values.

The best solution for this is to define any tables in the creation of the instances, not the creation of the metatable:

[lua]local HSetButton = { label = “def but”}
HSetButton.__index = HSetButton

function HSetButton:new()
local but = {}
but.dims = {x = 96, y = 15}
but.pos = {x = 0, y = 0}

setmetatable(but, HSetButton)
return but

end

function HSetButton.setPos(self, x, y)
self.pos.x = x
self.pos.y = y
end

function HSetButton.setLabel(self, l)
self.label = l
end[/lua]

This will produce the results you are after.

[editline]8th January 2011[/editline]

I’m sorry if that is hard to understand.

You got it, MakeR. Thanks for the solution and the explanation :smile:
I’m still trying to understand why the absence of a table is different to the absence of a variable. I’m sorry that I need it to be spelled out.

The absence of a table isn’t different to the absence of a non-table value. It is the fact that when you call the setPos function, you don’t set the table, you set the members of the table. This means that the instance never gets it’s own pos table like it would get it’s own label variable when you call the setLabel function, instead, it is always using the pos table on HSetButton.

Ah, I think I get it. I had always thought that the creation of the table was implicit if you defined a member. Thanks for clearing this up!