I created a script to generate a maze and it works as expected but during coding, I discovered I should create object instances differently, so I refactored this:
local Cell = {
index = nil,
coordinates = Vector2.zero,
wasVisited = false,
markAsVisited = function (self)
self.wasVisited = true
end,
new = function(self, index: number, coordinates: Vector2)
local cell = table.clone(self)
cell.index = index
cell.coordinates = coordinates
return cell
end,
}
return Cell
to
local Cell = {
index = nil,
coordinates = Vector2.zero,
wasVisited = false
}
function Cell:new(index: number, coordinates: Vector2)
local instance = setmetatable({}, self)
self.__index = self
self.index = index
self.coordinates = coordinates
return instance
end
function Cell:markAsVisited()
self.wasVisited = true
end
return Cell
and instead of
I get
can someone explain to me why?
Lua doesn't have classes, where you can use a constructor to create a new instance of a type. But, being a flexible language, we can achieve something like classes through clever use of metatables. So let's talk for a moment about how the __index
metamethod works.
When you ask a table for a key that doesn't exist, it will return nil
. But, if you set the __index
property to another table, the lookup will fall-through to that table.
local a = {
foo = "hello"
}
local b = {
bar = "world"
}
-- ask b for "foo" which isn't defined on 'b'
print(b.foo, b.bar) -- returns nil, "world"
-- set the lookup table to 'a'
setmetatable(b, { __index = a })
-- ask again for b, which still isn't defined on 'b'
print(b.foo, b.bar) -- returns "hello", "world"
In this example, b
did not become a clone of a
with all of its properties, it is still a table with only bar
defined. If you were to ask for foo
, since it does not find it in b
it then checks a
to see if it can find it there. So b
is not a
, but it can act like it.
Let's look at what your code is doing :
-- create a table with some fields already defined
local Cell = {
index = nil,
coordinates = Vector2.zero,
wasVisited = false
}
-- create a "new" function that will pass the in original Cell object as the `self` variable
function Cell:new(index: number, coordinates: Vector2)
-- create an empty table that will reference the original Cell table
-- note - the first time this is called, the __index metamethod hasn't been set yet
local instance = setmetatable({}, self)
-- set Cell's __index metamethod to itself
self.__index = self
-- overwrite the original Cell's index to the passed in index
self.index = index
-- overwrite the original Cell's coordinates to the passed in coordinates
self.coordinates = coordinates
-- return an empty table that will reference all of these new values
return instance
end
As you can see, your code is struggling to define copies of Cells because your constructor is constantly overwriting the values in the original Cell table, not assigning them to the newly created instance. Each new instance is able to access these fields and not fail, but they are always referencing the last values set.
So, to fix it, here's what I would do :
Cell:new
and change them all to Cell.new
-- create a new table named Cell
local Cell = {}
-- set Cell's __index to itself to allow for future copies to access its functions
Cell.__index = Cell
-- set the "new" key in Cell to a function
function Cell.new(index: number, coordinates: Vector2)
-- create a new table and set the lookup table to be the Cell table
-- since no functions are defined on this new table, they will fall through to access Cell's functions
local instance = setmetatable({
-- set some properties on the new instance table
index = index,
coordinates = coordinates,
wasVisited = false,
}, Cell)
-- return the new table
return instance
end
-- set the "markAsVisited" key in Cell to a function
function Cell:markAsVisited()
-- here "self" is referring to the new instance table, not some field on Cell
self.wasVisited = true
end
return Cell