This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
MKlegoman357's profile picture

Lua tables: bad code or Lua/LuaJ bug?

Started by MKlegoman357, 13 July 2013 - 04:58 PM
MKlegoman357 #1
Posted 13 July 2013 - 06:58 PM
Hello pros,

I was creating my program, making clickable buttons. They worked perfectly as expected. It was done by creating a table called "button" and implementing in it some functions to handle buttons. Buttons are created and saved in the same table ("button") called "buttons":

local button = {
buttons = {},

add = function (self, and other variables)
  --blah, blah
end,

remove = function (same stuff)
  --another function
end,

--More functions...
}
Now, I didn't want all of my buttons to be in button.buttons table, so I wrote this:

local butt1 = setmetatable({}, {__index = button})
local butt2 = setmetatable({}, {__index = button})

butt1:add("blah, blah", arguments...)
butt1:draw()

This worked as expected, so I wanted to check what will the code do if there is no buttons in the "buttons" table, so I tried to draw buttons from the "butt2" table:

butt2:draw()
And it did draw a button, the one that I created in the "butt1" table. So I was searching how to fix it and I found it. I deleted the "buttons" table from "button" table:

local button = {
--removed the "buttons = {}," line

add = function (self, and other variables)
  --blah, blah
end,

remove = function (same stuff)
  --another function
end,

--More functions...
}
and defined that table in the setmetatable method:

local butt1 = setmetatable({buttons = {}}, {__index = button})
local butt2 = setmetatable({buttons = {}}, {__index = button})
and then "butt2:draw()" didn't draw any buttons (as it should be).

