Another Lua Vector library

Showcase your libraries, tools and other projects that help your fellow love users.
damv
Prole
Posts: 18
Joined: Thu Oct 11, 2018 9:09 pm

Another Lua Vector library

Post by damv » Mon Jul 29, 2019 5:08 pm

Hello, I share my library on 2d vectors, it has nothing very innovative but I hope you like it, I made it for my personal use but I share it, any advice, doubt, or criticism is accepted.

Doc: https://github.com/DeybisMelendez/lua-v ... /README.md
Source: https://github.com/DeybisMelendez/lua-vector

Release v0.6.0: https://github.com/DeybisMelendez/lua-v ... tag/v0.6.0

User avatar
ivan
Party member
Posts: 1531
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Another Lua Vector library

Post by ivan » Mon Jul 29, 2019 6:14 pm

Looks fine, I am not 100% convinced by your object-orientation code.
Some functionality is based on closures:

Code: Select all

        local vec = {x = x or 0, y = y or 0}
        function vec:string()
            return "vector(" .. self.x .. ", " .. self.y ..")"
        end
...
and some functionality is implemented using metatables:

Code: Select all

local vecMt = {
    __tostring = function(self)
        return self:string()
    end,
I don't see any benefit of using closures when your vectors already use metatables.
I mean yes, it works, but it's just not very efficient.
Other than that the code looks fine.
Cheers.

damv
Prole
Posts: 18
Joined: Thu Oct 11, 2018 9:09 pm

Re: Another Lua Vector library

Post by damv » Mon Jul 29, 2019 6:27 pm

ivan wrote:
Mon Jul 29, 2019 6:14 pm
Looks fine, I am not 100% convinced by your object-orientation code.
Some functionality is based on closures:

Code: Select all

        local vec = {x = x or 0, y = y or 0}
        function vec:string()
            return "vector(" .. self.x .. ", " .. self.y ..")"
        end
...
and some functionality is implemented using metatables:

Code: Select all

local vecMt = {
    __tostring = function(self)
        return self:string()
    end,
I don't see any benefit of using closures when your vectors already use metatables.
I mean yes, it works, but it's just not very efficient.
Other than that the code looks fine.
Cheers.
Thank you very much, I am not very experienced in this, I will review it and try to improve it.

Nelvin
Citizen
Posts: 94
Joined: Mon Sep 12, 2016 7:52 am
Location: Germany

Re: Another Lua Vector library

Post by Nelvin » Mon Jul 29, 2019 10:08 pm

I'd like to repeat what ivan said as it's not only not very efficient, it also wastes a lot of memory.

The problem with the code, as it is, is not primarily the closure but whenever you create a vector instance (which, at the moment, you do in almost all core operations) you create a new table and then add all the helper methods as key/value pairs to this very table. I.e. each of your vectors not only contains the x/y but also "dot", "normalized" etc. etc. as hashed keys with references to those functions.

This is very slow as it has to put those keys into the hashtable each time and of course it does cost a lot of memory. I did a quick test and it roughly requires 10x the memory for each created vector compared to a vector with only x/y.

The simplest optimization is to add all those vec:dot, vec:normalized etc. methods into the vecMt and then add the __index fallback to itself, so you can call those in the same convenient way as you can at the moment.

Like so

Code: Select all


local vector = {_VERSION = "v0.6.0", _TYPE = "module", _NAME = "vector"}
local vecMt = {
    __tostring = function(self)
        return self:string()
    end,
    __add = function(a, b)
        if type(a) == "number" then return vector(a + b.x, a + b.y) end
        if type(b) == "number" then return vector(a.x + b, a.y + b) end
        return vector(a.x + b.x, a.y + b.y)
    end,
    __sub = function(a, b)
        if type(a) == "number" then return vector(a - b.x, a - b.y) end
        if type(b) == "number" then return vector(a.x - b, a.y - b) end
        return vector(a.x - b.x, a.y - b.y)
    end,
    __mul = function(a, b)
        if type(a) == "number" then return vector(a * b.x, a * b.y) end
        if type(b) == "number" then return vector(a.x * b, a.y * b) end
        return vector(a.x * b.x, a.y * b.y)
    end,
    __div = function(a, b)
        if type(a) == "number" then return vector(a / b.x, a / b.y) end
        if type(b) == "number" then return vector(a.x / b, a.y / b) end
        return vector(a.x / b.x, a.y / b.y)
    end,
    __unm = function(t)
        return vector(-t.x, -t.y)
    end,
    __eq = function(a, b)
        return a.x == b.x and a.y == b.y
    end,
    __pow = function(vec, value)
        return vector(vec.x ^ value, vec.y ^ value)
    end,
    __concat = function(a, b)
        if type(a) == "string" then return a .. b:string() end
        if type(b) == "string" then return a:string() .. b end
        return a:string() .. b:string()
    end,

    string = function(self)
        return "vector(" .. self.x .. ", " .. self.y ..")"
    end,
    angle = function(self) -- Radians
        return math.atan2(self.y, self.x)
    end,
    normalized = function(self)
        local m = (self.x^2 + self.y^2)^0.5 --magnitude
        if self.x/m ~= self.x/m then self.x = 0 else self.x = self.x/m end
    if self.y/m ~= self.y/m then self.y = 0 else self.y = self.y/m end
    end,
    distanceSquaredTo = function(self, v)
        local x1, y1 = self.x, self.y
        local x2, y2 = v.x, v.y
        return (x2 - x1)^2 + (y2 - y1)^2
    end,
    distanceTo = function(self, v)
        return self:distanceSquaredTo(v)^0.5
    end,
    distanceSquared = function(self )
        return self.x^2 + self.y^2
    end,
    distance = function(self)
        return (self:distanceSquared())^0.5
    end,
    dot = function(self, v)
        return self.x * v.x + self.y * v.y
    end,
    perpDot = function(self, v)
        return self.x * v.x - self.y * v.y
    end,
    abs = function(self)
        self.x, self.y = math.abs(self.x), math.abs(self.y)
    end,
    round = function(self, dec)
        dec = dec or 0
        local mult = 10^(dec)
        local nx, ny
        if self.x >= 0 then nx = math.floor(self.x * mult + 0.5) / mult
        else nx = math.ceil(self.x * mult - 0.5) / mult end
        if self.y >= 0 then ny = math.floor(self.y * mult + 0.5) / mult
        else ny = math.ceil(self.y * mult - 0.5) / mult end
        self.x, self.y = nx, ny
    end,
    toPolar = function(self, angle, len)
        len = len or 1
        self.x, self.y = math.cos(angle) * len, math.sin(angle) * len
    end,
    rotated = function(self, phi)
        self.x = math.cos(phi) * self.x - math.sin(phi) * self.y
        self.y = math.sin(phi) * self.x + math.cos(phi) * self.y
    end,
    cross = function(self, v)
        return self.x * v.y - self.y * v.x
    end,
    perpendicular = function(self)
    local px, py = self.x, self.y
        self.x = -py
        self.y = px
    end,
    lerpTo = function(self, v, t)
        local i = 1 - t
        self.x, self.y = self.x * i + v.x * t, self.y * i + v.y * t
    end,
    unpack = function(self)
        return self.x, self.y
    end,
}

vecMt.__index = vecMt

local mt = { -- Metatable of vector
    __call = function(_, x, y)
        local vec = {x = x or 0, y = y or 0}
        setmetatable(vec, vecMt)
        return vec
    end
}

setmetatable(vector, mt)

-- CONSTANTS

vector.DOWN = vector(0, 1)
vector.UP = vector(0, -1)
vector.LEFT = vector(-1, 0)
vector.RIGHT = vector(1, 0)
vector.ZERO = vector(0, 0)
vector.ONE = vector(1, 1)

return vector

User avatar
ivan
Party member
Posts: 1531
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Another Lua Vector library

Post by ivan » Mon Jul 29, 2019 10:19 pm

Just to clarify - its not all bad.
Using closures is actually faster than metatables and doesn't require the colon : operator.
This does come at a price, in particular with the initialization overhead and memory usage.
Closures are very good for longer lived objects that you call a lot.

damv
Prole
Posts: 18
Joined: Thu Oct 11, 2018 9:09 pm

Re: Another Lua Vector library

Post by damv » Tue Jul 30, 2019 6:09 pm

ivan wrote:
Mon Jul 29, 2019 10:19 pm
Just to clarify - its not all bad.
Using closures is actually faster than metatables and doesn't require the colon : operator.
This does come at a price, in particular with the initialization overhead and memory usage.
Closures are very good for longer lived objects that you call a lot.
This is very interesting, I had never thought about it. So, what do you suggest to improve?

damv
Prole
Posts: 18
Joined: Thu Oct 11, 2018 9:09 pm

Re: Another Lua Vector library

Post by damv » Tue Jul 30, 2019 6:24 pm

Nelvin wrote:
Mon Jul 29, 2019 10:08 pm
I'd like to repeat what ivan said as it's not only not very efficient, it also wastes a lot of memory.

The problem with the code, as it is, is not primarily the closure but whenever you create a vector instance (which, at the moment, you do in almost all core operations) you create a new table and then add all the helper methods as key/value pairs to this very table. I.e. each of your vectors not only contains the x/y but also "dot", "normalized" etc. etc. as hashed keys with references to those functions.

This is very slow as it has to put those keys into the hashtable each time and of course it does cost a lot of memory. I did a quick test and it roughly requires 10x the memory for each created vector compared to a vector with only x/y.

The simplest optimization is to add all those vec:dot, vec:normalized etc. methods into the vecMt and then add the __index fallback to itself, so you can call those in the same convenient way as you can at the moment.

Like so

Code: Select all


local vector = {_VERSION = "v0.6.0", _TYPE = "module", _NAME = "vector"}
local vecMt = {
    __tostring = function(self)
        return self:string()
    end,
    __add = function(a, b)
        if type(a) == "number" then return vector(a + b.x, a + b.y) end
        if type(b) == "number" then return vector(a.x + b, a.y + b) end
        return vector(a.x + b.x, a.y + b.y)
    end,
    __sub = function(a, b)
        if type(a) == "number" then return vector(a - b.x, a - b.y) end
        if type(b) == "number" then return vector(a.x - b, a.y - b) end
        return vector(a.x - b.x, a.y - b.y)
    end,
    __mul = function(a, b)
        if type(a) == "number" then return vector(a * b.x, a * b.y) end
        if type(b) == "number" then return vector(a.x * b, a.y * b) end
        return vector(a.x * b.x, a.y * b.y)
    end,
    __div = function(a, b)
        if type(a) == "number" then return vector(a / b.x, a / b.y) end
        if type(b) == "number" then return vector(a.x / b, a.y / b) end
        return vector(a.x / b.x, a.y / b.y)
    end,
    __unm = function(t)
        return vector(-t.x, -t.y)
    end,
    __eq = function(a, b)
        return a.x == b.x and a.y == b.y
    end,
    __pow = function(vec, value)
        return vector(vec.x ^ value, vec.y ^ value)
    end,
    __concat = function(a, b)
        if type(a) == "string" then return a .. b:string() end
        if type(b) == "string" then return a:string() .. b end
        return a:string() .. b:string()
    end,

    string = function(self)
        return "vector(" .. self.x .. ", " .. self.y ..")"
    end,
    angle = function(self) -- Radians
        return math.atan2(self.y, self.x)
    end,
    normalized = function(self)
        local m = (self.x^2 + self.y^2)^0.5 --magnitude
        if self.x/m ~= self.x/m then self.x = 0 else self.x = self.x/m end
    if self.y/m ~= self.y/m then self.y = 0 else self.y = self.y/m end
    end,
    distanceSquaredTo = function(self, v)
        local x1, y1 = self.x, self.y
        local x2, y2 = v.x, v.y
        return (x2 - x1)^2 + (y2 - y1)^2
    end,
    distanceTo = function(self, v)
        return self:distanceSquaredTo(v)^0.5
    end,
    distanceSquared = function(self )
        return self.x^2 + self.y^2
    end,
    distance = function(self)
        return (self:distanceSquared())^0.5
    end,
    dot = function(self, v)
        return self.x * v.x + self.y * v.y
    end,
    perpDot = function(self, v)
        return self.x * v.x - self.y * v.y
    end,
    abs = function(self)
        self.x, self.y = math.abs(self.x), math.abs(self.y)
    end,
    round = function(self, dec)
        dec = dec or 0
        local mult = 10^(dec)
        local nx, ny
        if self.x >= 0 then nx = math.floor(self.x * mult + 0.5) / mult
        else nx = math.ceil(self.x * mult - 0.5) / mult end
        if self.y >= 0 then ny = math.floor(self.y * mult + 0.5) / mult
        else ny = math.ceil(self.y * mult - 0.5) / mult end
        self.x, self.y = nx, ny
    end,
    toPolar = function(self, angle, len)
        len = len or 1
        self.x, self.y = math.cos(angle) * len, math.sin(angle) * len
    end,
    rotated = function(self, phi)
        self.x = math.cos(phi) * self.x - math.sin(phi) * self.y
        self.y = math.sin(phi) * self.x + math.cos(phi) * self.y
    end,
    cross = function(self, v)
        return self.x * v.y - self.y * v.x
    end,
    perpendicular = function(self)
    local px, py = self.x, self.y
        self.x = -py
        self.y = px
    end,
    lerpTo = function(self, v, t)
        local i = 1 - t
        self.x, self.y = self.x * i + v.x * t, self.y * i + v.y * t
    end,
    unpack = function(self)
        return self.x, self.y
    end,
}

vecMt.__index = vecMt

local mt = { -- Metatable of vector
    __call = function(_, x, y)
        local vec = {x = x or 0, y = y or 0}
        setmetatable(vec, vecMt)
        return vec
    end
}

setmetatable(vector, mt)

-- CONSTANTS

vector.DOWN = vector(0, 1)
vector.UP = vector(0, -1)
vector.LEFT = vector(-1, 0)
vector.RIGHT = vector(1, 0)
vector.ZERO = vector(0, 0)
vector.ONE = vector(1, 1)

return vector
Thank you for your comment, I had not thought about that, do you think that change is enough to optimize it and work properly?

User avatar
ivan
Party member
Posts: 1531
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Another Lua Vector library

Post by ivan » Tue Jul 30, 2019 6:24 pm

What Nelvin said.
My point was that closures have their own benefits just not in this case.
Your vectors are short lived and you create intermediate ones all the time when using the Lua operators.

damv
Prole
Posts: 18
Joined: Thu Oct 11, 2018 9:09 pm

Re: Another Lua Vector library

Post by damv » Tue Jul 30, 2019 6:57 pm

ivan wrote:
Tue Jul 30, 2019 6:24 pm
What Nelvin said.
My point was that closures have their own benefits just not in this case.
Your vectors are short lived and you create intermediate ones all the time when using the Lua operators.
Thank you very much, I will make the change.

Nelvin
Citizen
Posts: 94
Joined: Mon Sep 12, 2016 7:52 am
Location: Germany

Re: Another Lua Vector library

Post by Nelvin » Tue Jul 30, 2019 9:09 pm

damv wrote:
Tue Jul 30, 2019 6:24 pm
Thank you for your comment, I had not thought about that, do you think that change is enough to optimize it and work properly?
The suggested change is a good, and primarily memory and construction time optimization. Without it, each single vector requires more than 1 kb of memory, quite an overhead compared to the actual information of 2 numbers, i.e. 16bytes, it contains. Of course a Lua table always has some overhead, but especially with lowlevel types you typically want their footprint to be as small as possible.

There is some overhead with regards to the performance as methods called through metatables mean, at runtime, Lua will do a lookup in the table of your vector instance and if it doesn't find the entry (say, "dot") it checks if there's a metatable set for the table and, if so, if there's a __index entry in the table and, if so, if it's of type function and, if so, it calls the function, if not it checks if it's a table and, if so, it repeats the lookup process in the metatable.

This is basically also how all OOP systems in Lua work.

If you really care about performance (maybe just in special areas of some innerloops where you need the best possible performance) you could easily localize the methods in question, shortcutting all the overhead described above and directly call them with your tables.

Code: Select all

local calc_dot = vecMt.dot

local vec1 = vector( 4, 2 )
local vec2 = vector( 2, 4 )

local dotResult = calc_dot( vec1, vec2 )
Not worth it for a single call of course, but if you're doing lots of calculations for, say hundreds or thousands of entities, it's a very simple and worthy optimization. The good thing is, you can do it without any library change, so you can pick and choose based on the needs of your code.

Post Reply

Who is online

Users browsing this forum: No registered users and 2 guests