diff --git a/async.lua b/async.lua index 50b05f8..d6c2ff9 100644 --- a/async.lua +++ b/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 diff --git a/colour.lua b/colour.lua index 9860120..eb5868e 100644 --- a/colour.lua +++ b/colour.lua @@ -1,7 +1,7 @@ --[[ colour handling stuff - feel free to alias to colour + feel free to alias to `color` :) ]] local bit = require("bit") diff --git a/functional.lua b/functional.lua index 6c387f4..15723b9 100644 --- a/functional.lua +++ b/functional.lua @@ -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 diff --git a/init.lua b/init.lua index bfa2b68..1afd3b3 100644 --- a/init.lua +++ b/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 - 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 diff --git a/math.lua b/mathx.lua similarity index 87% rename from math.lua rename to mathx.lua index 31713ce..202b9d0 100644 --- a/math.lua +++ b/mathx.lua @@ -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 diff --git a/readme.md b/readme.md index 3b15d0d..3585de0 100644 --- a/readme.md +++ b/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 diff --git a/sequence.lua b/sequence.lua index e4214a4..7b3d250 100644 --- a/sequence.lua +++ b/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 \ No newline at end of file diff --git a/stable_sort.lua b/stable_sort.lua index b0ac601..52b31da 100644 --- a/stable_sort.lua +++ b/stable_sort.lua @@ -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 diff --git a/table.lua b/tablex.lua similarity index 77% rename from table.lua rename to tablex.lua index 802f9a6..213335d 100644 --- a/table.lua +++ b/tablex.lua @@ -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 \ No newline at end of file diff --git a/vec2.lua b/vec2.lua index 4a92369..45f5764 100644 --- a/vec2.lua +++ b/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 diff --git a/vec3.lua b/vec3.lua index 754998a..2ff4b19 100644 --- a/vec3.lua +++ b/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