mirror of
https://github.com/1bardesign/batteries.git
synced 2024-11-22 14:14:36 +00:00
[modified] renamed to batteries, added global export suppression and more documentation
This commit is contained in:
parent
0dd92b2c2e
commit
66a9f41474
@ -5,7 +5,7 @@
|
|||||||
todo: collect some stats on classes/optional global class registry
|
todo: collect some stats on classes/optional global class registry
|
||||||
]]
|
]]
|
||||||
|
|
||||||
function class(inherits)
|
return local function class(inherits)
|
||||||
local c = {}
|
local c = {}
|
||||||
c.__mt = {__index = c}
|
c.__mt = {__index = c}
|
||||||
--handle single inheritence
|
--handle single inheritence
|
62
colour.lua
Normal file
62
colour.lua
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
--[[
|
||||||
|
colour handling stuff
|
||||||
|
|
||||||
|
feel free to alias to colour
|
||||||
|
]]
|
||||||
|
|
||||||
|
local bit = require("bit")
|
||||||
|
local band, bor = bit.band, bit.bor
|
||||||
|
local lshift, rshift = bit.lshift, bit.rshift
|
||||||
|
|
||||||
|
local colour = {}
|
||||||
|
|
||||||
|
function colour.packRGB(r, g, b)
|
||||||
|
local br = lshift(band(0xff, r * 255), 16)
|
||||||
|
local bg = lshift(band(0xff, g * 255), 8)
|
||||||
|
local bb = lshift(band(0xff, b * 255), 0)
|
||||||
|
return bor( br, bg, bb )
|
||||||
|
end
|
||||||
|
|
||||||
|
function colour.packARGB(r, g, b, a)
|
||||||
|
local ba = lshift(band(0xff, a * 255), 24)
|
||||||
|
local br = lshift(band(0xff, r * 255), 16)
|
||||||
|
local bg = lshift(band(0xff, g * 255), 8)
|
||||||
|
local bb = lshift(band(0xff, b * 255), 0)
|
||||||
|
return bor( br, bg, bb, ba )
|
||||||
|
end
|
||||||
|
|
||||||
|
function colour.packRGBA(r, g, b, a)
|
||||||
|
local br = lshift(band(0xff, r * 255), 24)
|
||||||
|
local bg = lshift(band(0xff, g * 255), 16)
|
||||||
|
local bb = lshift(band(0xff, b * 255), 8)
|
||||||
|
local ba = lshift(band(0xff, a * 255), 0)
|
||||||
|
return bor( br, bg, bb, ba )
|
||||||
|
end
|
||||||
|
|
||||||
|
function colour.unpackARGB(argb)
|
||||||
|
local r = rshift(band(argb, 0x00ff0000), 16) / 255.0
|
||||||
|
local g = rshift(band(argb, 0x0000ff00), 8) / 255.0
|
||||||
|
local b = rshift(band(argb, 0x000000ff), 0) / 255.0
|
||||||
|
local a = rshift(band(argb, 0xff000000), 24) / 255.0
|
||||||
|
return r, g, b, a
|
||||||
|
end
|
||||||
|
|
||||||
|
function colour.unpackRGBA(rgba)
|
||||||
|
local r = rshift(band(rgba, 0xff000000), 24) / 255.0
|
||||||
|
local g = rshift(band(rgba, 0x00ff0000), 16) / 255.0
|
||||||
|
local b = rshift(band(rgba, 0x0000ff00), 8) / 255.0
|
||||||
|
local a = rshift(band(rgba, 0x000000ff), 0) / 255.0
|
||||||
|
return r, g, b, a
|
||||||
|
end
|
||||||
|
|
||||||
|
function colour.unpackRGB(rgb)
|
||||||
|
local r = rshift(band(rgb, 0x00ff0000), 16) / 255.0
|
||||||
|
local g = rshift(band(rgb, 0x0000ff00), 8) / 255.0
|
||||||
|
local b = rshift(band(rgb, 0x000000ff), 0) / 255.0
|
||||||
|
local a = 1.0
|
||||||
|
return r, g, b, a
|
||||||
|
end
|
||||||
|
|
||||||
|
--todo: hsl, hsv, other colour spaces
|
||||||
|
|
||||||
|
return colour
|
135
functional.lua
135
functional.lua
@ -1,29 +1,25 @@
|
|||||||
--[[
|
--[[
|
||||||
functional programming facilities
|
functional programming facilities
|
||||||
|
|
||||||
|
notes:
|
||||||
|
be careful about creating closures in hot loops.
|
||||||
|
this is this module's achilles heel - there's no special
|
||||||
|
syntax for closures so it's not apparent that you're suddenly
|
||||||
|
allocating at every call
|
||||||
|
|
||||||
|
reduce has a similar problem, but at least arguments
|
||||||
|
there are clear!
|
||||||
|
|
||||||
|
optional:
|
||||||
|
set BATTERIES_FUNCTIONAL_MODULE to a table before requiring
|
||||||
|
if you don't want this to modify the global `table` table
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--collect all keys of a table into a sequence
|
local _table = BATTERIES_FUNCTIONAL_MODULE or table
|
||||||
function table.keys(t)
|
|
||||||
local r = {}
|
|
||||||
for k,v in pairs(t) do
|
|
||||||
table.insert(r, k)
|
|
||||||
end
|
|
||||||
return r
|
|
||||||
end
|
|
||||||
|
|
||||||
--collect all values of a table into a sequence
|
|
||||||
--(shallow copy if it's already a sequence)
|
|
||||||
function table.values(t)
|
|
||||||
local r = sequence:new()
|
|
||||||
for k,v in pairs(t) do
|
|
||||||
table.insert(r, v)
|
|
||||||
end
|
|
||||||
return r
|
|
||||||
end
|
|
||||||
|
|
||||||
--simple sequential iteration, f is called for all elements of t
|
--simple sequential iteration, f is called for all elements of t
|
||||||
--f can return non-nil to break the loop (and return the value)
|
--f can return non-nil to break the loop (and return the value)
|
||||||
function table.foreach(t, f)
|
function _table.foreach(t, f)
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
local r = f(v, i)
|
local r = f(v, i)
|
||||||
if r ~= nil then
|
if r ~= nil then
|
||||||
@ -35,7 +31,7 @@ end
|
|||||||
--performs a left to right reduction of t using f, with o as the initial value
|
--performs a left to right reduction of t using f, with o as the initial value
|
||||||
-- reduce({1, 2, 3}, f, 0) -> f(f(f(0, 1), 2), 3)
|
-- reduce({1, 2, 3}, f, 0) -> f(f(f(0, 1), 2), 3)
|
||||||
-- (but performed iteratively, so no stack smashing)
|
-- (but performed iteratively, so no stack smashing)
|
||||||
function table.reduce(t, f, o)
|
function _table.reduce(t, f, o)
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
o = f(o, v)
|
o = f(o, v)
|
||||||
end
|
end
|
||||||
@ -44,7 +40,7 @@ end
|
|||||||
|
|
||||||
--maps a sequence {a, b, c} -> {f(a), f(b), f(c)}
|
--maps a sequence {a, b, c} -> {f(a), f(b), f(c)}
|
||||||
-- (automatically drops any nils due to table.insert, which can be used to simultaneously map and filter)
|
-- (automatically drops any nils due to table.insert, which can be used to simultaneously map and filter)
|
||||||
function table.map(t, f)
|
function _table.map(t, f)
|
||||||
local r = {}
|
local r = {}
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
local mapped = f(v, i)
|
local mapped = f(v, i)
|
||||||
@ -55,8 +51,25 @@ function table.map(t, f)
|
|||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--maps a sequence inplace, modifying it {a, b, c} -> {f(a), f(b), f(c)}
|
||||||
|
-- (automatically drops any nils, which can be used to simultaneously map and filter,
|
||||||
|
-- but this results in a linear table.remove so "careful" for big working sets)
|
||||||
|
function _table.remap(t, f)
|
||||||
|
local i = 1
|
||||||
|
while i <= #t do
|
||||||
|
local mapped = f(t[i])
|
||||||
|
if mapped ~= nil then
|
||||||
|
t[i] = mapped
|
||||||
|
i = i + 1
|
||||||
|
else
|
||||||
|
table.remove(t, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
--filters a sequence
|
--filters a sequence
|
||||||
function table.filter(t, f)
|
function _table.filter(t, f)
|
||||||
local r = {}
|
local r = {}
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
if f(v, i) then
|
if f(v, i) then
|
||||||
@ -67,7 +80,7 @@ function table.filter(t, f)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--partitions a sequence based on filter criteria
|
--partitions a sequence based on filter criteria
|
||||||
function table.partition(t, f)
|
function _table.partition(t, f)
|
||||||
local a = {}
|
local a = {}
|
||||||
local b = {}
|
local b = {}
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
@ -84,7 +97,7 @@ end
|
|||||||
--iteration limited by min(#t1, #t2)
|
--iteration limited by min(#t1, #t2)
|
||||||
--function receives arguments (t1, t2, i)
|
--function receives arguments (t1, t2, i)
|
||||||
--nil results ignored
|
--nil results ignored
|
||||||
function table.zip(t1, t2, f)
|
function _table.zip(t1, t2, f)
|
||||||
local ret = {}
|
local ret = {}
|
||||||
local limit = math.min(#t2, #t2)
|
local limit = math.min(#t2, #t2)
|
||||||
for i=1, limit do
|
for i=1, limit do
|
||||||
@ -101,9 +114,9 @@ end
|
|||||||
--return a copy of a sequence with all duplicates removed
|
--return a copy of a sequence with all duplicates removed
|
||||||
-- causes a little "extra" gc churn; one table and one closure
|
-- causes a little "extra" gc churn; one table and one closure
|
||||||
-- as well as the copied deduped table
|
-- as well as the copied deduped table
|
||||||
function table.dedupe(t)
|
function _table.dedupe(t)
|
||||||
local seen = {}
|
local seen = {}
|
||||||
return table.filter(t, function(v)
|
return _table.filter(t, function(v)
|
||||||
if seen[v] then
|
if seen[v] then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@ -113,15 +126,15 @@ function table.dedupe(t)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--append sequence t2 into t1, modifying t1
|
--append sequence t2 into t1, modifying t1
|
||||||
function table.append_inplace(t1, t2)
|
function _table.append_inplace(t1, t2)
|
||||||
table.foreach(t2, function(v)
|
for i,v in ipairs(t2) do
|
||||||
table.insert(t1, v)
|
table.insert(t1, v)
|
||||||
end)
|
end
|
||||||
return t1
|
return t1
|
||||||
end
|
end
|
||||||
|
|
||||||
--return a new sequence with the elements of both t1 and t2
|
--return a new sequence with the elements of both t1 and t2
|
||||||
function table.append(t1, t2)
|
function _table.append(t1, t2)
|
||||||
local r = {}
|
local r = {}
|
||||||
append_inplace(r, t1)
|
append_inplace(r, t1)
|
||||||
append_inplace(r, t2)
|
append_inplace(r, t2)
|
||||||
@ -133,7 +146,7 @@ end
|
|||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
|
|
||||||
--true if any element of the table matches f
|
--true if any element of the table matches f
|
||||||
function table.any(t, f)
|
function _table.any(t, f)
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
if f(v) then
|
if f(v) then
|
||||||
return true
|
return true
|
||||||
@ -143,7 +156,7 @@ function table.any(t, f)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--true if no element of the table matches f
|
--true if no element of the table matches f
|
||||||
function table.none(t, f)
|
function _table.none(t, f)
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
if f(v) then
|
if f(v) then
|
||||||
return false
|
return false
|
||||||
@ -153,7 +166,7 @@ function table.none(t, f)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--true if all elements of the table match f
|
--true if all elements of the table match f
|
||||||
function table.all(t, f)
|
function _table.all(t, f)
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
if not f(v) then
|
if not f(v) then
|
||||||
return false
|
return false
|
||||||
@ -163,7 +176,7 @@ function table.all(t, f)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--counts the elements of t that match f
|
--counts the elements of t that match f
|
||||||
function table.count(t, f)
|
function _table.count(t, f)
|
||||||
local c = 0
|
local c = 0
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
if f(v) then
|
if f(v) then
|
||||||
@ -174,7 +187,7 @@ function table.count(t, f)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--true if the table contains element e
|
--true if the table contains element e
|
||||||
function table.contains(t, e)
|
function _table.contains(t, e)
|
||||||
for i, v in ipairs(t) do
|
for i, v in ipairs(t) do
|
||||||
if v == e then
|
if v == e then
|
||||||
return true
|
return true
|
||||||
@ -184,48 +197,53 @@ function table.contains(t, e)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--return the numeric sum of all elements of t
|
--return the numeric sum of all elements of t
|
||||||
function table.sum(t)
|
function _table.sum(t)
|
||||||
return table.reduce(t, function(a, b)
|
return _table.reduce(t, function(a, b)
|
||||||
return a + b
|
return a + b
|
||||||
end, 0)
|
end, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
--return the numeric mean of all elements of t
|
--return the numeric mean of all elements of t
|
||||||
function table.mean(t)
|
function _table.mean(t)
|
||||||
local len = #t
|
local len = #t
|
||||||
if len == 0 then
|
if len == 0 then
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
return table.sum(t) / len
|
return _table.sum(t) / len
|
||||||
end
|
end
|
||||||
|
|
||||||
--return the minimum and maximum of t in one pass
|
--return the minimum and maximum of t in one pass
|
||||||
function table.minmax(t)
|
--or zero for both if t is empty
|
||||||
local a = table.reduce(t, function(a, b)
|
-- (would perhaps more correctly be math.huge, -math.huge
|
||||||
a.min = a.min and math.min(a.min, b) or b
|
-- but that tends to be surprising/annoying in practice)
|
||||||
a.max = a.max and math.max(a.max, b) or b
|
function _table.minmax(t)
|
||||||
return a
|
local max, min
|
||||||
end, {})
|
for i,v in ipairs(t) do
|
||||||
if a.min == nil then
|
min = not min and v or math.min(min, v)
|
||||||
a.min = 0
|
max = not max and v or math.max(max, v)
|
||||||
a.max = 0
|
|
||||||
end
|
end
|
||||||
return a.min, a.max
|
if min == nil then
|
||||||
|
min = 0
|
||||||
|
max = 0
|
||||||
|
end
|
||||||
|
return min, max
|
||||||
end
|
end
|
||||||
|
|
||||||
function table.max(t)
|
--return the maximum element of t or zero if t is empty
|
||||||
local min, max = table.minmax(t)
|
function _table.max(t)
|
||||||
|
local min, max = _table.minmax(t)
|
||||||
return max
|
return max
|
||||||
end
|
end
|
||||||
|
|
||||||
function table.min(t)
|
--return the minimum element of t or zero if t is empty
|
||||||
local min, max = table.minmax(t)
|
function _table.min(t)
|
||||||
|
local min, max = _table.minmax(t)
|
||||||
return min
|
return min
|
||||||
end
|
end
|
||||||
|
|
||||||
--return the element of the table that results in the greatest numeric value
|
--return the element of the table that results in the greatest numeric value
|
||||||
--(function receives element and key respectively, table evaluated in pairs order)
|
--(function receives element and key respectively, table evaluated in pairs order)
|
||||||
function table.find_best(t, f)
|
function _table.find_best(t, f)
|
||||||
local current = nil
|
local current = nil
|
||||||
local current_best = -math.huge
|
local current_best = -math.huge
|
||||||
for k,e in pairs(t) do
|
for k,e in pairs(t) do
|
||||||
@ -239,14 +257,15 @@ function table.find_best(t, f)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--return the element of the table that results in the value nearest to the passed value
|
--return the element of the table that results in the value nearest to the passed value
|
||||||
function table.find_nearest(t, f, v)
|
--todo: optimise as this generates a closure each time
|
||||||
return table.find_best(t, function(e)
|
function _table.find_nearest(t, f, v)
|
||||||
|
return _table.find_best(t, function(e)
|
||||||
return -math.abs(f(e) - v)
|
return -math.abs(f(e) - v)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
--return the first element of the table that results in a true filter
|
--return the first element of the table that results in a true filter
|
||||||
function table.find_match(t, f)
|
function _table.find_match(t, f)
|
||||||
for i,v in ipairs(t) do
|
for i,v in ipairs(t) do
|
||||||
if f(v) then
|
if f(v) then
|
||||||
return v
|
return v
|
||||||
@ -254,3 +273,5 @@ function table.find_match(t, f)
|
|||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return _table
|
||||||
|
93
init.lua
93
init.lua
@ -1,35 +1,92 @@
|
|||||||
--[[
|
--[[
|
||||||
core modules
|
batteries for lua
|
||||||
|
|
||||||
if required as the "entire library" (ie by this file), puts everything into
|
if required as the "entire library" (ie by this file), puts everything into
|
||||||
global namespace as it'll presumably be commonly used
|
global namespace by default as it'll presumably be commonly used
|
||||||
|
|
||||||
if not, several of the modules work as "normal" modules and return a table
|
if not, several of the modules work as normal lua modules and return a table
|
||||||
for local-friendly use
|
for local-friendly use
|
||||||
|
|
||||||
|
the others that modify some global table can be talked into behaving as normal
|
||||||
|
lua modules as well by setting appropriate globals prior to inclusion
|
||||||
|
|
||||||
|
you can avoid modifying any global namespace by setting
|
||||||
|
|
||||||
|
BATTERIES_NO_GLOBALS = true
|
||||||
|
|
||||||
|
before requiring, then everything can be accessed as eg
|
||||||
|
|
||||||
|
batteries.table.stable_sort
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local path = ...
|
local path = ...
|
||||||
local function relative_file(p)
|
local function require_relative(p)
|
||||||
return table.concat({path, p}, ".")
|
return require(table.concat({path, p}, "."))
|
||||||
end
|
end
|
||||||
|
|
||||||
require(relative_file("oo"))
|
if BATTERIES_NO_GLOBALS then
|
||||||
|
--define local tables for everything to go into
|
||||||
|
BATTERIES_MATH_MODULE = {}
|
||||||
|
BATTERIES_TABLE_MODULE = {}
|
||||||
|
BATTERIES_FUNCTIONAL_MODULE = {}
|
||||||
|
end
|
||||||
|
|
||||||
require(relative_file("math"))
|
local _class = require_relative("class")
|
||||||
|
|
||||||
require(relative_file("table"))
|
local _math = require_relative("math")
|
||||||
require(relative_file("stable_sort"))
|
|
||||||
|
|
||||||
require(relative_file("functional"))
|
local _table = require_relative("table")
|
||||||
sequence = require(relative_file("sequence"))
|
local _stable_sort = require_relative("stable_sort")
|
||||||
unique_mapping = require(relative_file("unique_mapping"))
|
|
||||||
|
|
||||||
vec2 = require(relative_file("vec2"))
|
local _functional = require_relative("functional")
|
||||||
vec3 = require(relative_file("vec3"))
|
local _sequence = require_relative("sequence")
|
||||||
intersect = require(relative_file("intersect"))
|
|
||||||
|
|
||||||
state_machine = require(relative_file("state_machine"))
|
local _vec2 = require_relative("vec2")
|
||||||
|
local _vec3 = require_relative("vec3")
|
||||||
|
local _intersect = require_relative("intersect")
|
||||||
|
|
||||||
async = require(relative_file("async"))
|
local _unique_mapping = require_relative("unique_mapping")
|
||||||
|
local _state_machine = require_relative("state_machine")
|
||||||
|
|
||||||
manual_gc = require(relative_file("manual_gc"))
|
local _async = require_relative("async")
|
||||||
|
|
||||||
|
local _manual_gc = require_relative("manual_gc")
|
||||||
|
|
||||||
|
local _colour = require_relative("colour")
|
||||||
|
|
||||||
|
--export globally if required
|
||||||
|
if not BATTERIES_NO_GLOBALS then
|
||||||
|
class = _class
|
||||||
|
sequence = _sequence
|
||||||
|
|
||||||
|
vec2 = _vec2
|
||||||
|
vec3 = _vec3
|
||||||
|
intersect = _intersect
|
||||||
|
|
||||||
|
unique_mapping = _unique_mapping
|
||||||
|
state_machine = _state_machine
|
||||||
|
async = _async
|
||||||
|
manual_gc = _manual_gc
|
||||||
|
|
||||||
|
--support both spellings
|
||||||
|
colour = _colour
|
||||||
|
color = _colour
|
||||||
|
end
|
||||||
|
|
||||||
|
--either way, export to package registry
|
||||||
|
return {
|
||||||
|
class = _class,
|
||||||
|
math = _math,
|
||||||
|
table = _table,
|
||||||
|
stable_sort = _stable_sort,
|
||||||
|
functional = _functional,
|
||||||
|
sequence = _sequence,
|
||||||
|
vec2 = _vec2,
|
||||||
|
vec3 = _vec3,
|
||||||
|
intersect = _intersect,
|
||||||
|
unique_mapping = _unique_mapping,
|
||||||
|
state_machine = _state_machine,
|
||||||
|
async = _async,
|
||||||
|
manual_gc = _manual_gc,
|
||||||
|
colour = _colour,
|
||||||
|
}
|
||||||
|
109
math.lua
109
math.lua
@ -1,11 +1,15 @@
|
|||||||
--[[
|
--[[
|
||||||
extra mathematical functions
|
extra mathematical functions
|
||||||
|
|
||||||
|
optional:
|
||||||
|
set BATTERIES_MATH_MODULE to a table before requiring
|
||||||
|
if you don't want this to modify the global `math` table
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local bit = require("bit")
|
local _math = BATTERIES_MATH_MODULE or math
|
||||||
|
|
||||||
--wrap v around range [lo, hi)
|
--wrap v around range [lo, hi)
|
||||||
function math.wrap(v, lo, hi)
|
function _math.wrap(v, lo, hi)
|
||||||
local range = hi - lo
|
local range = hi - lo
|
||||||
local relative = v - lo
|
local relative = v - lo
|
||||||
local relative_wrapped = relative % range
|
local relative_wrapped = relative % range
|
||||||
@ -15,51 +19,51 @@ function math.wrap(v, lo, hi)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--clamp v to range [lo, hi]
|
--clamp v to range [lo, hi]
|
||||||
function math.clamp(v, lo, hi)
|
function _math.clamp(v, lo, hi)
|
||||||
return math.max(lo, math.min(v, hi))
|
return math.max(lo, math.min(v, hi))
|
||||||
end
|
end
|
||||||
|
|
||||||
function math.clamp01(v)
|
function _math.clamp01(v)
|
||||||
return math.clamp(v, 0, 1)
|
return _math.clamp(v, 0, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
--round v to nearest whole
|
--round v to nearest whole
|
||||||
function math.round(v)
|
function _math.round(v)
|
||||||
return math.floor(v + 0.5)
|
return math.floor(v + 0.5)
|
||||||
end
|
end
|
||||||
|
|
||||||
--round v to one-in x
|
--round v to one-in x
|
||||||
-- (eg x = 2, v rounded to increments of 0.5)
|
-- (eg x = 2, v rounded to increments of 0.5)
|
||||||
function math.to_one_in(v, x)
|
function _math.to_one_in(v, x)
|
||||||
return math.round(v * x) / x
|
return _math.round(v * x) / x
|
||||||
end
|
end
|
||||||
|
|
||||||
--round v to a given decimal precision
|
--round v to a given decimal precision
|
||||||
function math.to_precision(v, decimal_points)
|
function _math.to_precision(v, decimal_points)
|
||||||
return math.to_one_in(v, math.pow(10, decimal_points))
|
return _math.to_one_in(v, math.pow(10, decimal_points))
|
||||||
end
|
end
|
||||||
|
|
||||||
--0, 1, -1 sign of a scalar
|
--0, 1, -1 sign of a scalar
|
||||||
function math.sign(v)
|
function _math.sign(v)
|
||||||
if v < 0 then return -1 end
|
if v < 0 then return -1 end
|
||||||
if v > 0 then return 1 end
|
if v > 0 then return 1 end
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
--linear interpolation between a and b
|
--linear interpolation between a and b
|
||||||
function math.lerp(a, b, t)
|
function _math.lerp(a, b, t)
|
||||||
return a * (1.0 - t) + b * t
|
return a * (1.0 - t) + b * t
|
||||||
end
|
end
|
||||||
|
|
||||||
--classic smoothstep
|
--classic smoothstep
|
||||||
--(only "safe" for 0-1 range)
|
--(only "safe" for 0-1 range)
|
||||||
function math.smoothstep(v)
|
function _math.smoothstep(v)
|
||||||
return v * v * (3 - 2 * v)
|
return v * v * (3 - 2 * v)
|
||||||
end
|
end
|
||||||
|
|
||||||
--classic smootherstep; zero 2nd order derivatives at 0 and 1
|
--classic smootherstep; zero 2nd order derivatives at 0 and 1
|
||||||
--(only safe for 0-1 range)
|
--(only safe for 0-1 range)
|
||||||
function math.smootherstep(v)
|
function _math.smootherstep(v)
|
||||||
return v * v * v * (v * (v * 6 - 15) + 10)
|
return v * v * v * (v * (v * 6 - 15) + 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -125,7 +129,7 @@ local sparse_primes_1k = {
|
|||||||
6689, 7039, 7307, 7559, 7573, 7919,
|
6689, 7039, 7307, 7559, 7573, 7919,
|
||||||
}
|
}
|
||||||
|
|
||||||
function math.first_above(v, t)
|
function _math.first_above(v, t)
|
||||||
for _,p in ipairs(t) do
|
for _,p in ipairs(t) do
|
||||||
if p > v then
|
if p > v then
|
||||||
return p
|
return p
|
||||||
@ -134,79 +138,30 @@ function math.first_above(v, t)
|
|||||||
return t[#t]
|
return t[#t]
|
||||||
end
|
end
|
||||||
|
|
||||||
function math.next_prime_1k(v)
|
function _math.next_prime_1k(v)
|
||||||
return math.first_above(v, primes_1k)
|
return _math.first_above(v, primes_1k)
|
||||||
end
|
end
|
||||||
|
|
||||||
function math.next_prime_1k_sparse(v)
|
function _math.next_prime_1k_sparse(v)
|
||||||
return math.first_above(v, sparse_primes_1k)
|
return _math.first_above(v, sparse_primes_1k)
|
||||||
end
|
|
||||||
|
|
||||||
--color handling stuff
|
|
||||||
local band, bor = bit.band, bit.bor
|
|
||||||
local lshift, rshift = bit.lshift, bit.rshift
|
|
||||||
|
|
||||||
function math.colorToRGB(r, g, b)
|
|
||||||
local br = lshift(band(0xff, r * 255), 16)
|
|
||||||
local bg = lshift(band(0xff, g * 255), 8)
|
|
||||||
local bb = lshift(band(0xff, b * 255), 0)
|
|
||||||
return bor( br, bg, bb )
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.colorToARGB(r, g, b, a)
|
|
||||||
local ba = lshift(band(0xff, a * 255), 24)
|
|
||||||
local br = lshift(band(0xff, r * 255), 16)
|
|
||||||
local bg = lshift(band(0xff, g * 255), 8)
|
|
||||||
local bb = lshift(band(0xff, b * 255), 0)
|
|
||||||
return bor( br, bg, bb, ba )
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.colorToRGBA(r, g, b, a)
|
|
||||||
local br = lshift(band(0xff, r * 255), 24)
|
|
||||||
local bg = lshift(band(0xff, g * 255), 16)
|
|
||||||
local bb = lshift(band(0xff, b * 255), 8)
|
|
||||||
local ba = lshift(band(0xff, a * 255), 0)
|
|
||||||
return bor( br, bg, bb, ba )
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.ARGBToColor(argb)
|
|
||||||
local r = rshift(band(argb, 0x00ff0000), 16) / 255.0
|
|
||||||
local g = rshift(band(argb, 0x0000ff00), 8) / 255.0
|
|
||||||
local b = rshift(band(argb, 0x000000ff), 0) / 255.0
|
|
||||||
local a = rshift(band(argb, 0xff000000), 24) / 255.0
|
|
||||||
return r, g, b, a
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.RGBAToColor(rgba)
|
|
||||||
local r = rshift(band(rgba, 0xff000000), 24) / 255.0
|
|
||||||
local g = rshift(band(rgba, 0x00ff0000), 16) / 255.0
|
|
||||||
local b = rshift(band(rgba, 0x0000ff00), 8) / 255.0
|
|
||||||
local a = rshift(band(rgba, 0x000000ff), 0) / 255.0
|
|
||||||
return r, g, b, a
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.RGBToColor(rgb)
|
|
||||||
local r = rshift(band(rgb, 0x00ff0000), 16) / 255.0
|
|
||||||
local g = rshift(band(rgb, 0x0000ff00), 8) / 255.0
|
|
||||||
local b = rshift(band(rgb, 0x000000ff), 0) / 255.0
|
|
||||||
local a = 1.0
|
|
||||||
return r, g, b, a
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--angle handling stuff
|
--angle handling stuff
|
||||||
function math.normalise_angle(a)
|
function _math.normalise_angle(a)
|
||||||
return math.wrap(a, -math.pi, math.pi)
|
return _math.wrap(a, -math.pi, math.pi)
|
||||||
end
|
end
|
||||||
|
|
||||||
function math.relative_angle(a1, a2)
|
function _math.relative_angle(a1, a2)
|
||||||
a1 = math.normalise_angle(a1)
|
a1 = _math.normalise_angle(a1)
|
||||||
a2 = math.normalise_angle(a2)
|
a2 = _math.normalise_angle(a2)
|
||||||
return math.normalise_angle(a1 - a2)
|
return _math.normalise_angle(a1 - a2)
|
||||||
end
|
end
|
||||||
|
|
||||||
--geometric rotation multi-return
|
--geometric rotation multi-return
|
||||||
function math.rotate(x, y, r)
|
function _math.rotate(x, y, r)
|
||||||
local s = math.sin(r)
|
local s = math.sin(r)
|
||||||
local c = math.cos(r)
|
local c = math.cos(r)
|
||||||
return c * x - s * y, s * x + c * y
|
return c * x - s * y, s * x + c * y
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return _math
|
||||||
|
37
readme.md
37
readme.md
@ -1,5 +1,38 @@
|
|||||||
# lua-core
|
# batteries
|
||||||
|
|
||||||
Core dependencies for making games with lua, especially with [love](https://love2d.org).
|
Core dependencies for making games with lua, especially with [love](https://love2d.org).
|
||||||
|
|
||||||
Definitely needs a catchier name, but does a lot of useful stuff to get projects off the ground faster.
|
Does a lot to get projects off the ground faster, filling out lua's standard library and providing implementations of common algorithms and data structures useful for games.
|
||||||
|
|
||||||
|
# Module Overview
|
||||||
|
|
||||||
|
- `class` - single-inheritance oo in a single function
|
||||||
|
- `math` - mathematical extensions
|
||||||
|
- `table` - table handling extensions
|
||||||
|
- `stable_sort` - a stable sorting algorithm that is also faster than table.sort under luajit
|
||||||
|
- `functional` - functional programming facilities. `map`, `reduce`, `any`, `match`, `minmax`, `mean`...
|
||||||
|
- `sequence` - an oo wrapper on sequential tables so you can do `t:insert(i, v)` instead of `table.insert(t, i, v)`. Also supports the functional interfance.
|
||||||
|
- `vec2` - 2d vectors with method chaining, garbage saving interface
|
||||||
|
- `vec3` - 3d vectors as above
|
||||||
|
- `intersect` - 2d intersection routines, a bit sparse at the moment
|
||||||
|
- `unique_mapping` - generate a unique mapping from arbitrary lua values to numeric keys - essentially making up a consistent ordering for unordered data. niche, but useful for optimising draw batches for example, as you can't sort on textures without it.
|
||||||
|
- `state_machine` - finite state machine implementation with state transitions and all the rest. useful for game states, ai, cutscenes...
|
||||||
|
- `async` - async operations as coroutines.
|
||||||
|
- `manual_gc` - get gc out of your update/draw calls. really good when trying to get accurate profiling information. requires you to think a bit about your garbage budgets though.
|
||||||
|
- `colour` - colour conversion routines. can also be spelled `color` if you're into that.
|
||||||
|
|
||||||
|
# PRs
|
||||||
|
|
||||||
|
Pull requests are welcome. If you have something "big" to add please get in touch before starting work, but I'm quite open minded!
|
||||||
|
|
||||||
|
# Globals?
|
||||||
|
|
||||||
|
By default `batteries` will modify builtin lua tables (such as `table` and `math`) as it sees fit, as this eases consumption later on - you don't have to remember if say, `table.remove_value` is built in to lua or not. As the intended use case is fairly pervasive, required early, and "fire and forget", this sits with me just fine.
|
||||||
|
|
||||||
|
If however, you'd prefer to require things locally, you can (rather ironically) set a few globals at boot time as documented in each module to change this behaviour, or set `BATTERIES_NO_GLOBALS = true` to make none of them modify anything global. If you really want to you can undefine the behaviour-changing globals after the module is required, as the results are cached.
|
||||||
|
|
||||||
|
I'd strongly recommend that if you find yourself defining the above, stop and think why/if you really want to avoid globals for a library intended to be commonly used across your entire codebase!
|
||||||
|
|
||||||
|
Some folks will have good reasons, which is why the functionality is present!
|
||||||
|
|
||||||
|
Others may wish to reconsider and save themselves typing batteries a few hundred times :)
|
||||||
|
36
sequence.lua
36
sequence.lua
@ -7,6 +7,7 @@
|
|||||||
in that case, you can still use the table methods that accept a table
|
in that case, you can still use the table methods that accept a table
|
||||||
first as method calls.
|
first as method calls.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local sequence = {}
|
local sequence = {}
|
||||||
|
|
||||||
sequence.mt = {__index = sequence}
|
sequence.mt = {__index = sequence}
|
||||||
@ -20,52 +21,59 @@ function sequence:new(t)
|
|||||||
return setmetatable(t or {}, sequence.mt)
|
return setmetatable(t or {}, sequence.mt)
|
||||||
end
|
end
|
||||||
|
|
||||||
--import table functions to sequence as-is
|
--alias
|
||||||
sequence.join = table.concat --alias
|
sequence.join = table.concat
|
||||||
|
|
||||||
--sorting default to stable if present
|
--sorting default to stable if present
|
||||||
sequence.sort = table.stable_sort or table.sort
|
sequence.sort = table.stable_sort or table.sort
|
||||||
|
|
||||||
--import functional interface to sequence in a sequence preserving way
|
--(handle functional module delegation correctly)
|
||||||
|
local _func = BATTERIES_FUNCTIONAL_MODULE or table
|
||||||
|
|
||||||
|
--import functional interface to sequence in a type preserving way
|
||||||
function sequence:keys()
|
function sequence:keys()
|
||||||
return sequence:new(table.keys(self))
|
return sequence:new(_func.keys(self))
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:values()
|
function sequence:values()
|
||||||
return sequence:new(table.values(self))
|
return sequence:new(_func.values(self))
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:foreach(f)
|
function sequence:foreach(f)
|
||||||
return table.foreach(self, f)
|
return _func.foreach(self, f)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:reduce(f, o)
|
function sequence:reduce(f, o)
|
||||||
return table.foreach(self, f, o)
|
return _func.foreach(self, f, o)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:map(f)
|
function sequence:map(f)
|
||||||
return sequence:new(table.map(self, f))
|
return sequence:new(_func.map(self, f))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:remap(f)
|
||||||
|
return _func.remap(self, f)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:filter(f)
|
function sequence:filter(f)
|
||||||
return sequence:new(table.filter(self, f))
|
return sequence:new(_func.filter(self, f))
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:partition(f)
|
function sequence:partition(f)
|
||||||
local a, b = table.partition(self, f)
|
local a, b = _func.partition(self, f)
|
||||||
return sequence:new(a), sequence:new(b)
|
return sequence:new(a), sequence:new(b)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:zip(other, f)
|
function sequence:zip(other, f)
|
||||||
return sequence:new(table.zip(self, other, f))
|
return sequence:new(_func.zip(self, other, f))
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:dedupe()
|
function sequence:dedupe()
|
||||||
return table.dedupe(self)
|
return _func.dedupe(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:append_inplace(other)
|
function sequence:append_inplace(other)
|
||||||
return table.append_inplace(self, other)
|
return _func.append_inplace(self, other)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:append(other)
|
function sequence:append(other)
|
||||||
@ -73,7 +81,7 @@ function sequence:append(other)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function sequence:copy(deep)
|
function sequence:copy(deep)
|
||||||
return sequence:new(table.copy(self, deep))
|
return sequence:new(_func.copy(self, deep))
|
||||||
end
|
end
|
||||||
|
|
||||||
return sequence
|
return sequence
|
@ -1,6 +1,6 @@
|
|||||||
-- stable sorting routines for lua
|
-- stable sorting routines for lua
|
||||||
--
|
--
|
||||||
-- modifies the global table namespace so you don't have
|
-- by default modifies the global table namespace so you don't have
|
||||||
-- to re-require it everywhere.
|
-- to re-require it everywhere.
|
||||||
--
|
--
|
||||||
-- table.stable_sort
|
-- table.stable_sort
|
||||||
@ -10,6 +10,11 @@
|
|||||||
-- table.insertion_sort
|
-- table.insertion_sort
|
||||||
-- an insertion sort, should you prefer it
|
-- an insertion sort, should you prefer it
|
||||||
--
|
--
|
||||||
|
-- alternatively, adds them to either BATTERIES_SORT_MODULE or
|
||||||
|
-- BATTERIES_TABLE_MODULE if defined
|
||||||
|
--
|
||||||
|
-- also returns just the interface above as a module either way
|
||||||
|
--
|
||||||
|
|
||||||
--this is based on MIT licensed code from Dirk Laurie and Steve Fisher
|
--this is based on MIT licensed code from Dirk Laurie and Steve Fisher
|
||||||
--license as follows:
|
--license as follows:
|
||||||
@ -36,14 +41,15 @@
|
|||||||
DEALINGS IN THE SOFTWARE.
|
DEALINGS IN THE SOFTWARE.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
-- (modifications by Max Cahill 2018)
|
-- (modifications by Max Cahill 2018, 2020)
|
||||||
|
|
||||||
local _sort_core = {}
|
local _sort_core = {}
|
||||||
|
|
||||||
--tunable size for
|
--tunable size for insertion sort "bottom out"
|
||||||
_sort_core.max_chunk_size = 24
|
_sort_core.max_chunk_size = 32
|
||||||
|
|
||||||
function _sort_core.insertion_sort_impl(array, first, last, less)
|
--insertion sort on a section of array
|
||||||
|
function _sort_core._insertion_sort_impl(array, first, last, less)
|
||||||
for i = first + 1, last do
|
for i = first + 1, last do
|
||||||
local k = first
|
local k = first
|
||||||
local v = array[i]
|
local v = array[i]
|
||||||
@ -59,7 +65,8 @@ function _sort_core.insertion_sort_impl(array, first, last, less)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function _sort_core.merge(array, workspace, low, middle, high, less)
|
--merge sorted adjacent sections of array
|
||||||
|
function _sort_core._merge(array, workspace, low, middle, high, less)
|
||||||
local i, j, k
|
local i, j, k
|
||||||
i = 1
|
i = 1
|
||||||
-- copy first half of array to auxiliary array
|
-- copy first half of array to auxiliary array
|
||||||
@ -91,24 +98,25 @@ function _sort_core.merge(array, workspace, low, middle, high, less)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--implementation for the merge sort
|
||||||
function _sort_core.merge_sort_impl(array, workspace, low, high, less)
|
function _sort_core._merge_sort_impl(array, workspace, low, high, less)
|
||||||
if high - low <= _sort_core.max_chunk_size then
|
if high - low <= _sort_core.max_chunk_size then
|
||||||
_sort_core.insertion_sort_impl(array, low, high, less)
|
_sort_core._insertion_sort_impl(array, low, high, less)
|
||||||
else
|
else
|
||||||
local middle = math.floor((low + high) / 2)
|
local middle = math.floor((low + high) / 2)
|
||||||
_sort_core.merge_sort_impl(array, workspace, low, middle, less)
|
_sort_core._merge_sort_impl(array, workspace, low, middle, less)
|
||||||
_sort_core.merge_sort_impl(array, workspace, middle + 1, high, less)
|
_sort_core._merge_sort_impl(array, workspace, middle + 1, high, less)
|
||||||
_sort_core.merge(array, workspace, low, middle, high, less)
|
_sort_core._merge(array, workspace, low, middle, high, less)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function default_less(a, b)
|
--default comparison; hoisted for clarity
|
||||||
|
local function default_less(a, b)
|
||||||
return a < b
|
return a < b
|
||||||
end
|
end
|
||||||
|
|
||||||
--inline common setup stuff
|
--inline common setup stuff
|
||||||
function _sort_core.sort_setup(array, less)
|
function _sort_core._sort_setup(array, less)
|
||||||
--default less
|
--default less
|
||||||
less = less or default_less
|
less = less or default_less
|
||||||
--
|
--
|
||||||
@ -127,28 +135,35 @@ end
|
|||||||
|
|
||||||
function _sort_core.stable_sort(array, less)
|
function _sort_core.stable_sort(array, less)
|
||||||
--setup
|
--setup
|
||||||
local trivial, n, less = _sort_core.sort_setup(array, less)
|
local trivial, n, less = _sort_core._sort_setup(array, less)
|
||||||
if not trivial then
|
if not trivial then
|
||||||
--temp storage; allocate ahead of time
|
--temp storage; allocate ahead of time
|
||||||
local workspace = {}
|
local workspace = {}
|
||||||
local middle = math.ceil(n / 2)
|
local middle = math.ceil(n / 2)
|
||||||
workspace[middle] = array[1]
|
workspace[middle] = array[1]
|
||||||
--dive in
|
--dive in
|
||||||
_sort_core.merge_sort_impl( array, workspace, 1, n, less )
|
_sort_core._merge_sort_impl( array, workspace, 1, n, less )
|
||||||
end
|
end
|
||||||
return array
|
return array
|
||||||
end
|
end
|
||||||
|
|
||||||
function _sort_core.insertion_sort(array, less)
|
function _sort_core.insertion_sort(array, less)
|
||||||
--setup
|
--setup
|
||||||
local trivial, n, less = _sort_core.sort_setup(array, less)
|
local trivial, n, less = _sort_core._sort_setup(array, less)
|
||||||
if not trivial then
|
if not trivial then
|
||||||
_sort_core.insertion_sort_impl(array, 1, n, less)
|
_sort_core._insertion_sort_impl(array, 1, n, less)
|
||||||
end
|
end
|
||||||
return array
|
return array
|
||||||
end
|
end
|
||||||
|
|
||||||
|
_sort_core.unstable_sort = table.sort
|
||||||
|
|
||||||
--export sort core
|
--export sort core
|
||||||
table.insertion_sort = _sort_core.insertion_sort
|
|
||||||
table.stable_sort = _sort_core.stable_sort
|
local _table = BATTERIES_SORT_MODULE or BATTERIES_TABLE_MODULE or table
|
||||||
table.unstable_sort = table.sort
|
|
||||||
|
_table.insertion_sort = _sort_core.insertion_sort
|
||||||
|
_table.stable_sort = _sort_core.stable_sort
|
||||||
|
_table.unstable_sort = _sort_core.unstable_sort
|
||||||
|
|
||||||
|
return _sort_core
|
||||||
|
151
table.lua
151
table.lua
@ -1,36 +1,52 @@
|
|||||||
--[[
|
--[[
|
||||||
extra table routines
|
extra table routines
|
||||||
|
|
||||||
|
optional:
|
||||||
|
set BATTERIES_TABLE_MODULE to a table before requiring
|
||||||
|
if you don't want this to modify the global `table` table
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
local _table = BATTERIES_TABLE_MODULE or table
|
||||||
|
|
||||||
|
--apply prototype to module if it isn't the global table
|
||||||
|
if BATTERIES_TABLE_MODULE ~= table then
|
||||||
|
setmetatable(BATTERIES_TABLE_MODULE, {
|
||||||
|
__index = table,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--alias
|
||||||
|
_table.join = _table.concat
|
||||||
|
|
||||||
--return the back element of a table
|
--return the back element of a table
|
||||||
function table.back(t)
|
function _table.back(t)
|
||||||
return t[#t]
|
return t[#t]
|
||||||
end
|
end
|
||||||
|
|
||||||
--remove the back element of a table and return it
|
--remove the back element of a table and return it
|
||||||
function table.pop(t)
|
function _table.pop(t)
|
||||||
return table.remove(t)
|
return table.remove(t)
|
||||||
end
|
end
|
||||||
|
|
||||||
--insert to the back of a table
|
--insert to the back of a table
|
||||||
function table.push(t, v)
|
function _table.push(t, v)
|
||||||
return table.insert(t, v)
|
return table.insert(t, v)
|
||||||
end
|
end
|
||||||
|
|
||||||
--remove the front element of a table and return it
|
--remove the front element of a table and return it
|
||||||
function table.shift(t)
|
function _table.shift(t)
|
||||||
return table.remove(t, 1)
|
return table.remove(t, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
--insert to the front of a table
|
--insert to the front of a table
|
||||||
function table.unshift(t, v)
|
function _table.unshift(t, v)
|
||||||
return table.insert(t, 1, v)
|
return table.insert(t, 1, v)
|
||||||
end
|
end
|
||||||
|
|
||||||
--find the index in a sequential table that a resides at
|
--find the index in a sequential table that a resides at
|
||||||
--or nil if nothing was found
|
--or nil if nothing was found
|
||||||
--(todo: consider pairs version?)
|
--(todo: consider pairs version?)
|
||||||
function table.index_of(t, a)
|
function _table.index_of(t, a)
|
||||||
if a == nil then return nil end
|
if a == nil then return nil end
|
||||||
for i,b in ipairs(t) do
|
for i,b in ipairs(t) do
|
||||||
if a == b then
|
if a == b then
|
||||||
@ -42,8 +58,8 @@ end
|
|||||||
|
|
||||||
--remove the first instance of value from a table (linear search)
|
--remove the first instance of value from a table (linear search)
|
||||||
--returns true if the value was removed, else false
|
--returns true if the value was removed, else false
|
||||||
function table.remove_value(t, a)
|
function _table.remove_value(t, a)
|
||||||
local i = table.index_of(t, a)
|
local i = _table.index_of(t, a)
|
||||||
if i then
|
if i then
|
||||||
table.remove(t, i)
|
table.remove(t, i)
|
||||||
return true
|
return true
|
||||||
@ -53,8 +69,8 @@ end
|
|||||||
|
|
||||||
--add a value to a table if it doesn't already exist (linear search)
|
--add a value to a table if it doesn't already exist (linear search)
|
||||||
--returns true if the value was added, else false
|
--returns true if the value was added, else false
|
||||||
function table.add_value(t, a)
|
function _table.add_value(t, a)
|
||||||
local i = table.index_of(t, a)
|
local i = _table.index_of(t, a)
|
||||||
if not i then
|
if not i then
|
||||||
table.insert(t, a)
|
table.insert(t, a)
|
||||||
return true
|
return true
|
||||||
@ -70,7 +86,7 @@ local function _random(min, max, r)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--pick a random value from a table (or nil if it's empty)
|
--pick a random value from a table (or nil if it's empty)
|
||||||
function table.pick_random(t, r)
|
function _table.pick_random(t, r)
|
||||||
if #t == 0 then
|
if #t == 0 then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@ -78,7 +94,7 @@ function table.pick_random(t, r)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--shuffle the order of a table
|
--shuffle the order of a table
|
||||||
function table.shuffle(t, r)
|
function _table.shuffle(t, r)
|
||||||
for i = 1, #t do
|
for i = 1, #t do
|
||||||
local j = _random(1, #t, r)
|
local j = _random(1, #t, r)
|
||||||
t[i], t[j] = t[j], t[i]
|
t[i], t[j] = t[j], t[i]
|
||||||
@ -87,7 +103,7 @@ function table.shuffle(t, r)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--reverse the order of a table
|
--reverse the order of a table
|
||||||
function table.reverse(t)
|
function _table.reverse(t)
|
||||||
for i = 1, #t / 2 do
|
for i = 1, #t / 2 do
|
||||||
local j = #t - i + 1
|
local j = #t - i + 1
|
||||||
t[i], t[j] = t[j], t[i]
|
t[i], t[j] = t[j], t[i]
|
||||||
@ -95,28 +111,57 @@ function table.reverse(t)
|
|||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--collect all keys of a table into a sequential table
|
||||||
|
--(useful if you need to iterate non-changing keys often and want an nyi tradeoff;
|
||||||
|
-- this call will be slow but then following iterations can use ipairs)
|
||||||
|
function _table.keys(t)
|
||||||
|
local r = {}
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
table.insert(r, k)
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--collect all values of a keyed table into a sequential table
|
||||||
|
--(shallow copy if it's already sequential)
|
||||||
|
function _table.values(t)
|
||||||
|
local r = {}
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
table.insert(r, v)
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
--(might already exist depending on luajit)
|
--(might already exist depending on luajit)
|
||||||
if table.clear == nil then
|
if _table.clear == nil then
|
||||||
--destructively clear a numerically keyed table
|
if _table ~= table and table.clear then
|
||||||
--useful when multiple references are floating around
|
--import from global if it exists
|
||||||
--so you cannot just pop a new table out of nowhere
|
_table.clear = table.clear
|
||||||
function table.clear(t)
|
else
|
||||||
assert(type(to) == "table", "table.clear - argument 't' must be a table")
|
--remove all values from a table
|
||||||
while t[1] ~= nil do
|
--useful when multiple references are floating around
|
||||||
table.remove(t)
|
--so you cannot just pop a new table out of nowhere
|
||||||
|
function _table.clear(t)
|
||||||
|
assert(type(to) == "table", "table.clear - argument 't' must be a table")
|
||||||
|
local k = next(t)
|
||||||
|
while k ~= nil do
|
||||||
|
t[k] = nil
|
||||||
|
k = next(t)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--overlay one table directly onto another, shallow only
|
--note:
|
||||||
function table.overlay(to, from)
|
-- copies and overlays are currently not satisfactory
|
||||||
assert(type(to) == "table", "table.overlay - argument 'to' must be a table")
|
--
|
||||||
assert(type(from) == "table", "table.overlay - argument 'from' must be a table")
|
-- i feel that copy especially tries to do too much and
|
||||||
for k,v in pairs(from) do
|
-- probably they should be split into separate functions
|
||||||
to[k] = v
|
-- to be both more explicit and performant, ie
|
||||||
end
|
--
|
||||||
return to
|
-- shallow_copy, deep_copy, shallow_overlay, deep_overlay
|
||||||
end
|
--
|
||||||
|
-- input is welcome on this :)
|
||||||
|
|
||||||
--copy a table
|
--copy a table
|
||||||
-- deep_or_into is either:
|
-- deep_or_into is either:
|
||||||
@ -128,7 +173,7 @@ end
|
|||||||
-- if into specified, copies into that table
|
-- if into specified, copies into that table
|
||||||
-- but doesn't clear anything out
|
-- but doesn't clear anything out
|
||||||
-- (useful for deep overlays and avoiding garbage)
|
-- (useful for deep overlays and avoiding garbage)
|
||||||
function table.copy(t, deep_or_into)
|
function _table.copy(t, deep_or_into)
|
||||||
assert(type(t) == "table", "table.copy - argument 't' must be a table")
|
assert(type(t) == "table", "table.copy - argument 't' must be a table")
|
||||||
local is_bool = type(deep_or_into) == "boolean"
|
local is_bool = type(deep_or_into) == "boolean"
|
||||||
local is_table = type(deep_or_into) == "table"
|
local is_table = type(deep_or_into) == "table"
|
||||||
@ -140,7 +185,7 @@ function table.copy(t, deep_or_into)
|
|||||||
if type(v.copy) == "function" then
|
if type(v.copy) == "function" then
|
||||||
v = v:copy()
|
v = v:copy()
|
||||||
else
|
else
|
||||||
v = table.copy(v, deep)
|
v = _table.copy(v, deep)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
into[k] = v
|
into[k] = v
|
||||||
@ -148,3 +193,45 @@ function table.copy(t, deep_or_into)
|
|||||||
return into
|
return into
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--overlay one table directly onto another, shallow only
|
||||||
|
function _table.overlay(to, from)
|
||||||
|
assert(type(to) == "table", "table.overlay - argument 'to' must be a table")
|
||||||
|
assert(type(from) == "table", "table.overlay - argument 'from' must be a table")
|
||||||
|
for k,v in pairs(from) do
|
||||||
|
to[k] = v
|
||||||
|
end
|
||||||
|
return to
|
||||||
|
end
|
||||||
|
|
||||||
|
--faster unpacking for known-length tables up to 8
|
||||||
|
--gets around nyi in luajit
|
||||||
|
--note: you can use a larger unpack than you need as the rest
|
||||||
|
-- can be discarded, but it "feels dirty" :)
|
||||||
|
|
||||||
|
function _table.unpack2(t)
|
||||||
|
return t[1], t[2],
|
||||||
|
end
|
||||||
|
|
||||||
|
function _table.unpack3(t)
|
||||||
|
return t[1], t[2], t[3]
|
||||||
|
end
|
||||||
|
|
||||||
|
function _table.unpack4(t)
|
||||||
|
return t[1], t[2], t[3], t[4]
|
||||||
|
end
|
||||||
|
|
||||||
|
function _table.unpack5(t)
|
||||||
|
return t[1], t[2], t[3], t[4], t[5]
|
||||||
|
end
|
||||||
|
|
||||||
|
function _table.unpack6(t)
|
||||||
|
return t[1], t[2], t[3], t[4], t[5], t[6]
|
||||||
|
end
|
||||||
|
|
||||||
|
function _table.unpack7(t)
|
||||||
|
return t[1], t[2], t[3], t[4], t[5], t[6], t[7]
|
||||||
|
end
|
||||||
|
|
||||||
|
function _table.unpack8(t)
|
||||||
|
return t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8]
|
||||||
|
end
|
||||||
|
2
vec2.lua
2
vec2.lua
@ -5,7 +5,7 @@
|
|||||||
--[[
|
--[[
|
||||||
notes:
|
notes:
|
||||||
|
|
||||||
depends on a class() function as in oo.lua
|
depends on a class() function as in batteries class.lua
|
||||||
|
|
||||||
some methods depend on math library extensions
|
some methods depend on math library extensions
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user