Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Class inheritance

edited June 2013 in Questions Posts: 20

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?

Comments

  • IgnatzIgnatz Mod
    Posts: 5,396

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

    Lua is case sensitive

  • edited June 2013 Posts: 1,976
    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 :)

  • edited June 2013 Posts: 1,976

    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

  • Posts: 1,976

    So it worked?

  • edited June 2013 Posts: 20

    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

  • Posts: 1,976

    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!

  • edited June 2013 Posts: 20

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

  • Posts: 1,976
    self.x = function(x)
        <your code here>
    end
    
  • Posts: 2,161

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

Sign In or Register to comment.