So, my question is that:
Why is it behaving like that? Maybe "setmetatable()" method is making table "buttons" public to every other instance (wrong word, don't remember other words) of "button" table?

Here are the codes (just run it in CC, they don't error):
Working:
Spoiler

local version = "0.1a"

local button = {
add = function (self, id, x, y, text, w, h, tColor, bColor, offsetX, offsetY, enabledButtons)
  table.insert(self.buttons, {id = id, x = x, y = y, w = w or text and #text or 1, h = h or 1, text = text, tc = tColor or colors.white, bc = bColor or colors.black, ox = offsetX or 0, oy = offsetY or 0, b = enabledButtons or {true, true, true}})

  return true
end,

remove = function (self, id)
  for i, b in pairs(self.buttons) do
    if b.id == id then
      table.remove(self.buttons, i)
      return true
    end
  end

  return false
end,

click = function (self, but, x, y)
  for i, b in pairs(self.buttons) do
    if b.x <= x and b.x + b.w - 1 >= x then
      if b.y <= y and b.y + b.h - 1 >= y then
        if b.b[but] then
          return b.id
        end
      end
    end
  end
end,

draw = function (self)
  for i, b in pairs(self.buttons) do
    term.setBackgroundColor(b.bc)
    term.setTextColor(b.tc)

    for w = 0, b.w - 1 do
      for h = 0, b.h - 1 do
        term.setCursorPos(b.x + w, b.y + h)
        term.write(" ")
      end
    end

    term.setCursorPos(b.x + b.ox, b.y + b.oy)
    term.write(b.text:sub(1, b.w - b.ox))
  end
end,

exists = function (self, id)
  for i, b in pairs(self.buttons) do
    if b.id == id then
      return true
    end
  end

  return false
end,

list = function (self)
  local butt = {}

  for i, but in pairs(self.buttons) do
    table.insert(butt, but.id)
  end

  return unpack(butt)
end
}

local function newButtons ()
  return setmetatable({buttons = {}}, {__index = button})
end

local butt1 = newButtons()
local butt2 = newButtons()

butt1:add("new", 1, 1, "New Button", 10, nil, nil, nil, 2)
butt1:draw()
print()
sleep(1)
print("list of butt2: ", butt2:list())
print("list of butt1: ", butt1:list())
print("list of butt2: ", butt2:list())
print("Press any key to continue")
os.pullEvent()
term.clear()
butt2:draw()
print()
print("list of butt2: ", butt2:list())
sleep(1)
print("\"new\" exists in butt2: ", butt2:exists("new"))
print("butt1 == butt2: ", butt1 == butt2)

Not Working:
Spoiler

local version = "0.1a"

local button = {
buttons = {},

add = function (self, id, x, y, text, w, h, tColor, bColor, offsetX, offsetY, enabledButtons)
  table.insert(self.buttons, {id = id, x = x, y = y, w = w or text and #text or 1, h = h or 1, text = text, tc = tColor or colors.white, bc = bColor or colors.black, ox = offsetX or 0, oy = offsetY or 0, b = enabledButtons or {true, true, true}})

  return true
end,

remove = function (self, id)
  for i, b in pairs(self.buttons) do
    if b.id == id then
      table.remove(self.buttons, i)
      return true
    end
  end

  return false
end,

click = function (self, but, x, y)
  for i, b in pairs(self.buttons) do
    if b.x <= x and b.x + b.w - 1 >= x then
      if b.y <= y and b.y + b.h - 1 >= y then
        if b.b[but] then
          return b.id
        end
      end
    end
  end
end,

draw = function (self)
  for i, b in pairs(self.buttons) do
    term.setBackgroundColor(b.bc)
    term.setTextColor(b.tc)

    for w = 0, b.w - 1 do
      for h = 0, b.h - 1 do
        term.setCursorPos(b.x + w, b.y + h)
        term.write(" ")
      end
    end

    term.setCursorPos(b.x + b.ox, b.y + b.oy)
    term.write(b.text:sub(1, b.w - b.ox))
  end
end,

exists = function (self, id)
  for i, b in pairs(self.buttons) do
    if b.id == id then
      return true
    end
  end

  return false
end,

list = function (self)
  local butt = {}

  for i, but in pairs(self.buttons) do
    table.insert(butt, but.id)
  end

  return unpack(butt)
end
}

local function newButtons ()
  return setmetatable({}, {__index = button})
end

local butt1 = newButtons()
local butt2 = newButtons()

butt1:add("new", 1, 1, "New Button", 10, nil, nil, nil, 2)
butt1:draw()
print()
sleep(1)
print("list of butt2: ", butt2:list())
print("list of butt1: ", butt1:list())
print("list of butt2: ", butt2:list())
print("Press any key to continue")
os.pullEvent()
term.clear()
butt2:draw()
print()
print("list of butt2: ", butt2:list())
sleep(1)
print("\"new\" exists in butt2: ", butt2:exists("new"))
print("butt1 == butt2: ", butt1 == butt2)
MysticT #2
Posted 13 July 2013 - 07:31 PM
That's how metatables work. The __index metamethod will search the referenced table for the given key if it doesn't exist in the table.
Example:

local someTable = {
key1 = "Hello World!",
key2 = 2
}

local tbl = {
key2 = 10,
key3 = "Some Random String"
}
setmetatable(tbl, { __index = someTable })

print(tbl.key1) -- this will print "Hello World!", from someTable, because the key is not in tbl
print(tbl.key2) -- this will print 10, as the key is present in tbl
print(tbl.key3) -- this will print "Some Random String"

So, when doing oop in lua, you need to define the functions and common variables in the metatable, and "instance" variables in the instance table. Something like this:

local Class = {}

function Class:doSomething()
  print("Test")
end

function Class:test()
  print(self.value)
end

local obj = setmetatable({}, { __index = Class })
obj.value = 10 -- set value in the instance object
obj:doSomething()
obj:test() -- this should print 10, the value we just assigned to obj.value

Hope you understand all that. If you don't, or want to know something else, just ask :)/>
MKlegoman357 #3
Posted 13 July 2013 - 07:42 PM
SpoilerThat's how metatables work. The __index metamethod will search the referenced table for the given key if it doesn't exist in the table.
Example:

local someTable = {
key1 = "Hello World!",
key2 = 2
}

local tbl = {
key2 = 10,
key3 = "Some Random String"
}
setmetatable(tbl, { __index = someTable })

print(tbl.key1) -- this will print "Hello World!", from someTable, because the key is not in tbl
print(tbl.key2) -- this will print 10, as the key is present in tbl
print(tbl.key3) -- this will print "Some Random String"

So, when doing oop in lua, you need to define the functions and common variables in the metatable, and "instance" variables in the instance table. Something like this:

local Class = {}

function Class:doSomething()
  print("Test")
end

function Class:test()
  print(self.value)
end

local obj = setmetatable({}, { __index = Class })
obj.value = 10 -- set value in the instance object
obj:doSomething()
obj:test() -- this should print 10, the value we just assigned to obj.value

Hope you understand all that. If you don't, or want to know something else, just ask :)/>/>/>

Thanks for the help! This really helped me.
MKlegoman357 #4
Posted 13 July 2013 - 07:53 PM
Well, this code is working properly (note: it is working the same way that my Non-Working code works):

local but = {
n = 1,

a = function (self)
  self.n = 2
end,

b = function (self)
  self.n = 1
end
}

local but1 = setmetatable({}, {__index = but})
local but2 = setmetatable({}, {__index = but})

print(but1.n)
print(but2.n)

but1:a()

print(but1.n)
print(but2.n)

Maybe you can explain why is it not happening the same as it was with the buttons?

EDIT: This code is working fine, that's why I though it could be Lua/LuaJ bug.
MysticT #5
Posted 13 July 2013 - 10:27 PM
Well, it works because the first "n" value referenced is the one on the shared table. But, when you assign a value to self.n, self is referencing the "btn1" table, so it sets "n" to 2 in that one, not the shared one. See the comments:

local but = {
n = 1, -- shared n, initialized to 1

a = function (self)
  self.n = 2 -- self is the passed argument, not the "but" table
end,

b = function (self)
  self.n = 1
end
}

local but1 = setmetatable({}, {__index = but})
local but2 = setmetatable({}, {__index = but})

print(but1.n) -- both this "n" references are to the shared value
print(but2.n)

but1:a() -- this would call but.a(but1)

print(but1.n) -- but1.n was set before, so it takes that value
print(but2.n) -- but2.n is not defined, so it uses but.n (the shared value)
MKlegoman357 #6
Posted 14 July 2013 - 02:27 AM
Well, it works because the first "n" value referenced is the one on the shared table. But, when you assign a value to self.n, self is referencing the "btn1" table, so it sets "n" to 2 in that one, not the shared one. See the comments:

local but = {
n = 1, -- shared n, initialized to 1

a = function (self)
  self.n = 2 -- self is the passed argument, not the "but" table
end,

b = function (self)
  self.n = 1
end
}

local but1 = setmetatable({}, {__index = but})
local but2 = setmetatable({}, {__index = but})

print(but1.n) -- both this "n" references are to the shared value
print(but2.n)

but1:a() -- this would call but.a(but1)

print(but1.n) -- but1.n was set before, so it takes that value
print(but2.n) -- but2.n is not defined, so it uses but.n (the shared value)

With few more tests and your good explanation I found what was wrong. My problem was that I tried to put buttons into a not existing table, so lua was just using the "shared" table of buttons. Same thing when getting values. Thank you very much MysticT!