Class inheritance

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?

You have a typo. You define a function Points:Register and than call Points:register

Lua is case sensitive

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

Sorry, it’s not a typo in my code I must have missed an auto capitalisation by the iPad. Will try your idea SkyTheCoder and report back :slight_smile:

Okay. By the way, use three tildes (3 * ~) to have the forums recognize the text as code. Three to start, three to stop.

Okay, using

table.insert

Fixed that issue but when I pass the Point “self” reference through it comes through as null?

Yeah sorry was trying to use the standard 4 spaces for code but it was all lost during a copy and paste accident lol

So it worked?

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

Sorry about all the code lol

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 :slight_smile:

self.x = function(x)
    <your code here>
end

A few general remarks:

  1. 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 same Points object.

  2. 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.)

  3. 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.)