Page 2 of 2

Re: Classy - Middleclass Inspired, But a lot faster

Posted: Thu Mar 09, 2017 5:53 pm
by Forgemistress
Update has been made.

Re: Classy - Middleclass Inspired, But a lot faster

Posted: Sun Mar 12, 2017 4:06 pm
by DanielPower
I'm porting my engine to use Classy rather than Middleclass in a separate branch so I can test the difference. But I'm running into an issue.

Middleclass syntax:

Code: Select all

local Person = class('Person')
Person.species = 'Human'
Person.numArms = 2
Person.numFingers = 10
Person.defaultPicture = love.graphics.newImage('photo.jpg')
Classy syntax:

Code: Select all

local Person = class('Person', {
    species = 'Human',
    numArms = 2,
    numFingers = 10,
    defaultPicture = love.graphics.newImage('photo.jpg'), -- Causes error
}
First of all, I perfer the Middleclass syntax for initializing a class, however I can get over this for the performance boost. The issue I'm running into is that while middleclass will handle an Image or a Quad as a class variable just fine, Classy returns this error:

Code: Select all

Error: libraries/classy/footable.lua:111: [string "return {restQuad=Quad: 0x025dd3e0}"]:1: '<name>' expected near '0x025dd3e0'
stack traceback:
        [C]: in function 'assert'
        libraries/classy/footable.lua:111: in function '_makeAllocatorFunction'
        libraries/classy/classy.lua:213: in function 'class'
        class/unit/unit.lua:1: in main chunk
        [C]: in function 'require'
        main.lua:29: in main chunk
        [C]: in function 'require'
        [string "boot.lua"]:429: in function <[string "boot.lua"]:275>
        [C]: in function 'xpcall'
Am I doing something wrong, or is this an issue with Classy?

Re: Classy - Middleclass Inspired, But a lot faster

Posted: Wed Mar 29, 2017 4:06 pm
by Forgemistress
The template that you provide to the class only defines the sets of keys that the underlying table will have as well as values that are automatically converted into strings. In this instance, defaultPicture is not the value you expect it to be since it is a love2d class object. You will want to instead assign this value via the love module functions in the classes __init__ function (its constructor).

This is something that I covered in my opening post. The template values are simply that, values. References are not supported.

You want something like this.

Code: Select all

-- Class definition ommited.
function Person:__init__()
    self.defaultPicture = love.graphics.newImage ('person.jpg')
end

-- Alternatively if you want to save memory for something like this.
Person.static.defaultPicture = love.graphics.newImage('person.jpg')

-- And then...
function Person:__init__(image)
    if image then
        self.image = love.graphics.newImage (image)
    else
        self.image = Person.defaultPicture
    end
end 
Under the hood, the module footable takes the template you provide, converts it into a string, then wraps it into a special function that it then compiles using dostring and stores in the class definition (I believe it stores it under <classname>.allocator, I'll need to double check). However, this is only done in <classname>:finalize, hence why that function is basically the linchpin of the entire implementation and is the reason for the performance I was able to squeeze out of it.

In cases of inheritance, the tables from the superclass is allocated and then merged with the definition of the subclass (with any collisions throwing out an error). Then another new allocator function is created using that merged table. When a class has its *:new function called, it invokes this allocator function that was compiled by footable and then does a whole bunch of metatable shenanigans before it returns to you the class instance that you then use.

The nice thing about this is that it also allows you to treat class instances as if they are normal tables. If memory serves, you should be able to do a 'pairs' Iteration on a class instance to see the keys and values that are stored as well as pass it into a library like binser to serialize it without any special modification (though my own personal copy includes templating and a serialization/deserialization utility that wraps binser, json, and lume into encoding functions because I'm secretly a masochist). The functions of the class are omitted from the pairs Iteration because they are part of the instance's __class__ key, though you could iterate those as well using instance.__class__.methods (which is subject to change). The static value I showed in my example would likewise be stored under instance.__class__.static. __class__ itself is part of the instances metatable, which the instance is set to __index into per standard lua metatable conventions.