mirror of
https://github.com/1bardesign/batteries.git
synced 2024-11-25 23:24:35 +00:00
[modified] big refactor to localised modules and require("batteries"):export()
for global usage; renamed files table
to tablex
and math
to mathx
to allow using the library in your module root if you really want to.
This commit is contained in:
parent
4bbbdf86d6
commit
6386aaf298
10
async.lua
10
async.lua
@ -14,14 +14,16 @@
|
||||
cancelling
|
||||
]]
|
||||
|
||||
local async = {}
|
||||
async._mt = {__index = async}
|
||||
local path = (...):gsub("async", "")
|
||||
local class = require(path .. "class")
|
||||
|
||||
local async = class()
|
||||
|
||||
function async:new()
|
||||
return setmetatable({
|
||||
return self:init({
|
||||
tasks = {},
|
||||
tasks_stalled = {},
|
||||
}, self._mt)
|
||||
})
|
||||
end
|
||||
|
||||
--add a task to the kernel
|
||||
|
@ -1,7 +1,7 @@
|
||||
--[[
|
||||
colour handling stuff
|
||||
|
||||
feel free to alias to colour
|
||||
feel free to alias to `color` :)
|
||||
]]
|
||||
|
||||
local bit = require("bit")
|
||||
|
@ -9,17 +9,18 @@
|
||||
|
||||
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
|
||||
]]
|
||||
|
||||
local _table = BATTERIES_FUNCTIONAL_MODULE or table
|
||||
local path = (...):gsub("functional", "")
|
||||
local tablex = require(path .. "tablex")
|
||||
|
||||
local functional = setmetatable({}, {
|
||||
__index = tablex,
|
||||
})
|
||||
|
||||
--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 functional.foreach(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
local r = f(v, i)
|
||||
if r ~= nil then
|
||||
@ -31,7 +32,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 functional.reduce(t, f, o)
|
||||
for i,v in ipairs(t) do
|
||||
o = f(o, v)
|
||||
end
|
||||
@ -40,7 +41,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 functional.map(t, f)
|
||||
local r = {}
|
||||
for i,v in ipairs(t) do
|
||||
local mapped = f(v, i)
|
||||
@ -54,7 +55,7 @@ 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)
|
||||
function functional.remap(t, f)
|
||||
local i = 1
|
||||
while i <= #t do
|
||||
local mapped = f(t[i])
|
||||
@ -69,7 +70,7 @@ function _table.remap(t, f)
|
||||
end
|
||||
|
||||
--filters a sequence
|
||||
function _table.filter(t, f)
|
||||
function functional.filter(t, f)
|
||||
local r = {}
|
||||
for i,v in ipairs(t) do
|
||||
if f(v, i) then
|
||||
@ -80,7 +81,7 @@ function _table.filter(t, f)
|
||||
end
|
||||
|
||||
--partitions a sequence based on filter criteria
|
||||
function _table.partition(t, f)
|
||||
function functional.partition(t, f)
|
||||
local a = {}
|
||||
local b = {}
|
||||
for i,v in ipairs(t) do
|
||||
@ -97,7 +98,7 @@ end
|
||||
--iteration limited by min(#t1, #t2)
|
||||
--function receives arguments (t1, t2, i)
|
||||
--nil results ignored
|
||||
function _table.zip(t1, t2, f)
|
||||
function functional.zip(t1, t2, f)
|
||||
local ret = {}
|
||||
local limit = math.min(#t2, #t2)
|
||||
for i=1, limit do
|
||||
@ -114,9 +115,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 functional.dedupe(t)
|
||||
local seen = {}
|
||||
return _table.filter(t, function(v)
|
||||
return functional.filter(t, function(v)
|
||||
if seen[v] then
|
||||
return false
|
||||
end
|
||||
@ -126,7 +127,7 @@ function _table.dedupe(t)
|
||||
end
|
||||
|
||||
--append sequence t2 into t1, modifying t1
|
||||
function _table.append_inplace(t1, t2)
|
||||
function functional.append_inplace(t1, t2)
|
||||
for i,v in ipairs(t2) do
|
||||
table.insert(t1, v)
|
||||
end
|
||||
@ -134,10 +135,10 @@ function _table.append_inplace(t1, t2)
|
||||
end
|
||||
|
||||
--return a new sequence with the elements of both t1 and t2
|
||||
function _table.append(t1, t2)
|
||||
function functional.append(t1, t2)
|
||||
local r = {}
|
||||
append_inplace(r, t1)
|
||||
append_inplace(r, t2)
|
||||
functional.append_inplace(r, t1)
|
||||
functional.append_inplace(r, t2)
|
||||
return r
|
||||
end
|
||||
|
||||
@ -146,7 +147,7 @@ end
|
||||
-----------------------------------------------------------
|
||||
|
||||
--true if any element of the table matches f
|
||||
function _table.any(t, f)
|
||||
function functional.any(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
if f(v) then
|
||||
return true
|
||||
@ -156,7 +157,7 @@ function _table.any(t, f)
|
||||
end
|
||||
|
||||
--true if no element of the table matches f
|
||||
function _table.none(t, f)
|
||||
function functional.none(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
if f(v) then
|
||||
return false
|
||||
@ -166,7 +167,7 @@ function _table.none(t, f)
|
||||
end
|
||||
|
||||
--true if all elements of the table match f
|
||||
function _table.all(t, f)
|
||||
function functional.all(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
if not f(v) then
|
||||
return false
|
||||
@ -176,7 +177,7 @@ function _table.all(t, f)
|
||||
end
|
||||
|
||||
--counts the elements of t that match f
|
||||
function _table.count(t, f)
|
||||
function functional.count(t, f)
|
||||
local c = 0
|
||||
for i,v in ipairs(t) do
|
||||
if f(v) then
|
||||
@ -187,7 +188,7 @@ function _table.count(t, f)
|
||||
end
|
||||
|
||||
--true if the table contains element e
|
||||
function _table.contains(t, e)
|
||||
function functional.contains(t, e)
|
||||
for i, v in ipairs(t) do
|
||||
if v == e then
|
||||
return true
|
||||
@ -197,26 +198,26 @@ 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 functional.sum(t)
|
||||
return functional.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 functional.mean(t)
|
||||
local len = #t
|
||||
if len == 0 then
|
||||
return 0
|
||||
end
|
||||
return _table.sum(t) / len
|
||||
return functional.sum(t) / len
|
||||
end
|
||||
|
||||
--return the minimum and maximum of t in one pass
|
||||
--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)
|
||||
function functional.minmax(t)
|
||||
local max, min
|
||||
for i,v in ipairs(t) do
|
||||
min = not min and v or math.min(min, v)
|
||||
@ -230,26 +231,26 @@ function _table.minmax(t)
|
||||
end
|
||||
|
||||
--return the maximum element of t or zero if t is empty
|
||||
function _table.max(t)
|
||||
local min, max = _table.minmax(t)
|
||||
function functional.max(t)
|
||||
local min, max = functional.minmax(t)
|
||||
return max
|
||||
end
|
||||
|
||||
--return the minimum element of t or zero if t is empty
|
||||
function _table.min(t)
|
||||
local min, max = _table.minmax(t)
|
||||
function functional.min(t)
|
||||
local min, max = functional.minmax(t)
|
||||
return min
|
||||
end
|
||||
|
||||
--return the element of the table that results in the lowest numeric value
|
||||
--(function receives element and index respectively)
|
||||
function _table.find_min(t, f)
|
||||
function functional.find_min(t, f)
|
||||
local current = nil
|
||||
local current_best = math.huge
|
||||
for i, v in ipairs(t) do
|
||||
v = f(v, i)
|
||||
if v and v < current_best then
|
||||
current_best = v
|
||||
local current_min = math.huge
|
||||
for i, e in ipairs(t) do
|
||||
local v = f(e, i)
|
||||
if v and v < current_min then
|
||||
current_min = v
|
||||
current = e
|
||||
end
|
||||
end
|
||||
@ -258,13 +259,13 @@ end
|
||||
|
||||
--return the element of the table that results in the greatest numeric value
|
||||
--(function receives element and index respectively)
|
||||
function _table.find_max(t, f)
|
||||
function functional.find_max(t, f)
|
||||
local current = nil
|
||||
local current_best = -math.huge
|
||||
for i, v in ipairs(t) do
|
||||
v = f(v, i)
|
||||
if v and v > current_best then
|
||||
current_best = v
|
||||
local current_max = -math.huge
|
||||
for i, e in ipairs(t) do
|
||||
local v = f(e, i)
|
||||
if v and v > current_max then
|
||||
current_max = v
|
||||
current = e
|
||||
end
|
||||
end
|
||||
@ -272,18 +273,18 @@ function _table.find_max(t, f)
|
||||
end
|
||||
|
||||
--alias
|
||||
_table.find_best = _table.find_max
|
||||
functional.find_best = functional.find_max
|
||||
|
||||
--return the element of the table that results in the value nearest to the passed value
|
||||
--todo: optimise as this generates a closure each time
|
||||
function _table.find_nearest(t, f, v)
|
||||
return _table.find_best(t, function(e)
|
||||
function functional.find_nearest(t, f, v)
|
||||
return functional.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 functional.find_match(t, f)
|
||||
for i,v in ipairs(t) do
|
||||
if f(v) then
|
||||
return v
|
||||
@ -292,4 +293,4 @@ function _table.find_match(t, f)
|
||||
return nil
|
||||
end
|
||||
|
||||
return _table
|
||||
return functional
|
||||
|
87
init.lua
87
init.lua
@ -24,18 +24,11 @@ local function require_relative(p)
|
||||
return require(table.concat({path, p}, "."))
|
||||
end
|
||||
|
||||
if BATTERIES_NO_GLOBALS then
|
||||
--define local tables for everything to go into
|
||||
BATTERIES_MATH_MODULE = {}
|
||||
BATTERIES_TABLE_MODULE = {}
|
||||
BATTERIES_FUNCTIONAL_MODULE = {}
|
||||
end
|
||||
|
||||
local _class = require_relative("class")
|
||||
|
||||
local _math = require_relative("math")
|
||||
local _mathx = require_relative("mathx")
|
||||
|
||||
local _table = require_relative("table")
|
||||
local _tablex = require_relative("tablex")
|
||||
local _stable_sort = require_relative("stable_sort")
|
||||
|
||||
local _functional = require_relative("functional")
|
||||
@ -54,15 +47,63 @@ local _manual_gc = require_relative("manual_gc")
|
||||
|
||||
local _colour = require_relative("colour")
|
||||
|
||||
--export globally if required
|
||||
if not BATTERIES_NO_GLOBALS then
|
||||
--build the module
|
||||
local _batteries = {
|
||||
--fire and forget mode function
|
||||
export = export,
|
||||
--
|
||||
class = _class,
|
||||
--support x and non-x naming
|
||||
math = _mathx,
|
||||
mathx = _mathx,
|
||||
--
|
||||
table = _tablex,
|
||||
tablex = _tablex,
|
||||
--sorting routines
|
||||
stable_sort = _stable_sort,
|
||||
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,
|
||||
color = _colour,
|
||||
}
|
||||
|
||||
--easy export globally if required
|
||||
function _batteries:export(self)
|
||||
--export oo
|
||||
class = _class
|
||||
|
||||
--overlay tablex and functional and sort routines onto table
|
||||
_tablex.overlay(table, _tablex)
|
||||
_tablex.overlay(table, _functional)
|
||||
_stable_sort:export()
|
||||
|
||||
--functional module also available separate from table
|
||||
functional = _functional
|
||||
|
||||
--export sequence
|
||||
sequence = _sequence
|
||||
|
||||
--overlay onto math
|
||||
_tablex.overlay(math, _mathx)
|
||||
|
||||
--export geom
|
||||
vec2 = _vec2
|
||||
vec3 = _vec3
|
||||
intersect = _intersect
|
||||
|
||||
--misc :)
|
||||
unique_mapping = _unique_mapping
|
||||
state_machine = _state_machine
|
||||
async = _async
|
||||
@ -71,23 +112,11 @@ if not BATTERIES_NO_GLOBALS then
|
||||
--support both spellings
|
||||
colour = _colour
|
||||
color = _colour
|
||||
|
||||
--export top level module as well for ease of migration for code
|
||||
batteries = _batteries
|
||||
|
||||
return self
|
||||
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,
|
||||
color = _colour,
|
||||
}
|
||||
return _batteries
|
||||
|
@ -1,15 +1,13 @@
|
||||
--[[
|
||||
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 _math = BATTERIES_MATH_MODULE or math
|
||||
local mathx = setmetatable({}, {
|
||||
__index = math,
|
||||
})
|
||||
|
||||
--wrap v around range [lo, hi)
|
||||
function _math.wrap(v, lo, hi)
|
||||
function mathx.wrap(v, lo, hi)
|
||||
local range = hi - lo
|
||||
local relative = v - lo
|
||||
local relative_wrapped = relative % range
|
||||
@ -19,46 +17,47 @@ function _math.wrap(v, lo, hi)
|
||||
end
|
||||
|
||||
--clamp v to range [lo, hi]
|
||||
function _math.clamp(v, lo, hi)
|
||||
function mathx.clamp(v, lo, hi)
|
||||
return math.max(lo, math.min(v, hi))
|
||||
end
|
||||
|
||||
function _math.clamp01(v)
|
||||
return _math.clamp(v, 0, 1)
|
||||
--clamp v to range [0, 1]
|
||||
function mathx.clamp01(v)
|
||||
return mathx.clamp(v, 0, 1)
|
||||
end
|
||||
|
||||
--round v to nearest whole
|
||||
function _math.round(v)
|
||||
function mathx.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 mathx.to_one_in(v, x)
|
||||
return mathx.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 mathx.to_precision(v, decimal_points)
|
||||
return mathx.to_one_in(v, math.pow(10, decimal_points))
|
||||
end
|
||||
|
||||
--0, 1, -1 sign of a scalar
|
||||
function _math.sign(v)
|
||||
function mathx.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 mathx.lerp(a, b, t)
|
||||
return a * (1.0 - t) + b * t
|
||||
end
|
||||
|
||||
--linear interpolation with a minimum "final step" distance
|
||||
--useful for making sure dynamic lerps do actually reach their final destination
|
||||
function _math.lerp_eps(a, b, t, eps)
|
||||
local v = math.lerp(a, b, t)
|
||||
function mathx.lerp_eps(a, b, t, eps)
|
||||
local v = mathx.lerp(a, b, t)
|
||||
if math.abs(v - b) < eps then
|
||||
v = b
|
||||
end
|
||||
@ -67,13 +66,13 @@ end
|
||||
|
||||
--classic smoothstep
|
||||
--(only "safe" for 0-1 range)
|
||||
function _math.smoothstep(v)
|
||||
function mathx.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 mathx.smootherstep(v)
|
||||
return v * v * v * (v * (v * 6 - 15) + 10)
|
||||
end
|
||||
|
||||
@ -139,7 +138,7 @@ local sparse_primes_1k = {
|
||||
6689, 7039, 7307, 7559, 7573, 7919,
|
||||
}
|
||||
|
||||
function _math.first_above(v, t)
|
||||
function mathx.first_above(v, t)
|
||||
for _,p in ipairs(t) do
|
||||
if p > v then
|
||||
return p
|
||||
@ -148,30 +147,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 mathx.next_prime_1k(v)
|
||||
return mathx.first_above(v, primes_1k)
|
||||
end
|
||||
|
||||
function _math.next_prime_1k_sparse(v)
|
||||
return _math.first_above(v, sparse_primes_1k)
|
||||
function mathx.next_prime_1k_sparse(v)
|
||||
return mathx.first_above(v, sparse_primes_1k)
|
||||
end
|
||||
|
||||
--angle handling stuff
|
||||
function _math.normalise_angle(a)
|
||||
return _math.wrap(a, -math.pi, math.pi)
|
||||
function mathx.normalise_angle(a)
|
||||
return mathx.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 mathx.relative_angle(a1, a2)
|
||||
a1 = mathx.normalise_angle(a1)
|
||||
a2 = mathx.normalise_angle(a2)
|
||||
return mathx.normalise_angle(a1 - a2)
|
||||
end
|
||||
|
||||
--geometric rotation multi-return
|
||||
function _math.rotate(x, y, r)
|
||||
function mathx.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
|
||||
return mathx
|
36
readme.md
36
readme.md
@ -4,30 +4,30 @@ Core dependencies for making games with lua, especially with [love](https://love
|
||||
|
||||
Does a lot to get projects off the ground faster, filling out lua's sparse standard library a little and providing implementations of common algorithms and data structures useful for games.
|
||||
|
||||
It's a bit of a grab bag of functionality, but quite extensively documented, and currently still under a hundred kb uncompressed, including the license and readme, so you get quite a lot per byte! Of course, feel free to trim it down for your use case as required. Many of the modules are "mostly" standalone.
|
||||
It's a bit of a grab bag of functionality, but quite extensively documented, and currently still under a hundred kb uncompressed, including the license and readme, so you get quite a lot per byte! Of course, feel free to trim it down for your use case as required (see [below](#stripping-down-batteries).
|
||||
|
||||
# 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, as a bonus, faster than table.sort under luajit.
|
||||
- `mathx` - Mathematical extensions. Alias `math`.
|
||||
- `tablex` - Table handling extensions. Alias `table`.
|
||||
- `stable_sort` - A stable sorting algorithm that is also, as a bonus, often 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 chaining the functional interface above.
|
||||
- `vec2` - 2d vectors with method chaining, garbage saving interface. A bit of a mouthful at times.
|
||||
- `sequence` - An oo wrapper on sequential tables, so you can do `t:insert(i, v)` instead of `table.insert(t, i, v)`. Also supports method chaining for the `functional` interface above, which can save a lot of typing!
|
||||
- `vec2` - 2d vectors with method chaining, garbage saving interface. A bit of a mouthful at times, but you get used to it.
|
||||
- `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; no more spikes. 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.
|
||||
- `colour` - Colour conversion routines. Alias `color`.
|
||||
|
||||
# Todo/WIP list
|
||||
|
||||
Endless, of course :)
|
||||
|
||||
- `string` - As for table and math, would be good to have a more filled out string handling API.
|
||||
- `string` - As for tablex and mathx, would be good to have a more filled out string handling API.
|
||||
- `colour` - Bidirectional hsv conversion and friends would fit nicely here.
|
||||
- Geometry:
|
||||
- `vec3` - Needs more fleshing out for serious use.
|
||||
@ -50,23 +50,25 @@ Pull requests are welcome for anything!
|
||||
|
||||
If you have something "big" to contribute please get in touch before starting work so we can make sure it fits, but I'm quite open minded!
|
||||
|
||||
# Globals?
|
||||
# Export 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.
|
||||
You are strongly encouraged to use the library in a "fire and forget" manner through `require("batteries"):export()` (or whatever appropriate module path), which will modify builtin lua modules (such as `table` and `math`) and expose all the modules directly as globals for convenience.
|
||||
|
||||
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.
|
||||
This eases consumption later on - you don't have to remember if say, `table.remove_value` is built in to lua or not, or get used to accessing the builtin table functions through `batteries.table` or `tablex`.
|
||||
|
||||
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!
|
||||
While this will likely sit badly with anyone who's had "no globals!" hammered into them, I believe for `batteries` (and many foundational libraries) it makes sense to just import once at boot. You're going to be pulling it in almost everywhere anyway; why bother making yourself jump through more hoops.
|
||||
|
||||
Some folks will have good reasons, which is why the functionality is present!
|
||||
You can of course use the separate modules on their own, either with a single require for all of `batteries`, and use through something like `batteries.functional.map`, or requiring individual modules explicitly. This more careful approach _will_ let you be more clear about your dependencies, at the cost of more setup work needing to re-require batteries everywhere, or expose it as a global in the first place.
|
||||
|
||||
Others may wish to reconsider, and save themselves typing `batteries` a few hundred times :)
|
||||
I'd strongly recommend that if you find yourself frustrated with 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! You may wish to reconsider, and save yourself typing `batteries` a few hundred times :)
|
||||
|
||||
# Why aren't various types using `class`?
|
||||
# Stripping down `batteries`
|
||||
|
||||
To avoid a dependency on class.lua for those modules when used in isolation.
|
||||
Many of the modules "just work" on their own if you just want to vendor in something specific.
|
||||
|
||||
Makes them a little bit more of a pain internally and requires more lines of code, but means you can vendor in just the code you need with very little effort.
|
||||
There are some inter-dependencies in the more complex modules, which should be straightforward to detect and figure out the best course of action (include or strip out) if you want to make a stripped-down version for distribution.
|
||||
|
||||
Currently the lib is 30kb or so compressed, including the readme.
|
||||
|
||||
# License
|
||||
|
||||
|
42
sequence.lua
42
sequence.lua
@ -8,17 +8,18 @@
|
||||
first as method calls.
|
||||
]]
|
||||
|
||||
local sequence = {}
|
||||
|
||||
sequence._mt = {__index = sequence}
|
||||
local path = (...):gsub("sequence", "")
|
||||
local class = require(path .. "class")
|
||||
local table = require(path .. "tablex") --shadow global table module
|
||||
local functional = require(path .. "functional")
|
||||
|
||||
local sequence = class()
|
||||
--proxy missing table fns to global table api
|
||||
sequence.__mt = {__index = table}
|
||||
setmetatable(sequence, sequence.__mt)
|
||||
setmetatable(sequence, {__index = table})
|
||||
|
||||
--upgrade a table into a sequence, or create a new sequence
|
||||
function sequence:new(t)
|
||||
return setmetatable(t or {}, sequence._mt)
|
||||
return self:init(t or {})
|
||||
end
|
||||
|
||||
--alias
|
||||
@ -27,53 +28,50 @@ sequence.join = table.concat
|
||||
--sorting default to stable if present
|
||||
sequence.sort = table.stable_sort or table.sort
|
||||
|
||||
--(handle functional module delegation correctly)
|
||||
local _func = BATTERIES_FUNCTIONAL_MODULE or table
|
||||
|
||||
--import functional interface to sequence in a type preserving way
|
||||
--import functional interface to sequence in a type-preserving way, for method chaining
|
||||
function sequence:keys()
|
||||
return sequence:new(_func.keys(self))
|
||||
return sequence:new(functional.keys(self))
|
||||
end
|
||||
|
||||
function sequence:values()
|
||||
return sequence:new(_func.values(self))
|
||||
return sequence:new(functional.values(self))
|
||||
end
|
||||
|
||||
function sequence:foreach(f)
|
||||
return _func.foreach(self, f)
|
||||
return functional.foreach(self, f)
|
||||
end
|
||||
|
||||
function sequence:reduce(f, o)
|
||||
return _func.foreach(self, f, o)
|
||||
return functional.foreach(self, f, o)
|
||||
end
|
||||
|
||||
function sequence:map(f)
|
||||
return sequence:new(_func.map(self, f))
|
||||
return sequence:new(functional.map(self, f))
|
||||
end
|
||||
|
||||
function sequence:remap(f)
|
||||
return _func.remap(self, f)
|
||||
return functional.remap(self, f)
|
||||
end
|
||||
|
||||
function sequence:filter(f)
|
||||
return sequence:new(_func.filter(self, f))
|
||||
return sequence:new(functional.filter(self, f))
|
||||
end
|
||||
|
||||
function sequence:partition(f)
|
||||
local a, b = _func.partition(self, f)
|
||||
local a, b = functional.partition(self, f)
|
||||
return sequence:new(a), sequence:new(b)
|
||||
end
|
||||
|
||||
function sequence:zip(other, f)
|
||||
return sequence:new(_func.zip(self, other, f))
|
||||
return sequence:new(functional.zip(self, other, f))
|
||||
end
|
||||
|
||||
function sequence:dedupe()
|
||||
return _func.dedupe(self)
|
||||
return functional.dedupe(self)
|
||||
end
|
||||
|
||||
function sequence:append_inplace(other)
|
||||
return _func.append_inplace(self, other)
|
||||
return functional.append_inplace(self, other)
|
||||
end
|
||||
|
||||
function sequence:append(other)
|
||||
@ -81,7 +79,7 @@ function sequence:append(other)
|
||||
end
|
||||
|
||||
function sequence:copy(deep)
|
||||
return sequence:new(_func.copy(self, deep))
|
||||
return sequence:new(functional.copy(self, deep))
|
||||
end
|
||||
|
||||
return sequence
|
@ -1,20 +1,6 @@
|
||||
-- stable sorting routines for lua
|
||||
--
|
||||
-- by default modifies the global table namespace so you don't have
|
||||
-- to re-require it everywhere.
|
||||
--
|
||||
-- table.stable_sort
|
||||
-- a fast stable sort
|
||||
-- table.unstable_sort
|
||||
-- alias for the builtin unstable table.sort
|
||||
-- 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
|
||||
--
|
||||
--[[
|
||||
stable sorting routines
|
||||
]]
|
||||
|
||||
--this is based on MIT licensed code from Dirk Laurie and Steve Fisher
|
||||
--license as follows:
|
||||
@ -43,13 +29,13 @@
|
||||
|
||||
-- (modifications by Max Cahill 2018, 2020)
|
||||
|
||||
local _sort_core = {}
|
||||
local sort_core = {}
|
||||
|
||||
--tunable size for insertion sort "bottom out"
|
||||
_sort_core.max_chunk_size = 32
|
||||
sort_core.max_chunk_size = 32
|
||||
|
||||
--insertion sort on a section of array
|
||||
function _sort_core._insertion_sort_impl(array, first, last, less)
|
||||
function sort_core._insertion_sort_impl(array, first, last, less)
|
||||
for i = first + 1, last do
|
||||
local k = first
|
||||
local v = array[i]
|
||||
@ -66,7 +52,7 @@ function _sort_core._insertion_sort_impl(array, first, last, less)
|
||||
end
|
||||
|
||||
--merge sorted adjacent sections of array
|
||||
function _sort_core._merge(array, workspace, low, middle, high, less)
|
||||
function sort_core._merge(array, workspace, low, middle, high, less)
|
||||
local i, j, k
|
||||
i = 1
|
||||
-- copy first half of array to auxiliary array
|
||||
@ -99,14 +85,14 @@ function _sort_core._merge(array, workspace, low, middle, high, less)
|
||||
end
|
||||
|
||||
--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)
|
||||
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)
|
||||
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
|
||||
|
||||
@ -116,7 +102,7 @@ local function default_less(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
|
||||
--
|
||||
@ -133,37 +119,36 @@ function _sort_core._sort_setup(array, less)
|
||||
return trivial, n, less
|
||||
end
|
||||
|
||||
function _sort_core.stable_sort(array, less)
|
||||
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)
|
||||
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
|
||||
sort_core.unstable_sort = table.sort
|
||||
|
||||
--export sort core
|
||||
--export sort core to the global table module
|
||||
function sort_core:export()
|
||||
table.insertion_sort = sort_core.insertion_sort
|
||||
table.stable_sort = sort_core.stable_sort
|
||||
table.unstable_sort = sort_core.unstable_sort
|
||||
end
|
||||
|
||||
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
|
||||
return sort_core
|
||||
|
@ -1,54 +1,47 @@
|
||||
--[[
|
||||
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
|
||||
--so it works "as if" it was the global table api
|
||||
--upgraded with these routines
|
||||
if _table ~= table then
|
||||
setmetatable(_table, {
|
||||
|
||||
local tablex = setmetatable({}, {
|
||||
__index = table,
|
||||
})
|
||||
end
|
||||
|
||||
--alias
|
||||
_table.join = _table.concat
|
||||
tablex.join = tablex.concat
|
||||
|
||||
--return the back element of a table
|
||||
function _table.back(t)
|
||||
function tablex.back(t)
|
||||
return t[#t]
|
||||
end
|
||||
|
||||
--remove the back element of a table and return it
|
||||
function _table.pop(t)
|
||||
function tablex.pop(t)
|
||||
return table.remove(t)
|
||||
end
|
||||
|
||||
--insert to the back of a table
|
||||
function _table.push(t, v)
|
||||
function tablex.push(t, v)
|
||||
return table.insert(t, v)
|
||||
end
|
||||
|
||||
--remove the front element of a table and return it
|
||||
function _table.shift(t)
|
||||
function tablex.shift(t)
|
||||
return table.remove(t, 1)
|
||||
end
|
||||
|
||||
--insert to the front of a table
|
||||
function _table.unshift(t, v)
|
||||
function tablex.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 tablex.index_of(t, a)
|
||||
if a == nil then return nil end
|
||||
for i,b in ipairs(t) do
|
||||
if a == b then
|
||||
@ -60,8 +53,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 tablex.remove_value(t, a)
|
||||
local i = tablex.index_of(t, a)
|
||||
if i then
|
||||
table.remove(t, i)
|
||||
return true
|
||||
@ -71,8 +64,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 tablex.add_value(t, a)
|
||||
local i = tablex.index_of(t, a)
|
||||
if not i then
|
||||
table.insert(t, a)
|
||||
return true
|
||||
@ -88,7 +81,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 tablex.pick_random(t, r)
|
||||
if #t == 0 then
|
||||
return nil
|
||||
end
|
||||
@ -96,7 +89,7 @@ function _table.pick_random(t, r)
|
||||
end
|
||||
|
||||
--shuffle the order of a table
|
||||
function _table.shuffle(t, r)
|
||||
function tablex.shuffle(t, r)
|
||||
for i = 1, #t do
|
||||
local j = _random(1, #t, r)
|
||||
t[i], t[j] = t[j], t[i]
|
||||
@ -105,7 +98,7 @@ function _table.shuffle(t, r)
|
||||
end
|
||||
|
||||
--reverse the order of a table
|
||||
function _table.reverse(t)
|
||||
function tablex.reverse(t)
|
||||
for i = 1, #t / 2 do
|
||||
local j = #t - i + 1
|
||||
t[i], t[j] = t[j], t[i]
|
||||
@ -116,7 +109,7 @@ 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)
|
||||
function tablex.keys(t)
|
||||
local r = {}
|
||||
for k,v in pairs(t) do
|
||||
table.insert(r, k)
|
||||
@ -126,7 +119,7 @@ end
|
||||
|
||||
--collect all values of a keyed table into a sequential table
|
||||
--(shallow copy if it's already sequential)
|
||||
function _table.values(t)
|
||||
function tablex.values(t)
|
||||
local r = {}
|
||||
for k,v in pairs(t) do
|
||||
table.insert(r, v)
|
||||
@ -135,15 +128,15 @@ function _table.values(t)
|
||||
end
|
||||
|
||||
--(might already exist depending on luajit)
|
||||
if _table.clear == nil then
|
||||
if _table ~= table and table.clear then
|
||||
if tablex.clear == nil then
|
||||
if tablex ~= table and table.clear then
|
||||
--import from global if it exists
|
||||
_table.clear = table.clear
|
||||
tablex.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)
|
||||
function tablex.clear(t)
|
||||
assert(type(t) == "table", "table.clear - argument 't' must be a table")
|
||||
local k = next(t)
|
||||
while k ~= nil do
|
||||
@ -175,19 +168,19 @@ 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 tablex.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"
|
||||
local istablex = type(deep_or_into) == "table"
|
||||
|
||||
local deep = (is_bool and deep_or_into) or is_table
|
||||
local into = is_table and deep_or_into or {}
|
||||
local deep = (is_bool and deep_or_into) or istablex
|
||||
local into = istablex and deep_or_into or {}
|
||||
for k,v in pairs(t) do
|
||||
if deep and type(v) == "table" then
|
||||
if type(v.copy) == "function" then
|
||||
v = v:copy()
|
||||
else
|
||||
v = _table.copy(v, deep)
|
||||
v = tablex.copy(v, deep)
|
||||
end
|
||||
end
|
||||
into[k] = v
|
||||
@ -196,7 +189,7 @@ function _table.copy(t, deep_or_into)
|
||||
end
|
||||
|
||||
--overlay one table directly onto another, shallow only
|
||||
function _table.overlay(to, from)
|
||||
function tablex.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
|
||||
@ -210,30 +203,32 @@ end
|
||||
--note: you can use a larger unpack than you need as the rest
|
||||
-- can be discarded, but it "feels dirty" :)
|
||||
|
||||
function _table.unpack2(t)
|
||||
function tablex.unpack2(t)
|
||||
return t[1], t[2]
|
||||
end
|
||||
|
||||
function _table.unpack3(t)
|
||||
function tablex.unpack3(t)
|
||||
return t[1], t[2], t[3]
|
||||
end
|
||||
|
||||
function _table.unpack4(t)
|
||||
function tablex.unpack4(t)
|
||||
return t[1], t[2], t[3], t[4]
|
||||
end
|
||||
|
||||
function _table.unpack5(t)
|
||||
function tablex.unpack5(t)
|
||||
return t[1], t[2], t[3], t[4], t[5]
|
||||
end
|
||||
|
||||
function _table.unpack6(t)
|
||||
function tablex.unpack6(t)
|
||||
return t[1], t[2], t[3], t[4], t[5], t[6]
|
||||
end
|
||||
|
||||
function _table.unpack7(t)
|
||||
function tablex.unpack7(t)
|
||||
return t[1], t[2], t[3], t[4], t[5], t[6], t[7]
|
||||
end
|
||||
|
||||
function _table.unpack8(t)
|
||||
function tablex.unpack8(t)
|
||||
return t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8]
|
||||
end
|
||||
|
||||
return tablex
|
19
vec2.lua
19
vec2.lua
@ -2,24 +2,13 @@
|
||||
2d vector type
|
||||
]]
|
||||
|
||||
--[[
|
||||
notes:
|
||||
local path = (...):gsub("vec2", "")
|
||||
local class = require(path .. "class")
|
||||
local math = require(path .. "mathx") --shadow global math module
|
||||
|
||||
some methods depend on math library extensions
|
||||
|
||||
math.clamp(v, min, max) - return v clamped between min and max
|
||||
math.round(v) - round v downwards if fractional part is < 0.5
|
||||
]]
|
||||
|
||||
local vec2 = {}
|
||||
local vec2 = class()
|
||||
vec2.type = "vec2"
|
||||
|
||||
--class
|
||||
vec2._mt = {__index = vec2}
|
||||
function vec2:init(t)
|
||||
return setmetatable(t, self._mt)
|
||||
end
|
||||
|
||||
--probably-too-flexible ctor
|
||||
function vec2:new(x, y)
|
||||
if x and y then
|
||||
|
28
vec3.lua
28
vec3.lua
@ -2,33 +2,15 @@
|
||||
3d vector type
|
||||
]]
|
||||
|
||||
--[[
|
||||
notes:
|
||||
|
||||
some methods depend on math library extensions
|
||||
|
||||
math.clamp(v, min, max) - return v clamped between min and max
|
||||
math.round(v) - round v downwards if fractional part is < 0.5
|
||||
]]
|
||||
|
||||
--import vec2 if not defined globally
|
||||
local global_vec2 = vec2
|
||||
local vec2 = global_vec2
|
||||
if not vec2 then
|
||||
local path = ...
|
||||
local vec2_path = path:sub(1, path:len() - 1) .. "2"
|
||||
vec2 = require(vec2_path)
|
||||
end
|
||||
local path = (...):gsub("vec3", "")
|
||||
local class = require(path .. "class")
|
||||
local vec2 = require(path .. "vec2")
|
||||
local math = require(path .. "mathx") --shadow global math module
|
||||
|
||||
local vec3 = {}
|
||||
local vec3 = class()
|
||||
vec3.type = "vec3"
|
||||
|
||||
--class
|
||||
vec3._mt = {__index = vec3}
|
||||
function vec3:init(t)
|
||||
return setmetatable(t, self._mt)
|
||||
end
|
||||
|
||||
--probably-too-flexible ctor
|
||||
function vec3:new(x, y, z)
|
||||
if x and y and z then
|
||||
|
Loading…
Reference in New Issue
Block a user