mirror of
https://github.com/1bardesign/batteries.git
synced 2024-11-22 06:04:35 +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
|
||||
]]
|
||||
|
||||
function class(inherits)
|
||||
return local function class(inherits)
|
||||
local c = {}
|
||||
c.__mt = {__index = c}
|
||||
--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
|
||||
|
||||
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
|
||||
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
|
||||
local _table = BATTERIES_FUNCTIONAL_MODULE or table
|
||||
|
||||
--simple sequential iteration, f is called for all elements of t
|
||||
--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
|
||||
local r = f(v, i)
|
||||
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
|
||||
-- reduce({1, 2, 3}, f, 0) -> f(f(f(0, 1), 2), 3)
|
||||
-- (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
|
||||
o = f(o, v)
|
||||
end
|
||||
@ -44,7 +40,7 @@ end
|
||||
|
||||
--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)
|
||||
function table.map(t, f)
|
||||
function _table.map(t, f)
|
||||
local r = {}
|
||||
for i,v in ipairs(t) do
|
||||
local mapped = f(v, i)
|
||||
@ -55,8 +51,25 @@ function table.map(t, f)
|
||||
return r
|
||||
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
|
||||
function table.filter(t, f)
|
||||
function _table.filter(t, f)
|
||||
local r = {}
|
||||
for i,v in ipairs(t) do
|
||||
if f(v, i) then
|
||||
@ -67,7 +80,7 @@ function table.filter(t, f)
|
||||
end
|
||||
|
||||
--partitions a sequence based on filter criteria
|
||||
function table.partition(t, f)
|
||||
function _table.partition(t, f)
|
||||
local a = {}
|
||||
local b = {}
|
||||
for i,v in ipairs(t) do
|
||||
@ -84,7 +97,7 @@ end
|
||||
--iteration limited by min(#t1, #t2)
|
||||
--function receives arguments (t1, t2, i)
|
||||
--nil results ignored
|
||||
function table.zip(t1, t2, f)
|
||||
function _table.zip(t1, t2, f)
|
||||
local ret = {}
|
||||
local limit = math.min(#t2, #t2)
|
||||
for i=1, limit do
|
||||
@ -101,9 +114,9 @@ end
|
||||
--return a copy of a sequence with all duplicates removed
|
||||
-- causes a little "extra" gc churn; one table and one closure
|
||||
-- as well as the copied deduped table
|
||||
function table.dedupe(t)
|
||||
function _table.dedupe(t)
|
||||
local seen = {}
|
||||
return table.filter(t, function(v)
|
||||
return _table.filter(t, function(v)
|
||||
if seen[v] then
|
||||
return false
|
||||
end
|
||||
@ -113,15 +126,15 @@ function table.dedupe(t)
|
||||
end
|
||||
|
||||
--append sequence t2 into t1, modifying t1
|
||||
function table.append_inplace(t1, t2)
|
||||
table.foreach(t2, function(v)
|
||||
function _table.append_inplace(t1, t2)
|
||||
for i,v in ipairs(t2) do
|
||||
table.insert(t1, v)
|
||||
end)
|
||||
end
|
||||
return t1
|
||||
end
|
||||
|
||||
--return a new sequence with the elements of both t1 and t2
|
||||
function table.append(t1, t2)
|
||||
function _table.append(t1, t2)
|
||||
local r = {}
|
||||
append_inplace(r, t1)
|
||||
append_inplace(r, t2)
|
||||
@ -133,7 +146,7 @@ end
|
||||
-----------------------------------------------------------
|
||||
|
||||
--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
|
||||
if f(v) then
|
||||
return true
|
||||
@ -143,7 +156,7 @@ function table.any(t, f)
|
||||
end
|
||||
|
||||
--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
|
||||
if f(v) then
|
||||
return false
|
||||
@ -153,7 +166,7 @@ function table.none(t, f)
|
||||
end
|
||||
|
||||
--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
|
||||
if not f(v) then
|
||||
return false
|
||||
@ -163,7 +176,7 @@ function table.all(t, f)
|
||||
end
|
||||
|
||||
--counts the elements of t that match f
|
||||
function table.count(t, f)
|
||||
function _table.count(t, f)
|
||||
local c = 0
|
||||
for i,v in ipairs(t) do
|
||||
if f(v) then
|
||||
@ -174,7 +187,7 @@ function table.count(t, f)
|
||||
end
|
||||
|
||||
--true if the table contains element e
|
||||
function table.contains(t, e)
|
||||
function _table.contains(t, e)
|
||||
for i, v in ipairs(t) do
|
||||
if v == e then
|
||||
return true
|
||||
@ -184,48 +197,53 @@ function table.contains(t, e)
|
||||
end
|
||||
|
||||
--return the numeric sum of all elements of t
|
||||
function table.sum(t)
|
||||
return table.reduce(t, function(a, b)
|
||||
function _table.sum(t)
|
||||
return _table.reduce(t, function(a, b)
|
||||
return a + b
|
||||
end, 0)
|
||||
end
|
||||
|
||||
--return the numeric mean of all elements of t
|
||||
function table.mean(t)
|
||||
function _table.mean(t)
|
||||
local len = #t
|
||||
if len == 0 then
|
||||
return 0
|
||||
end
|
||||
return table.sum(t) / len
|
||||
return _table.sum(t) / len
|
||||
end
|
||||
|
||||
--return the minimum and maximum of t in one pass
|
||||
function table.minmax(t)
|
||||
local a = table.reduce(t, function(a, b)
|
||||
a.min = a.min and math.min(a.min, b) or b
|
||||
a.max = a.max and math.max(a.max, b) or b
|
||||
return a
|
||||
end, {})
|
||||
if a.min == nil then
|
||||
a.min = 0
|
||||
a.max = 0
|
||||
--or zero for both if t is empty
|
||||
-- (would perhaps more correctly be math.huge, -math.huge
|
||||
-- but that tends to be surprising/annoying in practice)
|
||||
function _table.minmax(t)
|
||||
local max, min
|
||||
for i,v in ipairs(t) do
|
||||
min = not min and v or math.min(min, v)
|
||||
max = not max and v or math.max(max, v)
|
||||
end
|
||||
return a.min, a.max
|
||||
if min == nil then
|
||||
min = 0
|
||||
max = 0
|
||||
end
|
||||
return min, max
|
||||
end
|
||||
|
||||
function table.max(t)
|
||||
local min, max = table.minmax(t)
|
||||
--return the maximum element of t or zero if t is empty
|
||||
function _table.max(t)
|
||||
local min, max = _table.minmax(t)
|
||||
return max
|
||||
end
|
||||
|
||||
function table.min(t)
|
||||
local min, max = table.minmax(t)
|
||||
--return the minimum element of t or zero if t is empty
|
||||
function _table.min(t)
|
||||
local min, max = _table.minmax(t)
|
||||
return min
|
||||
end
|
||||
|
||||
--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 table.find_best(t, f)
|
||||
function _table.find_best(t, f)
|
||||
local current = nil
|
||||
local current_best = -math.huge
|
||||
for k,e in pairs(t) do
|
||||
@ -239,14 +257,15 @@ function table.find_best(t, f)
|
||||
end
|
||||
|
||||
--return the element of the table that results in the value nearest to the passed value
|
||||
function table.find_nearest(t, f, v)
|
||||
return table.find_best(t, function(e)
|
||||
--todo: optimise as this generates a closure each time
|
||||
function _table.find_nearest(t, f, v)
|
||||
return _table.find_best(t, function(e)
|
||||
return -math.abs(f(e) - v)
|
||||
end)
|
||||
end
|
||||
|
||||
--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
|
||||
if f(v) then
|
||||
return v
|
||||
@ -254,3 +273,5 @@ function table.find_match(t, f)
|
||||
end
|
||||
return nil
|
||||
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
|
||||
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
|
||||
|
||||
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 function relative_file(p)
|
||||
return table.concat({path, p}, ".")
|
||||
local function require_relative(p)
|
||||
return require(table.concat({path, p}, "."))
|
||||
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"))
|
||||
require(relative_file("stable_sort"))
|
||||
local _math = require_relative("math")
|
||||
|
||||
require(relative_file("functional"))
|
||||
sequence = require(relative_file("sequence"))
|
||||
unique_mapping = require(relative_file("unique_mapping"))
|
||||
local _table = require_relative("table")
|
||||
local _stable_sort = require_relative("stable_sort")
|
||||
|
||||
vec2 = require(relative_file("vec2"))
|
||||
vec3 = require(relative_file("vec3"))
|
||||
intersect = require(relative_file("intersect"))
|
||||
local _functional = require_relative("functional")
|
||||
local _sequence = require_relative("sequence")
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
function math.wrap(v, lo, hi)
|
||||
function _math.wrap(v, lo, hi)
|
||||
local range = hi - lo
|
||||
local relative = v - lo
|
||||
local relative_wrapped = relative % range
|
||||
@ -15,51 +19,51 @@ function math.wrap(v, lo, hi)
|
||||
end
|
||||
|
||||
--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))
|
||||
end
|
||||
|
||||
function math.clamp01(v)
|
||||
return math.clamp(v, 0, 1)
|
||||
function _math.clamp01(v)
|
||||
return _math.clamp(v, 0, 1)
|
||||
end
|
||||
|
||||
--round v to nearest whole
|
||||
function math.round(v)
|
||||
function _math.round(v)
|
||||
return math.floor(v + 0.5)
|
||||
end
|
||||
|
||||
--round v to one-in x
|
||||
-- (eg x = 2, v rounded to increments of 0.5)
|
||||
function math.to_one_in(v, x)
|
||||
return math.round(v * x) / x
|
||||
function _math.to_one_in(v, x)
|
||||
return _math.round(v * x) / x
|
||||
end
|
||||
|
||||
--round v to a given decimal precision
|
||||
function math.to_precision(v, decimal_points)
|
||||
return math.to_one_in(v, math.pow(10, decimal_points))
|
||||
function _math.to_precision(v, decimal_points)
|
||||
return _math.to_one_in(v, math.pow(10, decimal_points))
|
||||
end
|
||||
|
||||
--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
|
||||
return 0
|
||||
end
|
||||
|
||||
--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
|
||||
end
|
||||
|
||||
--classic smoothstep
|
||||
--(only "safe" for 0-1 range)
|
||||
function math.smoothstep(v)
|
||||
function _math.smoothstep(v)
|
||||
return v * v * (3 - 2 * v)
|
||||
end
|
||||
|
||||
--classic smootherstep; zero 2nd order derivatives at 0 and 1
|
||||
--(only safe for 0-1 range)
|
||||
function math.smootherstep(v)
|
||||
function _math.smootherstep(v)
|
||||
return v * v * v * (v * (v * 6 - 15) + 10)
|
||||
end
|
||||
|
||||
@ -125,7 +129,7 @@ local sparse_primes_1k = {
|
||||
6689, 7039, 7307, 7559, 7573, 7919,
|
||||
}
|
||||
|
||||
function math.first_above(v, t)
|
||||
function _math.first_above(v, t)
|
||||
for _,p in ipairs(t) do
|
||||
if p > v then
|
||||
return p
|
||||
@ -134,79 +138,30 @@ function math.first_above(v, t)
|
||||
return t[#t]
|
||||
end
|
||||
|
||||
function math.next_prime_1k(v)
|
||||
return math.first_above(v, primes_1k)
|
||||
function _math.next_prime_1k(v)
|
||||
return _math.first_above(v, primes_1k)
|
||||
end
|
||||
|
||||
function math.next_prime_1k_sparse(v)
|
||||
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
|
||||
function _math.next_prime_1k_sparse(v)
|
||||
return _math.first_above(v, sparse_primes_1k)
|
||||
end
|
||||
|
||||
--angle handling stuff
|
||||
function math.normalise_angle(a)
|
||||
return math.wrap(a, -math.pi, math.pi)
|
||||
function _math.normalise_angle(a)
|
||||
return _math.wrap(a, -math.pi, math.pi)
|
||||
end
|
||||
|
||||
function math.relative_angle(a1, a2)
|
||||
a1 = math.normalise_angle(a1)
|
||||
a2 = math.normalise_angle(a2)
|
||||
return math.normalise_angle(a1 - a2)
|
||||
function _math.relative_angle(a1, a2)
|
||||
a1 = _math.normalise_angle(a1)
|
||||
a2 = _math.normalise_angle(a2)
|
||||
return _math.normalise_angle(a1 - a2)
|
||||
end
|
||||
|
||||
--geometric rotation multi-return
|
||||
function math.rotate(x, y, r)
|
||||
function _math.rotate(x, y, r)
|
||||
local s = math.sin(r)
|
||||
local c = math.cos(r)
|
||||
return c * x - s * y, s * x + c * y
|
||||
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).
|
||||
|
||||
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
|
||||
first as method calls.
|
||||
]]
|
||||
|
||||
local sequence = {}
|
||||
|
||||
sequence.mt = {__index = sequence}
|
||||
@ -20,52 +21,59 @@ function sequence:new(t)
|
||||
return setmetatable(t or {}, sequence.mt)
|
||||
end
|
||||
|
||||
--import table functions to sequence as-is
|
||||
sequence.join = table.concat --alias
|
||||
--alias
|
||||
sequence.join = table.concat
|
||||
|
||||
--sorting default to stable if present
|
||||
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()
|
||||
return sequence:new(table.keys(self))
|
||||
return sequence:new(_func.keys(self))
|
||||
end
|
||||
|
||||
function sequence:values()
|
||||
return sequence:new(table.values(self))
|
||||
return sequence:new(_func.values(self))
|
||||
end
|
||||
|
||||
function sequence:foreach(f)
|
||||
return table.foreach(self, f)
|
||||
return _func.foreach(self, f)
|
||||
end
|
||||
|
||||
function sequence:reduce(f, o)
|
||||
return table.foreach(self, f, o)
|
||||
return _func.foreach(self, f, o)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
function sequence:filter(f)
|
||||
return sequence:new(table.filter(self, f))
|
||||
return sequence:new(_func.filter(self, f))
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
function sequence:zip(other, f)
|
||||
return sequence:new(table.zip(self, other, f))
|
||||
return sequence:new(_func.zip(self, other, f))
|
||||
end
|
||||
|
||||
function sequence:dedupe()
|
||||
return table.dedupe(self)
|
||||
return _func.dedupe(self)
|
||||
end
|
||||
|
||||
function sequence:append_inplace(other)
|
||||
return table.append_inplace(self, other)
|
||||
return _func.append_inplace(self, other)
|
||||
end
|
||||
|
||||
function sequence:append(other)
|
||||
@ -73,7 +81,7 @@ function sequence:append(other)
|
||||
end
|
||||
|
||||
function sequence:copy(deep)
|
||||
return sequence:new(table.copy(self, deep))
|
||||
return sequence:new(_func.copy(self, deep))
|
||||
end
|
||||
|
||||
return sequence
|
@ -1,6 +1,6 @@
|
||||
-- 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.
|
||||
--
|
||||
-- table.stable_sort
|
||||
@ -10,6 +10,11 @@
|
||||
-- table.insertion_sort
|
||||
-- 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
|
||||
--license as follows:
|
||||
@ -36,14 +41,15 @@
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
]]
|
||||
|
||||
-- (modifications by Max Cahill 2018)
|
||||
-- (modifications by Max Cahill 2018, 2020)
|
||||
|
||||
local _sort_core = {}
|
||||
|
||||
--tunable size for
|
||||
_sort_core.max_chunk_size = 24
|
||||
--tunable size for insertion sort "bottom out"
|
||||
_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
|
||||
local k = first
|
||||
local v = array[i]
|
||||
@ -59,7 +65,8 @@ function _sort_core.insertion_sort_impl(array, first, last, less)
|
||||
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
|
||||
i = 1
|
||||
-- copy first half of array to auxiliary array
|
||||
@ -91,24 +98,25 @@ function _sort_core.merge(array, workspace, low, middle, high, less)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _sort_core.merge_sort_impl(array, workspace, low, high, less)
|
||||
--implementation for the merge sort
|
||||
function _sort_core._merge_sort_impl(array, workspace, low, high, less)
|
||||
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
|
||||
local middle = math.floor((low + high) / 2)
|
||||
_sort_core.merge_sort_impl(array, workspace, low, middle, less)
|
||||
_sort_core.merge_sort_impl(array, workspace, middle + 1, high, less)
|
||||
_sort_core.merge(array, workspace, low, middle, high, 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(array, workspace, low, middle, high, less)
|
||||
end
|
||||
end
|
||||
|
||||
function default_less(a, b)
|
||||
--default comparison; hoisted for clarity
|
||||
local function default_less(a, b)
|
||||
return a < b
|
||||
end
|
||||
|
||||
--inline common setup stuff
|
||||
function _sort_core.sort_setup(array, less)
|
||||
function _sort_core._sort_setup(array, less)
|
||||
--default less
|
||||
less = less or default_less
|
||||
--
|
||||
@ -127,28 +135,35 @@ end
|
||||
|
||||
function _sort_core.stable_sort(array, less)
|
||||
--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
|
||||
--temp storage; allocate ahead of time
|
||||
local workspace = {}
|
||||
local middle = math.ceil(n / 2)
|
||||
workspace[middle] = array[1]
|
||||
--dive in
|
||||
_sort_core.merge_sort_impl( array, workspace, 1, n, less )
|
||||
_sort_core._merge_sort_impl( array, workspace, 1, n, less )
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
function _sort_core.insertion_sort(array, less)
|
||||
--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
|
||||
_sort_core.insertion_sort_impl(array, 1, n, less)
|
||||
_sort_core._insertion_sort_impl(array, 1, n, less)
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
_sort_core.unstable_sort = table.sort
|
||||
|
||||
--export sort core
|
||||
table.insertion_sort = _sort_core.insertion_sort
|
||||
table.stable_sort = _sort_core.stable_sort
|
||||
table.unstable_sort = table.sort
|
||||
|
||||
local _table = BATTERIES_SORT_MODULE or BATTERIES_TABLE_MODULE or table
|
||||
|
||||
_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
|
||||
|
||||
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
|
||||
function table.back(t)
|
||||
function _table.back(t)
|
||||
return t[#t]
|
||||
end
|
||||
|
||||
--remove the back element of a table and return it
|
||||
function table.pop(t)
|
||||
function _table.pop(t)
|
||||
return table.remove(t)
|
||||
end
|
||||
|
||||
--insert to the back of a table
|
||||
function table.push(t, v)
|
||||
function _table.push(t, v)
|
||||
return table.insert(t, v)
|
||||
end
|
||||
|
||||
--remove the front element of a table and return it
|
||||
function table.shift(t)
|
||||
function _table.shift(t)
|
||||
return table.remove(t, 1)
|
||||
end
|
||||
|
||||
--insert to the front of a table
|
||||
function table.unshift(t, v)
|
||||
function _table.unshift(t, v)
|
||||
return table.insert(t, 1, v)
|
||||
end
|
||||
|
||||
--find the index in a sequential table that a resides at
|
||||
--or nil if nothing was found
|
||||
--(todo: consider pairs version?)
|
||||
function table.index_of(t, a)
|
||||
function _table.index_of(t, a)
|
||||
if a == nil then return nil end
|
||||
for i,b in ipairs(t) do
|
||||
if a == b then
|
||||
@ -42,8 +58,8 @@ end
|
||||
|
||||
--remove the first instance of value from a table (linear search)
|
||||
--returns true if the value was removed, else false
|
||||
function table.remove_value(t, a)
|
||||
local i = table.index_of(t, a)
|
||||
function _table.remove_value(t, a)
|
||||
local i = _table.index_of(t, a)
|
||||
if i then
|
||||
table.remove(t, i)
|
||||
return true
|
||||
@ -53,8 +69,8 @@ end
|
||||
|
||||
--add a value to a table if it doesn't already exist (linear search)
|
||||
--returns true if the value was added, else false
|
||||
function table.add_value(t, a)
|
||||
local i = table.index_of(t, a)
|
||||
function _table.add_value(t, a)
|
||||
local i = _table.index_of(t, a)
|
||||
if not i then
|
||||
table.insert(t, a)
|
||||
return true
|
||||
@ -70,7 +86,7 @@ local function _random(min, max, r)
|
||||
end
|
||||
|
||||
--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
|
||||
return nil
|
||||
end
|
||||
@ -78,7 +94,7 @@ function table.pick_random(t, r)
|
||||
end
|
||||
|
||||
--shuffle the order of a table
|
||||
function table.shuffle(t, r)
|
||||
function _table.shuffle(t, r)
|
||||
for i = 1, #t do
|
||||
local j = _random(1, #t, r)
|
||||
t[i], t[j] = t[j], t[i]
|
||||
@ -87,7 +103,7 @@ function table.shuffle(t, r)
|
||||
end
|
||||
|
||||
--reverse the order of a table
|
||||
function table.reverse(t)
|
||||
function _table.reverse(t)
|
||||
for i = 1, #t / 2 do
|
||||
local j = #t - i + 1
|
||||
t[i], t[j] = t[j], t[i]
|
||||
@ -95,28 +111,57 @@ function table.reverse(t)
|
||||
return t
|
||||
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)
|
||||
if table.clear == nil then
|
||||
--destructively clear a numerically keyed table
|
||||
--useful when multiple references are floating around
|
||||
--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")
|
||||
while t[1] ~= nil do
|
||||
table.remove(t)
|
||||
if _table.clear == nil then
|
||||
if _table ~= table and table.clear then
|
||||
--import from global if it exists
|
||||
_table.clear = table.clear
|
||||
else
|
||||
--remove all values from a table
|
||||
--useful when multiple references are floating around
|
||||
--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
|
||||
|
||||
--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
|
||||
--note:
|
||||
-- copies and overlays are currently not satisfactory
|
||||
--
|
||||
-- i feel that copy especially tries to do too much and
|
||||
-- probably they should be split into separate functions
|
||||
-- to be both more explicit and performant, ie
|
||||
--
|
||||
-- shallow_copy, deep_copy, shallow_overlay, deep_overlay
|
||||
--
|
||||
-- input is welcome on this :)
|
||||
|
||||
--copy a table
|
||||
-- deep_or_into is either:
|
||||
@ -128,7 +173,7 @@ end
|
||||
-- if into specified, copies into that table
|
||||
-- but doesn't clear anything out
|
||||
-- (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")
|
||||
local is_bool = type(deep_or_into) == "boolean"
|
||||
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
|
||||
v = v:copy()
|
||||
else
|
||||
v = table.copy(v, deep)
|
||||
v = _table.copy(v, deep)
|
||||
end
|
||||
end
|
||||
into[k] = v
|
||||
@ -148,3 +193,45 @@ function table.copy(t, deep_or_into)
|
||||
return into
|
||||
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:
|
||||
|
||||
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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user