[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:
Max Cahill 2020-04-07 13:49:10 +10:00
parent 4bbbdf86d6
commit 6386aaf298
11 changed files with 263 additions and 281 deletions

View File

@ -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

View File

@ -1,7 +1,7 @@
--[[
colour handling stuff
feel free to alias to colour
feel free to alias to `color` :)
]]
local bit = require("bit")

View File

@ -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

View File

@ -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
sequence = _sequence
--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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, {
__index = table,
})
end
local tablex = setmetatable({}, {
__index = table,
})
--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

View File

@ -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

View File

@ -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