Charakterbewegungen hinzugefügt, Deko hinzugefügt, Kochrezepte angepasst
This commit is contained in:
parent
95945c0306
commit
a0c893ca0b
1124 changed files with 64294 additions and 763 deletions
51
mods/futil/util/bisect.lua
Normal file
51
mods/futil/util/bisect.lua
Normal file
|
@ -0,0 +1,51 @@
|
|||
futil.bisect = {}
|
||||
|
||||
function futil.bisect.right(t, x, low, high, key)
|
||||
low = low or 1
|
||||
high = high or #t + 1
|
||||
if key then
|
||||
while low < high do
|
||||
local mid = math.floor((low + high) / 2)
|
||||
if x < key(t[mid]) then
|
||||
high = mid
|
||||
else
|
||||
low = mid + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
while low < high do
|
||||
local mid = math.floor((low + high) / 2)
|
||||
if x < t[mid] then
|
||||
high = mid
|
||||
else
|
||||
low = mid + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return low
|
||||
end
|
||||
|
||||
function futil.bisect.left(t, x, low, high, key)
|
||||
low = low or 1
|
||||
high = high or #t + 1
|
||||
if key then
|
||||
while low < high do
|
||||
local mid = math.floor((low + high) / 2)
|
||||
if key(t[mid]) < x then
|
||||
low = mid + 1
|
||||
else
|
||||
high = mid
|
||||
end
|
||||
end
|
||||
else
|
||||
while low < high do
|
||||
local mid = math.floor((low + high) / 2)
|
||||
if t[mid] < x then
|
||||
low = mid + 1
|
||||
else
|
||||
high = mid
|
||||
end
|
||||
end
|
||||
end
|
||||
return low
|
||||
end
|
89
mods/futil/util/class.lua
Normal file
89
mods/futil/util/class.lua
Normal file
|
@ -0,0 +1,89 @@
|
|||
function futil.class1(super)
|
||||
local class = {}
|
||||
class.__index = class -- this becomes the index "metamethod" of objects
|
||||
|
||||
setmetatable(class, {
|
||||
__index = super and super.__index or super,
|
||||
__call = function(this_class, ...)
|
||||
local obj = setmetatable({}, this_class)
|
||||
local init = obj._init
|
||||
if init then
|
||||
init(obj, ...)
|
||||
end
|
||||
return obj
|
||||
end,
|
||||
})
|
||||
|
||||
function class:is_a(class2)
|
||||
if class == class2 then
|
||||
return true
|
||||
end
|
||||
|
||||
if super and super:is_a(class2) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return class
|
||||
end
|
||||
|
||||
function futil.class(...)
|
||||
local class = {}
|
||||
class.__index = class
|
||||
|
||||
local meta = {
|
||||
__call = function(this_class, ...)
|
||||
local obj = setmetatable({}, this_class)
|
||||
local init = obj._init
|
||||
if init then
|
||||
init(obj, ...)
|
||||
end
|
||||
return obj
|
||||
end,
|
||||
}
|
||||
|
||||
local parents = { ... }
|
||||
class._parents = parents
|
||||
|
||||
if #parents > 0 then
|
||||
function meta:__index(key)
|
||||
for i = #parents, 1, -1 do
|
||||
local parent = parents[i]
|
||||
local index = parent.__index
|
||||
local v
|
||||
if index then
|
||||
if type(index) == "function" then
|
||||
v = index(self, key)
|
||||
else
|
||||
v = index[key]
|
||||
end
|
||||
else
|
||||
v = parent[key]
|
||||
end
|
||||
if v then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(class, meta)
|
||||
|
||||
function class:is_a(class2)
|
||||
if class == class2 then
|
||||
return true
|
||||
end
|
||||
|
||||
for _, parent in ipairs(parents) do
|
||||
if parent:is_a(class2) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return class
|
||||
end
|
9
mods/futil/util/coalesce.lua
Normal file
9
mods/futil/util/coalesce.lua
Normal file
|
@ -0,0 +1,9 @@
|
|||
function futil.coalesce(...)
|
||||
local arg = futil.table.pack(...)
|
||||
for i = 1, arg.n do
|
||||
local v = arg[i]
|
||||
if v ~= nil then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
28
mods/futil/util/equals.lua
Normal file
28
mods/futil/util/equals.lua
Normal file
|
@ -0,0 +1,28 @@
|
|||
local table_size = futil.table.size
|
||||
|
||||
local function equals(a, b)
|
||||
local t = type(a)
|
||||
|
||||
if t ~= type(b) then
|
||||
return false
|
||||
end
|
||||
|
||||
if t ~= "table" then
|
||||
return a == b
|
||||
elseif a == b then
|
||||
return true
|
||||
end
|
||||
|
||||
local size_a = 0
|
||||
|
||||
for key, value in pairs(a) do
|
||||
if not equals(value, b[key]) then
|
||||
return false
|
||||
end
|
||||
size_a = size_a + 1
|
||||
end
|
||||
|
||||
return size_a == table_size(b)
|
||||
end
|
||||
|
||||
futil.equals = equals
|
29
mods/futil/util/exception.lua
Normal file
29
mods/futil/util/exception.lua
Normal file
|
@ -0,0 +1,29 @@
|
|||
function futil.safe_wrap(func, rv_on_fail, error_callback)
|
||||
-- wrap a function w/ logic to avoid crashing
|
||||
return function(...)
|
||||
local rvs = { xpcall(func, debug.traceback, ...) }
|
||||
|
||||
if rvs[1] then
|
||||
return unpack(rvs, 2)
|
||||
else
|
||||
if error_callback then
|
||||
error_callback(debug.getinfo(func), { ... }, rvs[2])
|
||||
else
|
||||
futil.log(
|
||||
"error",
|
||||
"(check_call): %s args: %s out: %s",
|
||||
dump(debug.getinfo(func)),
|
||||
dump({ ... }),
|
||||
rvs[2]
|
||||
)
|
||||
end
|
||||
return rv_on_fail
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function futil.safe_call(func, rv_on_fail, error_callback, ...)
|
||||
return futil.safe_wrap(func, rv_on_fail, error_callback)(...)
|
||||
end
|
||||
|
||||
futil.check_call = futil.safe_wrap -- backwards compatibility
|
37
mods/futil/util/file.lua
Normal file
37
mods/futil/util/file.lua
Normal file
|
@ -0,0 +1,37 @@
|
|||
function futil.file_exists(path)
|
||||
local f = io.open(path, "r")
|
||||
if f then
|
||||
io.close(f)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function futil.load_file(filename)
|
||||
local file = io.open(filename, "r")
|
||||
|
||||
if not file then
|
||||
return
|
||||
end
|
||||
|
||||
local contents = file:read("*a")
|
||||
|
||||
file:close()
|
||||
|
||||
return contents
|
||||
end
|
||||
|
||||
-- minetest.safe_file_write is apparently unreliable on windows
|
||||
function futil.write_file(filename, contents)
|
||||
local file = io.open(filename, "w")
|
||||
|
||||
if not file then
|
||||
return false
|
||||
end
|
||||
|
||||
file:write(contents)
|
||||
file:close()
|
||||
|
||||
return true
|
||||
end
|
159
mods/futil/util/functional.lua
Normal file
159
mods/futil/util/functional.lua
Normal file
|
@ -0,0 +1,159 @@
|
|||
local functional = {}
|
||||
|
||||
local t_iterate = futil.table.iterate
|
||||
local t_insert = table.insert
|
||||
|
||||
function functional.noop()
|
||||
-- the NOTHING function does nothing.
|
||||
end
|
||||
|
||||
function functional.identity(x)
|
||||
return x
|
||||
end
|
||||
|
||||
function functional.izip(...)
|
||||
local is = { ... }
|
||||
if #is == 0 then
|
||||
return functional.noop
|
||||
end
|
||||
|
||||
return function()
|
||||
local t = {}
|
||||
for i in t_iterate(is) do
|
||||
local v = i()
|
||||
if v ~= nil then
|
||||
t_insert(t, v)
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
end
|
||||
|
||||
function functional.zip(...)
|
||||
local is = {}
|
||||
for t in t_iterate({ ... }) do
|
||||
t_insert(is, t_iterate(t))
|
||||
end
|
||||
return functional.izip(unpack(is))
|
||||
end
|
||||
|
||||
function functional.imap(func, ...)
|
||||
local zipper = functional.izip(...)
|
||||
return function()
|
||||
local args = zipper()
|
||||
if args then
|
||||
return func(unpack(args))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function functional.map(func, ...)
|
||||
local zipper = functional.zip(...)
|
||||
return function()
|
||||
local args = zipper()
|
||||
if args then
|
||||
return func(unpack(args))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function functional.apply(func, t)
|
||||
local t2 = {}
|
||||
for k, v in pairs(t) do
|
||||
t2[k] = func(v)
|
||||
end
|
||||
return t2
|
||||
end
|
||||
|
||||
function functional.reduce(func, t, initial)
|
||||
local i = t_iterate(t)
|
||||
if not initial then
|
||||
initial = i()
|
||||
end
|
||||
local next = i()
|
||||
while next do
|
||||
initial = func(initial, next)
|
||||
next = i()
|
||||
end
|
||||
return initial
|
||||
end
|
||||
|
||||
function functional.partial(func, ...)
|
||||
local args = { ... }
|
||||
return function(...)
|
||||
return func(unpack(args), ...)
|
||||
end
|
||||
end
|
||||
|
||||
function functional.compose(a, b)
|
||||
return function(...)
|
||||
return a(b(...))
|
||||
end
|
||||
end
|
||||
|
||||
function functional.ifilter(pred, i)
|
||||
local v
|
||||
return function()
|
||||
v = i()
|
||||
while v ~= nil and not pred(v) do
|
||||
v = i()
|
||||
end
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
function functional.filter(pred, t)
|
||||
return functional.ifilter(pred, t_iterate(t))
|
||||
end
|
||||
|
||||
function functional.iall(i)
|
||||
while true do
|
||||
local v = i()
|
||||
if v == false then
|
||||
return false
|
||||
elseif v == nil then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function functional.all(t)
|
||||
for i = 1, #t do
|
||||
if not t[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function functional.iany(i)
|
||||
while true do
|
||||
local v = i()
|
||||
if v == nil then
|
||||
return false
|
||||
elseif v then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function functional.any(t)
|
||||
for i = 1, #t do
|
||||
if t[i] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function functional.wrap(f)
|
||||
return function(...)
|
||||
return f(...)
|
||||
end
|
||||
end
|
||||
|
||||
futil.functional = functional
|
10
mods/futil/util/http.lua
Normal file
10
mods/futil/util/http.lua
Normal file
|
@ -0,0 +1,10 @@
|
|||
local function char_to_hex(c)
|
||||
return string.format("%%%02X", string.byte(c))
|
||||
end
|
||||
|
||||
function futil.urlencode(text)
|
||||
text = text:gsub("\n", "\r\n")
|
||||
text = text:gsub("([^0-9a-zA-Z !'()*._~-])", char_to_hex)
|
||||
text = text:gsub(" ", "+")
|
||||
return text
|
||||
end
|
24
mods/futil/util/init.lua
Normal file
24
mods/futil/util/init.lua
Normal file
|
@ -0,0 +1,24 @@
|
|||
futil.dofile("util", "bisect")
|
||||
futil.dofile("util", "class")
|
||||
futil.dofile("util", "coalesce")
|
||||
futil.dofile("util", "exception")
|
||||
futil.dofile("util", "file")
|
||||
futil.dofile("util", "http")
|
||||
futil.dofile("util", "list")
|
||||
futil.dofile("util", "math")
|
||||
futil.dofile("util", "matrix")
|
||||
futil.dofile("util", "memoization")
|
||||
futil.dofile("util", "memory")
|
||||
futil.dofile("util", "path")
|
||||
futil.dofile("util", "predicates")
|
||||
futil.dofile("util", "string")
|
||||
futil.dofile("util", "table")
|
||||
|
||||
futil.dofile("util", "equals") -- depends on table
|
||||
futil.dofile("util", "functional") -- depends on table
|
||||
futil.dofile("util", "iterators") -- depends on functional
|
||||
futil.dofile("util", "random") -- depends on math
|
||||
futil.dofile("util", "regex") -- depends on exception
|
||||
futil.dofile("util", "selection") -- depends on table, math
|
||||
futil.dofile("util", "time") -- depends on math
|
||||
futil.dofile("util", "limiters") -- depends on functional
|
106
mods/futil/util/iterators.lua
Normal file
106
mods/futil/util/iterators.lua
Normal file
|
@ -0,0 +1,106 @@
|
|||
local iterators = {}
|
||||
|
||||
function iterators.range(...)
|
||||
local a, b, c = ...
|
||||
if type(a) ~= "number" then
|
||||
error("invalid range")
|
||||
end
|
||||
if not b then
|
||||
a, b = 1, a
|
||||
end
|
||||
if type(b) ~= "number" then
|
||||
error("invalid range")
|
||||
end
|
||||
c = c or 1
|
||||
if type(c) ~= "number" or c == 0 then
|
||||
error("invalid range")
|
||||
end
|
||||
|
||||
if c > 0 then
|
||||
return function()
|
||||
if a > b then
|
||||
return
|
||||
end
|
||||
local to_return = a
|
||||
a = a + c
|
||||
return to_return
|
||||
end
|
||||
else
|
||||
return function()
|
||||
if a < b then
|
||||
return
|
||||
end
|
||||
local to_return = a
|
||||
a = a + c
|
||||
return to_return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function iterators.repeat_(value, times)
|
||||
if times then
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
if i <= times then
|
||||
return value
|
||||
end
|
||||
end
|
||||
else
|
||||
return function()
|
||||
return value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function iterators.chain(...)
|
||||
local arg = { ... }
|
||||
local i = 1
|
||||
|
||||
return function()
|
||||
while i <= #arg do
|
||||
local v = arg[i]()
|
||||
if v then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function iterators.count(start, step)
|
||||
step = step or 1
|
||||
return function()
|
||||
local rv = start
|
||||
start = start + step
|
||||
return rv
|
||||
end
|
||||
end
|
||||
|
||||
function iterators.values(t)
|
||||
local k
|
||||
return function()
|
||||
local value
|
||||
k, value = next(t, k)
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
function iterators.accumulate(t, composer, initial)
|
||||
local value = initial
|
||||
local i = futil.table.iterate(t)
|
||||
return function()
|
||||
local next_value = i()
|
||||
if next_value then
|
||||
if value == nil then
|
||||
value = next_value
|
||||
elseif composer then
|
||||
value = composer(value, next_value)
|
||||
else
|
||||
value = value + next_value
|
||||
end
|
||||
return value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
futil.iterators = iterators
|
42
mods/futil/util/limiters.lua
Normal file
42
mods/futil/util/limiters.lua
Normal file
|
@ -0,0 +1,42 @@
|
|||
--[[
|
||||
functions to limit the growth of a variable.
|
||||
the intention here is to provide a family of functions for which
|
||||
* f(x) is defined for all x >= 0
|
||||
* f(0) = 0
|
||||
* f(1) = 1
|
||||
* f is continuous
|
||||
* f is nondecreasing
|
||||
* f\'(x) is nonincreasing when x > 1 (when the parameters are appropriate)
|
||||
]]
|
||||
|
||||
local log = math.log
|
||||
local pow = math.pow
|
||||
local tanh = math.tanh
|
||||
|
||||
futil.limiters = {
|
||||
-- no limiting
|
||||
none = futil.functional.identity,
|
||||
-- f(x) = x ^ param_1. param_1 should be < 1 for f\'(x) to be nonincreasing
|
||||
-- f(x) will grow arbitrarily, but at a decreasing rate.
|
||||
gamma = function(x, param_1)
|
||||
return pow(x, param_1)
|
||||
end,
|
||||
-- the hyperbolic tangent scaled so that f(0) = 0 and f(1) = 1.
|
||||
-- f(x) will grow approximately linearly for small x, but it will never grow beyond a maximum value, which is
|
||||
-- approximately equal to param_1 + 1
|
||||
tanh = function(x, param_1)
|
||||
return (tanh((x - 1) / param_1) - tanh(-1 / param_1)) / -tanh(-1 / param_1)
|
||||
end,
|
||||
-- f(x) = log^param_2(param_1 * x + 1), scaled so that f(0) = 0 and f(1) = 1.
|
||||
-- f(x) will grow arbitrarily, but at a much slower rate than a gamma limiter
|
||||
log__n = function(x, param_1, param_2)
|
||||
return (log(x + 1) * pow(log(param_1 * x + 1), param_2) / (log(2) * pow(log(param_1 + 1), param_2)))
|
||||
end,
|
||||
}
|
||||
|
||||
function futil.create_limiter(name, param_1, param_2)
|
||||
local f = futil.limiters[name]
|
||||
return function(x)
|
||||
return f(x, param_1, param_2)
|
||||
end
|
||||
end
|
19
mods/futil/util/list.lua
Normal file
19
mods/futil/util/list.lua
Normal file
|
@ -0,0 +1,19 @@
|
|||
function futil.list(iterator)
|
||||
local t = {}
|
||||
local v = iterator()
|
||||
while v do
|
||||
t[#t + 1] = v
|
||||
v = iterator()
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
function futil.list_multiple(iterator)
|
||||
local t = {}
|
||||
local v = { iterator() }
|
||||
while #v > 0 do
|
||||
t[#t + 1] = v
|
||||
v = { iterator() }
|
||||
end
|
||||
return t
|
||||
end
|
162
mods/futil/util/math.lua
Normal file
162
mods/futil/util/math.lua
Normal file
|
@ -0,0 +1,162 @@
|
|||
futil.math = {}
|
||||
|
||||
local floor = math.floor
|
||||
local huge = math.huge
|
||||
local max = math.max
|
||||
local min = math.min
|
||||
|
||||
function futil.math.idiv(a, b)
|
||||
local rem = a % b
|
||||
return (a - rem) / b, rem
|
||||
end
|
||||
|
||||
function futil.math.bound(m, v, M)
|
||||
return max(m, min(v, M))
|
||||
end
|
||||
|
||||
function futil.math.in_bounds(m, v, M)
|
||||
return m <= v and v <= M
|
||||
end
|
||||
|
||||
local in_bounds = futil.math.in_bounds
|
||||
|
||||
function futil.math.is_integer(v)
|
||||
return floor(v) == v
|
||||
end
|
||||
|
||||
local is_integer = futil.math.is_integer
|
||||
|
||||
function futil.math.is_u8(i)
|
||||
return (type(i) == "number" and is_integer(i) and in_bounds(0, i, 0xFF))
|
||||
end
|
||||
|
||||
function futil.math.is_u16(i)
|
||||
return (type(i) == "number" and is_integer(i) and in_bounds(0, i, 0xFFFF))
|
||||
end
|
||||
|
||||
function futil.math.sum(t, initial)
|
||||
local sum
|
||||
local start
|
||||
if initial then
|
||||
sum = initial
|
||||
start = 1
|
||||
else
|
||||
sum = t[1]
|
||||
start = 2
|
||||
end
|
||||
|
||||
for i = start, #t do
|
||||
sum = sum + t[i]
|
||||
end
|
||||
|
||||
return sum
|
||||
end
|
||||
|
||||
function futil.math.isum(i, initial)
|
||||
local sum
|
||||
|
||||
if initial == nil then
|
||||
sum = i()
|
||||
else
|
||||
sum = initial
|
||||
end
|
||||
|
||||
local v = i()
|
||||
|
||||
while v do
|
||||
sum = sum + v
|
||||
v = i()
|
||||
end
|
||||
|
||||
return sum
|
||||
end
|
||||
|
||||
function futil.math.product(t, initial)
|
||||
local product
|
||||
local start
|
||||
if initial then
|
||||
product = initial
|
||||
start = 1
|
||||
else
|
||||
product = t[1]
|
||||
start = 2
|
||||
end
|
||||
|
||||
for i = start, #t do
|
||||
product = product * t[i]
|
||||
end
|
||||
|
||||
return product
|
||||
end
|
||||
|
||||
function futil.math.iproduct(i, initial)
|
||||
local product
|
||||
|
||||
if initial == nil then
|
||||
product = i()
|
||||
else
|
||||
product = initial
|
||||
end
|
||||
|
||||
local v = i()
|
||||
|
||||
while v do
|
||||
product = product * v
|
||||
v = i()
|
||||
end
|
||||
|
||||
return product
|
||||
end
|
||||
|
||||
function futil.math.probabilistic_round(v)
|
||||
return floor(v + math.random())
|
||||
end
|
||||
|
||||
function futil.math.cmp(a, b)
|
||||
return a < b
|
||||
end
|
||||
|
||||
futil.math.deg2rad = math.deg
|
||||
|
||||
futil.math.rad2deg = math.rad
|
||||
|
||||
function futil.math.do_intervals_overlap(min1, max1, min2, max2)
|
||||
return min1 <= max2 and min2 <= max1
|
||||
end
|
||||
|
||||
-- i took one class from kahan and can't stop doing this
|
||||
local function round(n)
|
||||
local d = n % 1
|
||||
local i = n - d
|
||||
|
||||
if i % 2 == 0 then
|
||||
if d <= 0.5 then
|
||||
return i
|
||||
else
|
||||
return i + 1
|
||||
end
|
||||
else
|
||||
if d < 0.5 then
|
||||
return i
|
||||
else
|
||||
return i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function futil.math.round(number, mult)
|
||||
if mult then
|
||||
return round(number / mult) * mult
|
||||
else
|
||||
return round(number)
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO this doesn't handle out-of-bounds exponents
|
||||
function futil.math.to_float32(number)
|
||||
if number == huge or number == -huge or number ~= number then
|
||||
return number
|
||||
end
|
||||
local sign, significand, exponent = ("%a"):format(number):match("^(-?)0x([0-9a-f\\.]+)p([0-9+-]+)$")
|
||||
return tonumber(("%s0x%sp%s"):format(sign, significand:sub(1, 8), exponent))
|
||||
end
|
30
mods/futil/util/matrix.lua
Normal file
30
mods/futil/util/matrix.lua
Normal file
|
@ -0,0 +1,30 @@
|
|||
futil.matrix = {}
|
||||
|
||||
function futil.matrix.multiply(m1, m2)
|
||||
assert(#m1[1] == #m2, "width of first argument must be height of second")
|
||||
local product = {}
|
||||
for i = 1, #m1 do
|
||||
local row = {}
|
||||
for j = 1, #m2[1] do
|
||||
local value = 0
|
||||
for k = 1, #m2 do
|
||||
value = value + m1[i][k] * m2[k][j]
|
||||
end
|
||||
row[j] = value
|
||||
end
|
||||
product[i] = row
|
||||
end
|
||||
return product
|
||||
end
|
||||
|
||||
function futil.matrix.transpose(m)
|
||||
local t = {}
|
||||
for i = 1, #m[1] do
|
||||
local row = {}
|
||||
for j = 1, #m do
|
||||
row[j] = m[j][i]
|
||||
end
|
||||
t[i] = row
|
||||
end
|
||||
return t
|
||||
end
|
51
mods/futil/util/memoization.lua
Normal file
51
mods/futil/util/memoization.lua
Normal file
|
@ -0,0 +1,51 @@
|
|||
local private_state = ...
|
||||
local mod_storage = private_state.mod_storage
|
||||
|
||||
function futil.memoize1(func)
|
||||
local memo = {}
|
||||
return function(arg)
|
||||
if arg == nil then
|
||||
return func(arg)
|
||||
end
|
||||
local rv = memo[arg]
|
||||
|
||||
if not rv then
|
||||
rv = func(arg)
|
||||
memo[arg] = rv
|
||||
end
|
||||
|
||||
return rv
|
||||
end
|
||||
end
|
||||
|
||||
function futil.memoize_dumpable(func)
|
||||
local memo = {}
|
||||
return function(...)
|
||||
local key = dump({ ... })
|
||||
local rv = memo[key]
|
||||
|
||||
if not rv then
|
||||
rv = func(...)
|
||||
memo[key] = rv
|
||||
end
|
||||
|
||||
return rv
|
||||
end
|
||||
end
|
||||
|
||||
function futil.memoize1_modstorage(id, func)
|
||||
local key_format = ("%%s:%s:memoize"):format(id)
|
||||
return function(arg)
|
||||
local key_key = key_format:format(tostring(arg))
|
||||
local rv = mod_storage:get(key_key)
|
||||
|
||||
if not rv then
|
||||
rv = func(arg)
|
||||
mod_storage:set_string(key_key, tostring(rv))
|
||||
end
|
||||
|
||||
return rv
|
||||
end
|
||||
end
|
||||
|
||||
futil.memoize1ms = futil.memoize1_modstorage -- backwards compatibility
|
45
mods/futil/util/memory.lua
Normal file
45
mods/futil/util/memory.lua
Normal file
|
@ -0,0 +1,45 @@
|
|||
-- i have no idea how accurate this is, i use documentation from the below link for a few things
|
||||
-- https://wowwiki-archive.fandom.com/wiki/Lua_object_memory_sizes
|
||||
|
||||
local function estimate_memory_usage(thing, seen)
|
||||
local typ = type(thing)
|
||||
if typ == "nil" then
|
||||
return 0
|
||||
end
|
||||
|
||||
seen = seen or {}
|
||||
if seen[thing] then
|
||||
return 0
|
||||
end
|
||||
seen[thing] = true
|
||||
|
||||
if typ == "boolean" then
|
||||
return 4
|
||||
elseif typ == "number" then
|
||||
return 8 -- this is probably larger?
|
||||
elseif typ == "string" then
|
||||
return 25 + typ:len()
|
||||
elseif typ == "function" then
|
||||
-- TODO: we can calculate the usage of upvalues, but that's complicated
|
||||
return 40
|
||||
elseif typ == "userdata" then
|
||||
return 0 -- this is probably larger
|
||||
elseif typ == "thread" then
|
||||
return 1224 -- this is probably larger
|
||||
elseif typ == "table" then
|
||||
local size = 64
|
||||
for k, v in pairs(thing) do
|
||||
if type(k) == "number" then
|
||||
size = size + 16 + estimate_memory_usage(v, seen)
|
||||
else
|
||||
size = size + 40 + estimate_memory_usage(k, seen) + estimate_memory_usage(v, seen)
|
||||
end
|
||||
end
|
||||
return size
|
||||
else
|
||||
futil.log("warning", "estimate_memory_usage: unknown type %s", typ)
|
||||
return 0 -- ????
|
||||
end
|
||||
end
|
||||
|
||||
futil.estimate_memory_usage = estimate_memory_usage
|
7
mods/futil/util/path.lua
Normal file
7
mods/futil/util/path.lua
Normal file
|
@ -0,0 +1,7 @@
|
|||
function futil.path_concat(...)
|
||||
return table.concat({ ... }, DIR_DELIM)
|
||||
end
|
||||
|
||||
function futil.path_split(path)
|
||||
return string.split(path, DIR_DELIM, true)
|
||||
end
|
43
mods/futil/util/predicates.lua
Normal file
43
mods/futil/util/predicates.lua
Normal file
|
@ -0,0 +1,43 @@
|
|||
function futil.is_nil(v)
|
||||
return v == nil
|
||||
end
|
||||
|
||||
function futil.is_boolean(v)
|
||||
return v == true or v == false
|
||||
end
|
||||
|
||||
function futil.is_number(v)
|
||||
return type(v) == "number"
|
||||
end
|
||||
|
||||
function futil.is_positive(v)
|
||||
return v > 0
|
||||
end
|
||||
|
||||
function futil.is_integer(v)
|
||||
return v % 1 == 0
|
||||
end
|
||||
|
||||
function futil.is_positive_integer(v)
|
||||
return type(v) == "number" and v > 0 and v % 1 == 0
|
||||
end
|
||||
|
||||
function futil.is_string(v)
|
||||
return type(v) == "string"
|
||||
end
|
||||
|
||||
function futil.is_userdata(v)
|
||||
return type(v) == "userdata"
|
||||
end
|
||||
|
||||
function futil.is_function(v)
|
||||
return type(v) == "function"
|
||||
end
|
||||
|
||||
function futil.is_thread(v)
|
||||
return type(v) == "thread"
|
||||
end
|
||||
|
||||
function futil.is_table(v)
|
||||
return type(v) == "table"
|
||||
end
|
84
mods/futil/util/random.lua
Normal file
84
mods/futil/util/random.lua
Normal file
|
@ -0,0 +1,84 @@
|
|||
local f = string.format
|
||||
|
||||
futil.random = {}
|
||||
|
||||
function futil.random.choice(t, random)
|
||||
random = random or math.random
|
||||
return t[random(#t)]
|
||||
end
|
||||
|
||||
function futil.random.weighted_choice(t, random)
|
||||
random = random or math.random
|
||||
local elements, weights = {}, {}
|
||||
local i = 1
|
||||
for element, weight in pairs(t) do
|
||||
elements[i] = element
|
||||
weights[i] = weight
|
||||
i = i + 1
|
||||
end
|
||||
local breaks = futil.list(futil.iterators.accumulate(weights))
|
||||
local value = random() * breaks[#breaks]
|
||||
return elements[futil.bisect.right(breaks, value)]
|
||||
end
|
||||
|
||||
local WeightedChooser = futil.class1()
|
||||
|
||||
function WeightedChooser:_init(t)
|
||||
local elements, weights = {}, {}
|
||||
local i = 1
|
||||
for element, weight in pairs(t) do
|
||||
elements[i] = element
|
||||
weights[i] = weight
|
||||
i = i + 1
|
||||
end
|
||||
self._elements = elements
|
||||
self._breaks = futil.list(futil.iterators.accumulate(weights))
|
||||
end
|
||||
|
||||
function WeightedChooser:next(random)
|
||||
random = random or math.random
|
||||
local breaks = self._breaks
|
||||
local value = random() * breaks[#breaks]
|
||||
return self._elements[futil.bisect.right(breaks, value)]
|
||||
end
|
||||
|
||||
futil.random.WeightedChooser = WeightedChooser
|
||||
|
||||
function futil.random.choice(t, random)
|
||||
assert(#t > 0, "cannot get choice from an empty table")
|
||||
random = random or math.random
|
||||
return t[random(#t)]
|
||||
end
|
||||
|
||||
-- https://stats.stackexchange.com/questions/569647/
|
||||
function futil.random.sample(t, k, random)
|
||||
assert(k <= #t, f("cannot sample %i items from a set of size %i", k, #t))
|
||||
random = random or math.random
|
||||
local sample = {}
|
||||
for i = 1, k do
|
||||
sample[i] = t[i]
|
||||
end
|
||||
for j = k + 1, #t do
|
||||
if random() < k / j then
|
||||
sample[random(1, k)] = t[j]
|
||||
end
|
||||
end
|
||||
|
||||
return sample
|
||||
end
|
||||
|
||||
function futil.random.sample_with_indices(t, k, random)
|
||||
assert(k <= #t, f("cannot sample %i items from a set of size %i", k, #t))
|
||||
random = random or math.random
|
||||
local sample = {}
|
||||
for i = 1, k do
|
||||
sample[i] = { i, t[i] }
|
||||
end
|
||||
for j = k + 1, #t do
|
||||
if random() < k / j then
|
||||
sample[random(1, k)] = { j, t[j] }
|
||||
end
|
||||
end
|
||||
|
||||
return sample
|
||||
end
|
6
mods/futil/util/regex.lua
Normal file
6
mods/futil/util/regex.lua
Normal file
|
@ -0,0 +1,6 @@
|
|||
function futil.is_valid_regex(pattern)
|
||||
return futil.safe_call(function()
|
||||
(""):match(pattern)
|
||||
return true
|
||||
end, false, futil.functional.noop)
|
||||
end
|
109
mods/futil/util/selection.lua
Normal file
109
mods/futil/util/selection.lua
Normal file
|
@ -0,0 +1,109 @@
|
|||
local floor = math.floor
|
||||
local min = math.min
|
||||
local random = math.random
|
||||
|
||||
local swap = futil.table.swap
|
||||
local default_cmp = futil.math.cmp
|
||||
|
||||
futil.selection = {}
|
||||
|
||||
local function partition5(t, left, right, cmp)
|
||||
cmp = cmp or default_cmp
|
||||
local i = left + 1
|
||||
while i <= right do
|
||||
local j = i
|
||||
while j > left and cmp(t[j], t[j - 1]) do
|
||||
swap(t, j - 1, j)
|
||||
j = j - 1
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return floor((left + right) / 2)
|
||||
end
|
||||
|
||||
local function partition(t, left, right, pivot_i, i, cmp)
|
||||
cmp = cmp or default_cmp
|
||||
local pivot_v = t[pivot_i]
|
||||
swap(t, pivot_i, right)
|
||||
local store_i = left
|
||||
for j = left, right - 1 do
|
||||
if cmp(t[j], pivot_v) then
|
||||
swap(t, store_i, j)
|
||||
store_i = store_i + 1
|
||||
end
|
||||
end
|
||||
local store_i_eq = store_i
|
||||
for j = store_i, right - 1 do
|
||||
if t[j] == pivot_v then
|
||||
swap(t, store_i_eq, j)
|
||||
store_i_eq = store_i_eq + 1
|
||||
end
|
||||
end
|
||||
swap(t, right, store_i_eq)
|
||||
if i < store_i then
|
||||
return store_i
|
||||
elseif i <= store_i_eq then
|
||||
return i
|
||||
else
|
||||
return store_i_eq
|
||||
end
|
||||
end
|
||||
|
||||
local function quickselect(t, left, right, i, pivot_alg, cmp)
|
||||
cmp = cmp or default_cmp
|
||||
while true do
|
||||
if left == right then
|
||||
return left
|
||||
end
|
||||
local pivot_i = partition(t, left, right, pivot_alg(t, left, right, cmp), i, cmp)
|
||||
if i == pivot_i then
|
||||
return i
|
||||
elseif i < pivot_i then
|
||||
right = pivot_i - 1
|
||||
else
|
||||
left = pivot_i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
futil.selection.quickselect = quickselect
|
||||
|
||||
futil.selection.pivot = {}
|
||||
|
||||
function futil.selection.pivot.random(t, left, right, cmp)
|
||||
return random(left, right)
|
||||
end
|
||||
|
||||
local function pivot_medians_of_medians(t, left, right, cmp)
|
||||
cmp = cmp or default_cmp
|
||||
if right - left < 5 then
|
||||
return partition5(t, left, right, cmp)
|
||||
end
|
||||
for i = left, right, 5 do
|
||||
local sub_right = min(i + 4, right)
|
||||
local median5 = partition5(t, i, sub_right, cmp)
|
||||
swap(t, median5, left + floor((i - left) / 5))
|
||||
end
|
||||
local mid = floor((right - left) / 10) + left + 1
|
||||
return quickselect(t, left, left + floor((right - left) / 5), mid, pivot_medians_of_medians, cmp)
|
||||
end
|
||||
|
||||
futil.selection.pivot.median_of_medians = pivot_medians_of_medians
|
||||
|
||||
--[[
|
||||
make use of quickselect to munge a table:
|
||||
median_index = math.floor(#t / 2)
|
||||
after calling this,
|
||||
t[1] through t[median_index - 1] will be the elements less than t[median_index]
|
||||
t[median_index] will be the median (or element less-than-the-median for even length tables)
|
||||
t[median_index + 1] through t[#t] will be the elements greater than t[median_index]
|
||||
pivot is a pivot algorithm, defaults to random selection
|
||||
cmp is a comparison function.
|
||||
returns median_index.
|
||||
]]
|
||||
function futil.selection.select(t, pivot_alg, cmp)
|
||||
cmp = cmp or default_cmp
|
||||
pivot_alg = pivot_alg or futil.selection.pivot.random
|
||||
local median_index = math.floor(#t / 2)
|
||||
return quickselect(t, 1, #t, median_index, pivot_alg, cmp)
|
||||
end
|
62
mods/futil/util/string.lua
Normal file
62
mods/futil/util/string.lua
Normal file
|
@ -0,0 +1,62 @@
|
|||
futil.string = {}
|
||||
|
||||
function futil.string.truncate(s, max_length, suffix)
|
||||
suffix = suffix or "..."
|
||||
|
||||
if s:len() > max_length then
|
||||
return s:sub(1, max_length - suffix:len()) .. suffix
|
||||
else
|
||||
return s
|
||||
end
|
||||
end
|
||||
|
||||
function futil.string.lc_cmp(a, b)
|
||||
return a:lower() < b:lower()
|
||||
end
|
||||
|
||||
function futil.string.startswith(s, start, start_i, end_i)
|
||||
return s:sub(start_i or 0, end_i or #s):sub(1, #start) == start
|
||||
end
|
||||
|
||||
local escape_pattern = "([%(%)%.%%%+%-%*%?%[%^%$])"
|
||||
local function escape_regex(str)
|
||||
return str:gsub(escape_pattern, "%%%1")
|
||||
end
|
||||
|
||||
local glob_patterns = {
|
||||
["?"] = ".",
|
||||
["*"] = ".*",
|
||||
}
|
||||
|
||||
local function transform_pattern(pattern)
|
||||
local parts = {}
|
||||
local start = 1
|
||||
for i = 1, #pattern do
|
||||
local glob_pattern = glob_patterns[pattern:sub(i)]
|
||||
if glob_pattern then
|
||||
if start < i then
|
||||
parts[#parts + 1] = escape_regex(pattern:sub(start, i - 1))
|
||||
end
|
||||
parts[#parts + 1] = glob_pattern
|
||||
start = i + 1
|
||||
end
|
||||
end
|
||||
if start < #pattern then
|
||||
parts[#parts + 1] = escape_regex(pattern:sub(start, #pattern))
|
||||
end
|
||||
return table.concat(parts, "")
|
||||
end
|
||||
|
||||
function futil.string.globmatch(str, pattern)
|
||||
return str:match(transform_pattern(pattern))
|
||||
end
|
||||
|
||||
futil.GlobMatcher = futil.class1()
|
||||
|
||||
function futil.GlobMatcher:_init(pattern)
|
||||
self._pattern = transform_pattern(pattern)
|
||||
end
|
||||
|
||||
function futil.GlobMatcher:match(str)
|
||||
return str:match(self._pattern)
|
||||
end
|
188
mods/futil/util/table.lua
Normal file
188
mods/futil/util/table.lua
Normal file
|
@ -0,0 +1,188 @@
|
|||
local default_cmp = futil.math.cmp
|
||||
|
||||
futil.table = {}
|
||||
|
||||
function futil.table.set_all(t1, t2)
|
||||
for k, v in pairs(t2) do
|
||||
t1[k] = v
|
||||
end
|
||||
return t1
|
||||
end
|
||||
|
||||
function futil.table.compose(t1, t2)
|
||||
local t = table.copy(t1)
|
||||
futil.table.set_all(t, t2)
|
||||
return t
|
||||
end
|
||||
|
||||
function futil.table.pairs_by_value(t, cmp)
|
||||
cmp = cmp or default_cmp
|
||||
local s = {}
|
||||
for k, v in pairs(t) do
|
||||
table.insert(s, { k, v })
|
||||
end
|
||||
|
||||
table.sort(s, function(a, b)
|
||||
return cmp(a[2], b[2])
|
||||
end)
|
||||
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
local v = s[i]
|
||||
if v then
|
||||
return unpack(v)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function futil.table.pairs_by_key(t, cmp)
|
||||
cmp = cmp or default_cmp
|
||||
local s = {}
|
||||
for k, v in pairs(t) do
|
||||
table.insert(s, { k, v })
|
||||
end
|
||||
|
||||
table.sort(s, function(a, b)
|
||||
return cmp(a[1], b[1])
|
||||
end)
|
||||
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
local v = s[i]
|
||||
if v then
|
||||
return unpack(v)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function futil.table.size(t)
|
||||
local size = 0
|
||||
for _ in pairs(t) do
|
||||
size = size + 1
|
||||
end
|
||||
return size
|
||||
end
|
||||
|
||||
function futil.table.is_empty(t)
|
||||
return next(t) == nil
|
||||
end
|
||||
|
||||
function futil.table.count_elements(t)
|
||||
local counts = {}
|
||||
for _, item in ipairs(t) do
|
||||
counts[item] = (counts[item] or 0) + 1
|
||||
end
|
||||
return counts
|
||||
end
|
||||
|
||||
function futil.table.sets_intersect(set1, set2)
|
||||
for k in pairs(set1) do
|
||||
if set2[k] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function futil.table.iterate(t)
|
||||
local i = 0
|
||||
return function()
|
||||
i = i + 1
|
||||
return t[i]
|
||||
end
|
||||
end
|
||||
|
||||
function futil.table.reversed(t)
|
||||
local len = #t
|
||||
local reversed = {}
|
||||
|
||||
for i = len, 1, -1 do
|
||||
reversed[len - i + 1] = t[i]
|
||||
end
|
||||
|
||||
return reversed
|
||||
end
|
||||
|
||||
function futil.table.contains(t, value)
|
||||
for _, v in ipairs(t) do
|
||||
if v == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function futil.table.keys(t)
|
||||
local keys = {}
|
||||
for key in pairs(t) do
|
||||
keys[#keys + 1] = key
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
function futil.table.ikeys(t)
|
||||
local key
|
||||
return function()
|
||||
key = next(t, key)
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
function futil.table.values(t)
|
||||
local values = {}
|
||||
for _, value in pairs(t) do
|
||||
values[#values + 1] = value
|
||||
end
|
||||
return values
|
||||
end
|
||||
|
||||
function futil.table.sort_keys(t, cmp)
|
||||
local keys = futil.table.keys(t)
|
||||
table.sort(keys, cmp)
|
||||
return keys
|
||||
end
|
||||
|
||||
-- https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
|
||||
function futil.table.shuffle(t, rnd)
|
||||
rnd = rnd or math.random
|
||||
for i = #t, 2, -1 do
|
||||
local j = rnd(i)
|
||||
t[i], t[j] = t[j], t[i]
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local function swap(t, i, j)
|
||||
t[i], t[j] = t[j], t[i]
|
||||
end
|
||||
|
||||
futil.table.swap = swap
|
||||
|
||||
function futil.table.get(t, key, default)
|
||||
local value = t[key]
|
||||
if value == nil then
|
||||
return default
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
function futil.table.setdefault(t, key, default)
|
||||
local value = t[key]
|
||||
if value == nil then
|
||||
t[key] = default
|
||||
return default
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
function futil.table.pack(...)
|
||||
return { n = select("#", ...), ... }
|
||||
end
|
29
mods/futil/util/time.lua
Normal file
29
mods/futil/util/time.lua
Normal file
|
@ -0,0 +1,29 @@
|
|||
local idiv = futil.math.idiv
|
||||
|
||||
-- convert a number of seconds into a more human-readable value
|
||||
-- ignores the actual passage of time and assumes all years are 365 days
|
||||
function futil.seconds_to_interval(time)
|
||||
local s, m, h, d
|
||||
|
||||
time, s = idiv(time, 60)
|
||||
time, m = idiv(time, 60)
|
||||
time, h = idiv(time, 24)
|
||||
time, d = idiv(time, 365)
|
||||
|
||||
if time ~= 0 then
|
||||
return ("%d years %d days %02d:%02d:%02d"):format(time, d, h, m, s)
|
||||
elseif d ~= 0 then
|
||||
return ("%d days %02d:%02d:%02d"):format(d, h, m, s)
|
||||
elseif h ~= 0 then
|
||||
return ("%02d:%02d:%02d"):format(h, m, s)
|
||||
elseif m ~= 0 then
|
||||
return ("%02d:%02d"):format(m, s)
|
||||
else
|
||||
return ("%ds"):format(s)
|
||||
end
|
||||
end
|
||||
|
||||
-- ISO 8601 date format
|
||||
function futil.format_utc(timestamp)
|
||||
return os.date("!%Y-%m-%dT%TZ", timestamp)
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue