mirror of
https://github.com/1bardesign/batteries.git
synced 2024-11-26 07:34:35 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
77fa7b4288
17
assert.lua
17
assert.lua
@ -3,6 +3,7 @@
|
|||||||
- avoid garbage generation upon success,
|
- avoid garbage generation upon success,
|
||||||
- build nice formatted error messages
|
- build nice formatted error messages
|
||||||
- post one level above the call site by default
|
- post one level above the call site by default
|
||||||
|
- return their first argument so they can be used inline
|
||||||
|
|
||||||
default call is builtin global assert
|
default call is builtin global assert
|
||||||
|
|
||||||
@ -23,16 +24,18 @@ local function _extra(msg)
|
|||||||
if not msg then
|
if not msg then
|
||||||
return ""
|
return ""
|
||||||
end
|
end
|
||||||
return "(note: " .. msg .. ")"
|
return "\n\n\t(note: " .. msg .. ")"
|
||||||
end
|
end
|
||||||
|
|
||||||
--assert a value is not nil
|
--assert a value is not nil
|
||||||
|
--return the value, so this can be chained
|
||||||
function assert:some(v, msg, stack_level)
|
function assert:some(v, msg, stack_level)
|
||||||
if v == nil then
|
if v == nil then
|
||||||
error(("assertion failed: value is nil %s"):format(
|
error(("assertion failed: value is nil %s"):format(
|
||||||
_extra(msg)
|
_extra(msg)
|
||||||
), 2 + (stack_level or 0))
|
), 2 + (stack_level or 0))
|
||||||
end
|
end
|
||||||
|
return v
|
||||||
end
|
end
|
||||||
|
|
||||||
--assert two values are equal
|
--assert two values are equal
|
||||||
@ -44,6 +47,7 @@ function assert:equal(a, b, msg, stack_level)
|
|||||||
_extra(msg)
|
_extra(msg)
|
||||||
), 2 + (stack_level or 0))
|
), 2 + (stack_level or 0))
|
||||||
end
|
end
|
||||||
|
return a
|
||||||
end
|
end
|
||||||
|
|
||||||
--assert two values are not equal
|
--assert two values are not equal
|
||||||
@ -53,6 +57,7 @@ function assert:not_equal(a, b, msg, stack_level)
|
|||||||
_extra(msg)
|
_extra(msg)
|
||||||
), 2 + (stack_level or 0))
|
), 2 + (stack_level or 0))
|
||||||
end
|
end
|
||||||
|
return a
|
||||||
end
|
end
|
||||||
|
|
||||||
--assert a value is of a certain type
|
--assert a value is of a certain type
|
||||||
@ -65,15 +70,19 @@ function assert:type(a, t, msg, stack_level)
|
|||||||
_extra(msg)
|
_extra(msg)
|
||||||
), 2 + (stack_level or 0))
|
), 2 + (stack_level or 0))
|
||||||
end
|
end
|
||||||
|
return a
|
||||||
end
|
end
|
||||||
|
|
||||||
--replace everything in assert with nop functions, for near-zero overhead on release
|
--replace everything in assert with nop functions that just return their second argument, for near-zero overhead on release
|
||||||
function assert:nop()
|
function assert:nop()
|
||||||
|
local nop = function(self, a)
|
||||||
|
return a
|
||||||
|
end
|
||||||
setmetatable(self, {
|
setmetatable(self, {
|
||||||
__call = function() end,
|
__call = nop,
|
||||||
})
|
})
|
||||||
for k, v in pairs(self) do
|
for k, v in pairs(self) do
|
||||||
self[k] = function() end
|
self[k] = nop
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
6
init.lua
6
init.lua
@ -20,7 +20,7 @@ local _batteries = {
|
|||||||
tablex = require_relative("tablex"),
|
tablex = require_relative("tablex"),
|
||||||
stringx = require_relative("stringx"),
|
stringx = require_relative("stringx"),
|
||||||
--sorting routines
|
--sorting routines
|
||||||
stable_sort = require_relative("stable_sort"),
|
sort = require_relative("sort"),
|
||||||
--
|
--
|
||||||
functional = require_relative("functional"),
|
functional = require_relative("functional"),
|
||||||
--collections
|
--collections
|
||||||
@ -43,7 +43,7 @@ for _, alias in ipairs({
|
|||||||
{"mathx", "math"},
|
{"mathx", "math"},
|
||||||
{"tablex", "table"},
|
{"tablex", "table"},
|
||||||
{"stringx", "string"},
|
{"stringx", "string"},
|
||||||
{"stable_sort", "sort"},
|
{"sort", "stable_sort"},
|
||||||
{"colour", "color"},
|
{"colour", "color"},
|
||||||
}) do
|
}) do
|
||||||
_batteries[alias[2]] = _batteries[alias[1]]
|
_batteries[alias[2]] = _batteries[alias[1]]
|
||||||
@ -61,7 +61,7 @@ function _batteries:export()
|
|||||||
self.tablex.overlay(table, self.tablex)
|
self.tablex.overlay(table, self.tablex)
|
||||||
--now we can use it through table directly
|
--now we can use it through table directly
|
||||||
table.overlay(table, self.functional)
|
table.overlay(table, self.functional)
|
||||||
self.stable_sort:export()
|
self.sort:export()
|
||||||
|
|
||||||
--functional module also available separate from table
|
--functional module also available separate from table
|
||||||
functional = self.functional
|
functional = self.functional
|
||||||
|
20
readme.md
20
readme.md
@ -10,22 +10,26 @@ Examples [in another repo](https://github.com/1bardesign/batteries-examples) to
|
|||||||
|
|
||||||
# Module Overview
|
# Module Overview
|
||||||
|
|
||||||
- `class` - Single-inheritance oo in a single function.
|
**Lua Core Extensions:**
|
||||||
- `mathx` - Mathematical extensions. Alias `math`.
|
- `mathx` - Mathematical extensions. Alias `math`.
|
||||||
- `tablex` - Table handling extensions. Alias `table`.
|
- `tablex` - Table handling extensions. Alias `table`.
|
||||||
- `stringx` - String handling extensions. Alias `string`.
|
- `stringx` - String handling extensions. Alias `string`.
|
||||||
- `stable_sort` - A stable sorting algorithm that is also, as a bonus, often faster than table.sort under luajit.
|
**General Utility:**
|
||||||
|
- `class` - Single-inheritance oo in a single function.
|
||||||
- `functional` - Functional programming facilities. `map`, `reduce`, `any`, `match`, `minmax`, `mean`...
|
- `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 method chaining for the `functional` interface above, which can save a lot of typing!
|
- `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 needless typing!
|
||||||
- `set` - A set type supporting a full suite of set operations with fast membership testing and ipairs-style iteration.
|
- `set` - A set type supporting a full suite of set operations with fast membership testing and ipairs-style iteration.
|
||||||
|
- `sort` - Provides a stable merge+insertion sorting algorithm that is also, as a bonus, often faster than `table.sort` under luajit. Also exposes `insertion_sort` if needed. Alias `stable_sort`
|
||||||
- `state_machine` - Finite state machine implementation with state transitions and all the rest. Useful for game states, ai, cutscenes...
|
- `state_machine` - Finite state machine implementation with state transitions and all the rest. Useful for game states, ai, cutscenes...
|
||||||
- `async` - Async operations as coroutines.
|
**Geometry:**
|
||||||
- `manual_gc` - Get GC out of your update/draw calls. Useful when trying to get accurate profiling information; moves "randomness" of GC. Requires you to think a bit about your garbage budgets though.
|
- `intersect` - 2d intersection routines, a bit sparse at the moment
|
||||||
- `colour` - Colour conversion routines. Alias `color`.
|
|
||||||
- `unique_mapping` - Generate a unique mapping from arbitrary lua values to numeric keys - essentially making up a consistent ordering for unordered data. Niche, but can be used to optimise draw batches for example, as you can't sort on textures without it.
|
|
||||||
- `vec2` - 2d vectors with method chaining, garbage saving interface. A bit of a mouthful at times, but you get used to it.
|
- `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.
|
- `vec3` - 3d vectors as above.
|
||||||
- `intersect` - 2d intersection routines, a bit sparse at the moment
|
**Misc:**
|
||||||
|
- `async` - Async operations as coroutines.
|
||||||
|
- `colour` - Colour conversion routines. Alias `color`.
|
||||||
|
- `manual_gc` - Get GC out of your update/draw calls. Useful when trying to get accurate profiling information; moves "randomness" of GC. Requires you to think a bit about your garbage budgets though.
|
||||||
|
- `unique_mapping` - Generate a unique mapping from arbitrary lua values to numeric keys - essentially making up a consistent ordering for unordered data. Niche, but can be used to optimise draw batches for example, as you can't sort on textures without it.
|
||||||
|
|
||||||
Aliases are provided at both the `batteries` level and globally when exported.
|
Aliases are provided at both the `batteries` level and globally when exported.
|
||||||
|
|
||||||
|
30
sequence.lua
30
sequence.lua
@ -9,6 +9,7 @@ local path = (...):gsub("sequence", "")
|
|||||||
local class = require(path .. "class")
|
local class = require(path .. "class")
|
||||||
local table = require(path .. "tablex") --shadow global table module
|
local table = require(path .. "tablex") --shadow global table module
|
||||||
local functional = require(path .. "functional")
|
local functional = require(path .. "functional")
|
||||||
|
local stable_sort = require(path .. "sort").stable_sort
|
||||||
|
|
||||||
local sequence = class(table) --proxy missing table fns to tablex api
|
local sequence = class(table) --proxy missing table fns to tablex api
|
||||||
|
|
||||||
@ -17,29 +18,34 @@ function sequence:new(t)
|
|||||||
return self:init(t or {})
|
return self:init(t or {})
|
||||||
end
|
end
|
||||||
|
|
||||||
--alias
|
--sorting default to stable
|
||||||
sequence.join = table.concat
|
sequence.sort = stable_sort
|
||||||
|
|
||||||
--sorting default to stable if present
|
|
||||||
sequence.sort = table.stable_sort or table.sort
|
|
||||||
|
|
||||||
--patch various interfaces in a type-preserving way, for method chaining
|
--patch various interfaces in a type-preserving way, for method chaining
|
||||||
|
|
||||||
--import copying tablex
|
--import copying tablex
|
||||||
function sequence:keys()
|
function sequence:keys()
|
||||||
return sequence(self:keys())
|
return sequence(table.keys(self))
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:values()
|
function sequence:values()
|
||||||
return sequence(self:values())
|
return sequence(table.values(self))
|
||||||
end
|
|
||||||
|
|
||||||
function sequence:append(other)
|
|
||||||
return sequence(self:append(other))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function sequence:dedupe()
|
function sequence:dedupe()
|
||||||
return sequence(self:dedupe())
|
return sequence(table.dedupe(self))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:append(...)
|
||||||
|
return sequence(table.append(self, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:overlay(...)
|
||||||
|
return sequence(table.overlay(self, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:copy(...)
|
||||||
|
return sequence(table.copy(self, ...))
|
||||||
end
|
end
|
||||||
|
|
||||||
--import functional interface
|
--import functional interface
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
--[[
|
--[[
|
||||||
stable sorting routines
|
various sorting routines
|
||||||
]]
|
]]
|
||||||
|
|
||||||
--this is based on MIT licensed code from Dirk Laurie and Steve Fisher
|
--this is based on code from Dirk Laurie and Steve Fisher,
|
||||||
--license as follows:
|
--used under license as follows:
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Copyright © 2013 Dirk Laurie and Steve Fisher.
|
Copyright © 2013 Dirk Laurie and Steve Fisher.
|
||||||
@ -29,13 +29,13 @@
|
|||||||
|
|
||||||
-- (modifications by Max Cahill 2018, 2020)
|
-- (modifications by Max Cahill 2018, 2020)
|
||||||
|
|
||||||
local sort_core = {}
|
local sort = {}
|
||||||
|
|
||||||
--tunable size for insertion sort "bottom out"
|
--tunable size for insertion sort "bottom out"
|
||||||
sort_core.max_chunk_size = 32
|
sort.max_chunk_size = 32
|
||||||
|
|
||||||
--insertion sort on a section of array
|
--insertion sort on a section of array
|
||||||
function sort_core._insertion_sort_impl(array, first, last, less)
|
function sort._insertion_sort_impl(array, first, last, less)
|
||||||
for i = first + 1, last do
|
for i = first + 1, last do
|
||||||
local k = first
|
local k = first
|
||||||
local v = array[i]
|
local v = array[i]
|
||||||
@ -52,7 +52,7 @@ function sort_core._insertion_sort_impl(array, first, last, less)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--merge sorted adjacent sections of array
|
--merge sorted adjacent sections of array
|
||||||
function sort_core._merge(array, workspace, low, middle, high, less)
|
function sort._merge(array, workspace, low, middle, high, less)
|
||||||
local i, j, k
|
local i, j, k
|
||||||
i = 1
|
i = 1
|
||||||
-- copy first half of array to auxiliary array
|
-- copy first half of array to auxiliary array
|
||||||
@ -85,14 +85,14 @@ function sort_core._merge(array, workspace, low, middle, high, less)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--implementation for the merge sort
|
--implementation for the merge sort
|
||||||
function sort_core._merge_sort_impl(array, workspace, low, high, less)
|
function sort._merge_sort_impl(array, workspace, low, high, less)
|
||||||
if high - low <= sort_core.max_chunk_size then
|
if high - low <= sort.max_chunk_size then
|
||||||
sort_core._insertion_sort_impl(array, low, high, less)
|
sort._insertion_sort_impl(array, low, high, less)
|
||||||
else
|
else
|
||||||
local middle = math.floor((low + high) / 2)
|
local middle = math.floor((low + high) / 2)
|
||||||
sort_core._merge_sort_impl(array, workspace, low, middle, less)
|
sort._merge_sort_impl(array, workspace, low, middle, less)
|
||||||
sort_core._merge_sort_impl(array, workspace, middle + 1, high, less)
|
sort._merge_sort_impl(array, workspace, middle + 1, high, less)
|
||||||
sort_core._merge(array, workspace, low, middle, high, less)
|
sort._merge(array, workspace, low, middle, high, less)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ local function default_less(a, b)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--inline common setup stuff
|
--inline common setup stuff
|
||||||
function sort_core._sort_setup(array, less)
|
function sort._sort_setup(array, less)
|
||||||
--default less
|
--default less
|
||||||
less = less or default_less
|
less = less or default_less
|
||||||
--
|
--
|
||||||
@ -119,36 +119,36 @@ function sort_core._sort_setup(array, less)
|
|||||||
return trivial, n, less
|
return trivial, n, less
|
||||||
end
|
end
|
||||||
|
|
||||||
function sort_core.stable_sort(array, less)
|
function sort.stable_sort(array, less)
|
||||||
--setup
|
--setup
|
||||||
local trivial, n, less = sort_core._sort_setup(array, less)
|
local trivial, n, less = sort._sort_setup(array, less)
|
||||||
if not trivial then
|
if not trivial then
|
||||||
--temp storage; allocate ahead of time
|
--temp storage; allocate ahead of time
|
||||||
local workspace = {}
|
local workspace = {}
|
||||||
local middle = math.ceil(n / 2)
|
local middle = math.ceil(n / 2)
|
||||||
workspace[middle] = array[1]
|
workspace[middle] = array[1]
|
||||||
--dive in
|
--dive in
|
||||||
sort_core._merge_sort_impl( array, workspace, 1, n, less )
|
sort._merge_sort_impl( array, workspace, 1, n, less )
|
||||||
end
|
end
|
||||||
return array
|
return array
|
||||||
end
|
end
|
||||||
|
|
||||||
function sort_core.insertion_sort(array, less)
|
function sort.insertion_sort(array, less)
|
||||||
--setup
|
--setup
|
||||||
local trivial, n, less = sort_core._sort_setup(array, less)
|
local trivial, n, less = sort._sort_setup(array, less)
|
||||||
if not trivial then
|
if not trivial then
|
||||||
sort_core._insertion_sort_impl(array, 1, n, less)
|
sort._insertion_sort_impl(array, 1, n, less)
|
||||||
end
|
end
|
||||||
return array
|
return array
|
||||||
end
|
end
|
||||||
|
|
||||||
sort_core.unstable_sort = table.sort
|
sort.unstable_sort = table.sort
|
||||||
|
|
||||||
--export sort core to the global table module
|
--export sort core to the global table module
|
||||||
function sort_core:export()
|
function sort:export()
|
||||||
table.insertion_sort = sort_core.insertion_sort
|
table.insertion_sort = sort.insertion_sort
|
||||||
table.stable_sort = sort_core.stable_sort
|
table.stable_sort = sort.stable_sort
|
||||||
table.unstable_sort = sort_core.unstable_sort
|
table.unstable_sort = sort.unstable_sort
|
||||||
end
|
end
|
||||||
|
|
||||||
return sort_core
|
return sort
|
@ -12,6 +12,10 @@
|
|||||||
on draw, the current state's draw callback is called
|
on draw, the current state's draw callback is called
|
||||||
|
|
||||||
TODO: consider coroutine friendliness
|
TODO: consider coroutine friendliness
|
||||||
|
|
||||||
|
TODO: consider refactoring the callback signatures to allow using objects with methods
|
||||||
|
like update(dt)/draw() directly
|
||||||
|
current pattern means they need to be wrapped (as in :as_state())
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local path = (...):gsub("state_machine", "")
|
local path = (...):gsub("state_machine", "")
|
||||||
@ -41,12 +45,27 @@ end
|
|||||||
--make an internal call
|
--make an internal call
|
||||||
function state_machine:_call(name, ...)
|
function state_machine:_call(name, ...)
|
||||||
local state = self:_get_state()
|
local state = self:_get_state()
|
||||||
if state and type(state[name]) == "function" then
|
if state then
|
||||||
|
if type(state[name]) == "function" then
|
||||||
return state[name](self, state, ...)
|
return state[name](self, state, ...)
|
||||||
|
elseif type(state) == "function" then
|
||||||
|
return state(self, name, ...)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--make an internal call and transition if the return value is a valid state
|
||||||
|
--return the value if it isn't a valid state
|
||||||
|
function state_machine:_call_and_transition(name, ...)
|
||||||
|
local r = self:_call(name, ...)
|
||||||
|
if self:has_state(r) then
|
||||||
|
self:set_state(r, r == self.current_state)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
--various checks
|
--various checks
|
||||||
|
|
||||||
@ -63,7 +82,7 @@ end
|
|||||||
|
|
||||||
--add a state
|
--add a state
|
||||||
function state_machine:add_state(name, data)
|
function state_machine:add_state(name, data)
|
||||||
if self.has_state(name) then
|
if self:has_state(name) then
|
||||||
error("error: added duplicate state "..name)
|
error("error: added duplicate state "..name)
|
||||||
else
|
else
|
||||||
self.states[name] = data
|
self.states[name] = data
|
||||||
@ -77,7 +96,7 @@ end
|
|||||||
|
|
||||||
--remove a state
|
--remove a state
|
||||||
function state_machine:remove_state(name)
|
function state_machine:remove_state(name)
|
||||||
if not self.has_state(name) then
|
if not self:has_state(name) then
|
||||||
error("error: removed missed state "..name)
|
error("error: removed missed state "..name)
|
||||||
else
|
else
|
||||||
if self:in_state(name) then
|
if self:in_state(name) then
|
||||||
@ -99,13 +118,13 @@ function state_machine:replace_state(name, data, do_transitions)
|
|||||||
end
|
end
|
||||||
self.states[name] = data
|
self.states[name] = data
|
||||||
if do_transitions and current then
|
if do_transitions and current then
|
||||||
self:_call("enter")
|
self:_call_and_transition("enter")
|
||||||
end
|
end
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--ensure a state doesn't exist
|
--ensure a state doesn't exist; transition out of it if we're currently in it
|
||||||
function state_machine:clear_state(name)
|
function state_machine:clear_state(name)
|
||||||
return self:replace_state(name, nil, true)
|
return self:replace_state(name, nil, true)
|
||||||
end
|
end
|
||||||
@ -113,23 +132,52 @@ end
|
|||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
--transitions and updates
|
--transitions and updates
|
||||||
|
|
||||||
|
--set the current state
|
||||||
|
--if the enter callback of the target state returns a valid state name, then
|
||||||
|
-- it is transitioned to in turn, and so on until the machine is at rest
|
||||||
function state_machine:set_state(state, reset)
|
function state_machine:set_state(state, reset)
|
||||||
if self.current_state ~= state or reset then
|
if self.current_state ~= state or reset then
|
||||||
self:_call("exit")
|
self:_call("exit")
|
||||||
self.current_state = state
|
self.current_state = state
|
||||||
self:_call("enter")
|
self:_call_and_transition("enter")
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
--perform an update
|
--perform an update
|
||||||
--pass in an optional delta time which is passed as an arg to the state functions
|
--pass in an optional delta time which is passed as an arg to the state functions
|
||||||
|
--if the state update returns a string, and we have that state
|
||||||
|
-- then we change state (reset if it's the current state)
|
||||||
|
-- and return nil
|
||||||
|
--otherwise, the result is returned
|
||||||
function state_machine:update(dt)
|
function state_machine:update(dt)
|
||||||
return self:_call("update", dt)
|
return self:_call_and_transition("update", dt)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--draw the current state
|
||||||
function state_machine:draw()
|
function state_machine:draw()
|
||||||
self:_call("draw")
|
self:_call("draw")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--wrap a state machine in a table suitable for use directly as a state in another state_machine
|
||||||
|
--upon entry, this machine will be forced into enter_state
|
||||||
|
--the parent will be accessible under m.parent
|
||||||
|
function state_machine:as_state(enter_state)
|
||||||
|
if not self._as_state then
|
||||||
|
self._as_state = {
|
||||||
|
enter = function(m, s)
|
||||||
|
self.parent = m
|
||||||
|
self:set_state(enter_state)
|
||||||
|
end,
|
||||||
|
update = function(m, s, dt)
|
||||||
|
return self:update(dt)
|
||||||
|
end,
|
||||||
|
draw = function(m, s)
|
||||||
|
return self:draw()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return self._as_state
|
||||||
|
end
|
||||||
|
|
||||||
return state_machine
|
return state_machine
|
Loading…
Reference in New Issue
Block a user