Merge remote-tracking branch 'origin/master'

This commit is contained in:
Max Cahill 2020-06-02 15:17:56 +10:00
commit 77fa7b4288
6 changed files with 132 additions and 65 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
return state[name](self, state, ...) if type(state[name]) == "function" then
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