I’ve been learning Lua and Codea for the last couple of days and I’m absolutely loving it, I’m building a few classes and just wondering about how the inheritance, garbage collection and instantiation works.
I currently have two files just as a little toy I’m working on but the Points class seems to loose its property while accessing it in my Point class. So here are some stubs representing my classes in order of my tabs (I see that there was/is an inclusion issue?)
-- Points.lua
Points = class()
function Points:init()
self.registry = {}
end
function Points:Register(point)
self.registry.insert(self.registry, point)
end
-- Point.lua
Point = class(Points)
function Point:init()
Points.register(self)
end
But by the time my Point comes to register itself to the Points table Points complains that self is nil and when I work around that it says self.registry is nil too.
Since I’m creating an empty table in my init function and registering inside Point is the garbage collector collecting the empty table? Or is it just out of scope?
function setup()
points = Points()
point = Point(points)
end
-- Points.lua
Points = class()
function Points:init()
self.registry = {}
end
function Points:register(point)
table.insert(self.registry, point)
end
-- Point.lua
Point = class()
function Point:init(points)
points:register(self)
end
No, I’ll post all of my code to help, I’m going to open source it anyway…
#Point.lua
Point = class(Points)
-- If we pass all four parameters in it will
-- create a vec4, 3 a vec3 and the minumum
-- number of arguments is 2
function Point:init(x, y, z, w)
-- Always exit early
if x == nil or y == nil then
return error("Not enough arguments to Point")
end
-- Check the number of dimensions
if z == nil and w == nil then
self.point = vec2(x, y)
end
-- Check the number of dimensions
if w == nil and type(z) == "number" then
self.point = vec3(x, y, z)
end
-- Four dimensions, check you.
if type(z) == "number" and type(w) == "number" then
self.point = vec4(x, y, z, w)
end
-- If we made it this far, it's safe to create
-- a unique identifier for this point
self.id = math.random()
-- Register
Points.register(self)
return self
end
function Point:get()
-- Create local reference
local p = self.point
-- Return a pairs table
return {
x = p[1],
y = p[2],
z = p[3],
w = p[4]
}
end
function Point:x(x)
-- If we dont provide and argument return the coordinate
if x == nil then
return self:get().x
end
self.point[1] = self.point[1] + x
return self.point
end
function Point:y(y)
-- If we dont provide and argument return the coordinate
if y == nil then
return self:get().y
end
self.point[2] = self.point[2] + y
return self.point
end
function Point:z(z)
-- If we dont provide and argument return the coordinate
if z == nil then
return self:get().z
end
self.point[3] = self.point[3] + z
return self.point
end
function Point:w(w)
-- If we dont provide and argument return the coordinate
if w == nil then
return self:get().w
end
self.point[4] = self.point[4] + w
return self.point
end
#Points.lua
Points = class()
function Points:init()
self.reg = {}
return self
end
function Points:register(point)
print(self.reg) -- nil?
table.insert(self.reg, {
point = point
})
end
#main.lua
– dengine
-- Use this function to perform your initial setup
function setup()
local ps = Points()
local p = Point(0.6573829204, 1.574839)
end
-- This function gets called once every frame
function draw()
end
When you have Point:init() you need to have an extra parameter called “points”, and change Points:register(self) to points:register(self). And in main, when you call Point() add in the parameter ps. i.e.
-- Use this as Point:init()
function Point:init(points, x, y, z, w)
-- Always exit early
if x == nil or y == nil then
return error("Not enough arguments to Point")
end
-- Check the number of dimensions
if z == nil and w == nil then
self.point = vec2(x, y)
end
-- Check the number of dimensions
if w == nil and type(z) == "number" then
self.point = vec3(x, y, z)
end
-- Four dimensions, check you.
if type(z) == "number" and type(w) == "number" then
self.point = vec4(x, y, z, w)
end
-- If we made it this far, it's safe to create
-- a unique identifier for this point
self.id = math.random()
-- Register
points:register(self)
return self
end
-- And in main
function setup()
-- Try to not use local here, so you can access them later.
ps = Points()
p = Point(ps, 0.6573829204, 1.574839)
end
Okay, I’ll give that a go. I think this may also save some trouble later with referencing and indexing so thanks for inadvertently saving me a ball-ache later down the line!
Okay, that all appears to work but when I access the registry the point objects don’t have any methods? Should I add them using something like this to access them as field value methods instead of static methods? I.E
self.x = function Point:x(x) ....
I also have to ask if I do it that way, is there a performance hit at all?
[edit]
I’ve done this (and as somewhat of a prolific JavaScript developer I prefere having to instantiate and invoke in one scope) but still, these fields are hidden from me.
Does Lua not consider a value of [Function] in a field enumerable? Or is it not part of a pair/ipair?
Sorry for all the questions, hopefully someone else finds this thread useful.
[edit 2]
Ignore the above, it was my own stupid fault checking the wrong table for the functions lol
When you do inheritance such as Point = class(Points) then every object of class Point automatically inherits all the stuff from Points. But all of that stuff is unique to that object, so in the original code every Point object got its own registry to register itself in. SkyTheCoder’s code sorts that out by separating the Point and Points classes. By passing the object of the Points class to each object of the Point class you can be sure that they are all registering themselves with the samePoints object.
Calling Points.register(self) is completely valid code, but unlikely to do what you expect. The way that lua’s object stuff works is that obj:method(args) gets mapped to obj.method(obj,args) (and in the declaration, function Class:method(args) is equivalent to function Class.method(self,args) - see next remark for an exploit of this) which, unless you’ve done something strange, is the same as Class.method(obj,args). So calling Points.register(self) effectively says “Call the register method from the class Points on the instance self but with no arguments.” If we assume that self has inherited the register method from the Points class then the correct way to call the register method and pass it the current object to register would be self:register(self) and this would be equivalent to Points.register(self,self).
(Of course, from my first remark, this probably wouldn’t accomplish the desired outcome, but there are circumstances in which this might be useful.)
Following on from that, if you want to dynamically create a new method then the way to do it is:
self.newmethod = function (self,args)
-- do something with self and args here
end
~~~
(Note that the `self` inside the function and the `self` outside are not the same here.) This can be called with `obj:newmethod(args)` just as a method defined in the usual way since `obj:newmethod(args)` becomes `obj.newmethod(obj,args)`. (This is the "something strange" that I referred to above since by this way you can override a particular object's methods. When an object is created it is given a "copy" (aka a reference to) each of the class methods, but those copies can be overwritten later on.)