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
Another Lua Vector library
Re: Another Lua Vector library
Looks fine, I am not 100% convinced by your object-orientation code.
Some functionality is based on closures:
and some functionality is implemented using metatables:
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.
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
...
Code: Select all
local vecMt = {
__tostring = function(self)
return self:string()
end,
I mean yes, it works, but it's just not very efficient.
Other than that the code looks fine.
Cheers.
Re: Another Lua Vector library
Thank you very much, I am not very experienced in this, I will review it and try to improve it.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:and some functionality is implemented using metatables:Code: Select all
local vec = {x = x or 0, y = y or 0} function vec:string() return "vector(" .. self.x .. ", " .. self.y ..")" end ...
I don't see any benefit of using closures when your vectors already use metatables.Code: Select all
local vecMt = { __tostring = function(self) return self:string() end,
I mean yes, it works, but it's just not very efficient.
Other than that the code looks fine.
Cheers.
Re: Another Lua Vector library
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
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
Re: Another Lua Vector library
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.
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.
Re: Another Lua Vector library
This is very interesting, I had never thought about it. So, what do you suggest to improve?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.
Re: Another Lua Vector library
Thank you for your comment, I had not thought about that, do you think that change is enough to optimize it and work properly?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 soCode: 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
Re: Another Lua Vector library
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.
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.
Re: Another Lua Vector library
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 )
Who is online
Users browsing this forum: Ahrefs [Bot], Semrush [Bot] and 6 guests