Page 1 of 1

Bug with copying tables.

Posted: Tue Aug 18, 2020 4:27 am
by jotapapel
Hi!

So I'm developing a game and for it I've created a little class system for lua. But when I try to use it in the LOVE engine it does not seem to work.

Code: Select all

function deepcopy(orig)
  local orig_type = type(orig)
  local copy
  if orig_type == 'table' then
    copy = {}
    for orig_key, orig_value in next, orig, nil do
      copy[deepcopy(orig_key)] = deepcopy(orig_value)
    end
    setmetatable(copy, deepcopy(getmetatable(orig)))
	else -- number, string, boolean, etc
    copy = orig
  end
  return copy
end

local function class(prototype)
	local function metaclass(self, k, v) if ({constructor = true, id = true, super = true})[k] then rawset(getmetatable(self).__index, k, v) end end
	local function metaobject(self, k, v) if not(({class = true, id = true})[k]) then rawset(self, k, v) end end
	local function mt(table, type, timestamp) return {__timestamp = timestamp or 0, __index = table, __type = type, __tostring = function() return type .. tostring(table):sub(6) end, __newindex = function(self, k, v) pcall((type == 'class') and metaclass or metaobject, self, k, v) end} end
	local table = {
		prototype = prototype,
		constructor = function(self, ...) end,
		id = function(self, object) return tostring(object):sub(8) end,
		new = function(self, ...)
			local table = setmetatable({class = self}, {__index = self.prototype})
			table.id = self:id(table); self.constructor(table, ...)
			return setmetatable({}, mt(table, 'object', love.timer.getTime()))
		end,
		implements = function(self, interface, static)
			local interface = deepcopy(interface)
			local table = (static or false) and self or getmetatable(self).__index.prototype
			for k, v in pairs(interface) do rawset(table, k, v) end
			return self
		end,
		extend = function(self, prototype)
			local super = deepcopy(self)
			local index = getmetatable(super).__index
			local table = {super = self}
			for k, v in pairs(prototype) do rawset(index.prototype, k, v) end
			for k, v in pairs(index) do rawset(table, k, v) end
			for k, v in pairs(super) do rawset(table, k, v) end
			return setmetatable({}, mt(table, 'class'))
		end
	}
	return setmetatable({}, mt(table, 'class'))
end

local interface = {
	test = {
		slot = 0xc1,
		set = function(self, val) self[self.slot] = val end
	}
}

local original = class{}:implements(interface, true)
original.test:set(3)

local clone = original:extend{}
clone.test:set(8)

print(original.test, original.event[original.test.slot])
print(clone.test, clone.event[clone.test.slot])
The result of the print statements in other Lua compilers is the following:

Code: Select all

table: 0x1d2e820
table: 0x1d2d330
But in LOVE it outputs both tables as the same one, meaning that changing the value of one also changes the value of the other.

here's the example: https://repl.it/@JosMM2/simple-class#main.lua

help :(

Re: Bug with copying tables.

Posted: Tue Aug 18, 2020 11:45 am
by zorg
I tried it with the love2d version of repl.it, and (while i'm not sure which version they have, or what lua backend that uses), it behaved the same as how it did with the lua interpreter through your link... that said, while the löve version shouldn't matter, maybe luajit does something differently than vanilla lua that messes with your code, which i honestly couldn't read through.

A simpler deepcopy implementation should definitely work though, at least.

Re: Bug with copying tables.

Posted: Tue Aug 18, 2020 1:12 pm
by pgimeno
I get this when running the above snippet:

Code: Select all

lua: copytab.lua:61: attempt to index field 'event' (a nil value)
(I get the same in Lua, LuaJIT and Löve).

Re: Bug with copying tables.

Posted: Mon Aug 31, 2020 8:03 pm
by jotapapel
I've changed the implementation now with deepcopy and it works fine.

This is the version I'm currently using if anyone is interested.
class.lua
(3.33 KiB) Downloaded 285 times

Re: Bug with copying tables.

Posted: Tue Sep 01, 2020 6:37 am
by ivan
Yea, your code is hard to read so it may be good to look into the existing OO libraries out there.
Here is a simple one that I wrote myself - it's simple but very rarely would you need anything more sophisticated.
It's not good practice to use "table" as a variable or argument name:

Code: Select all

local table =

Code: Select all

local function encapsulate(table, prototype, ...)
Because "table" is already a well-known global variable.
In general, iterating all the key-value pairs of a table can be avoided if you use metatables efficiently.
I see a some unnecessary use of the "next" keyword too.
Also, note that your "deep-copy" clone function will crash with cycles where a table reference itself.