mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 03:25:23 +09:00
Further improvements for ExpUtil
This commit is contained in:
27
.luarc.json
27
.luarc.json
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
|
||||||
"completion.requireSeparator": "/",
|
"completion.requireSeparator": "/",
|
||||||
"doc.privateName": [ "__([_%w]+)" ],
|
"doc.regengine": "glob",
|
||||||
"doc.packageName": [ "_([_%w]+)" ],
|
"doc.privateName": [ "__*" ],
|
||||||
|
"doc.packageName": [ "_*" ],
|
||||||
"runtime.pluginArgs": [
|
"runtime.pluginArgs": [
|
||||||
"--clusterio-modules"
|
"--clusterio-modules"
|
||||||
],
|
],
|
||||||
"diagnostics.unusedLocalExclude": [ "_([_%w]*)", "i", "j", "k", "v" ],
|
"diagnostics.unusedLocalExclude": [ "_*", "i", "j", "k", "v" ],
|
||||||
"diagnostics.groupFileStatus": {
|
"diagnostics.groupFileStatus": {
|
||||||
"ambiguity": "Any",
|
"ambiguity": "Any",
|
||||||
"await": "None",
|
"await": "None",
|
||||||
@@ -32,49 +33,49 @@
|
|||||||
"nameStyle.config": {
|
"nameStyle.config": {
|
||||||
"local_name_style": [{
|
"local_name_style": [{
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(%w+)?",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "snake_case"
|
"$1": "snake_case"
|
||||||
}],
|
}],
|
||||||
"module_local_name_style": [{
|
"module_local_name_style": [{
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "snake_case"
|
"$1": "snake_case"
|
||||||
}, {
|
}, {
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "upper_snake_case"
|
"$1": "upper_snake_case"
|
||||||
}, {
|
}, {
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "pascal_case"
|
"$1": "pascal_case"
|
||||||
}],
|
}],
|
||||||
"function_param_name_style": [{
|
"function_param_name_style": [{
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)?",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "snake_case"
|
"$1": "snake_case"
|
||||||
}],
|
}],
|
||||||
"function_name_style": [{
|
"function_name_style": [{
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "snake_case"
|
"$1": "snake_case"
|
||||||
}],
|
}],
|
||||||
"local_function_name_style": [{
|
"local_function_name_style": [{
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "snake_case"
|
"$1": "snake_case"
|
||||||
}],
|
}],
|
||||||
"table_field_name_style": [{
|
"table_field_name_style": [{
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "snake_case"
|
"$1": "snake_case"
|
||||||
}],
|
}],
|
||||||
"global_variable_name_style": [{
|
"global_variable_name_style": [{
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "snake_case"
|
"$1": "snake_case"
|
||||||
}, {
|
}, {
|
||||||
"type" : "pattern",
|
"type" : "pattern",
|
||||||
"param": "_?_?(\\w+)",
|
"param": "_?_?(\\S*)",
|
||||||
"$1": "upper_snake_case"
|
"$1": "upper_snake_case"
|
||||||
}],
|
}],
|
||||||
"module_name_style": [ "pascal_case", "snake_case" ],
|
"module_name_style": [ "pascal_case", "snake_case" ],
|
||||||
|
|||||||
100
exp_util/module/aabb.lua
Normal file
100
exp_util/module/aabb.lua
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
--[[-- ExpUtil - AABB
|
||||||
|
Provides a set of common functions for working with axis aligned bounding boxes
|
||||||
|
]]
|
||||||
|
|
||||||
|
local floor = math.floor
|
||||||
|
local ceil = math.ceil
|
||||||
|
local min = math.min
|
||||||
|
local max = math.max
|
||||||
|
|
||||||
|
--- @class ExpUtil_AABB
|
||||||
|
local AABB = {}
|
||||||
|
|
||||||
|
--- Check if an area is valid
|
||||||
|
--- @param aabb BoundingBox
|
||||||
|
--- @return boolean # True if the area is valid
|
||||||
|
function AABB.valid(aabb)
|
||||||
|
return aabb.left_top.x < aabb.right_bottom.x and aabb.left_top.y < aabb.right_bottom.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clone an area, allows for safe mutation of an input value
|
||||||
|
--- @param aabb BoundingBox
|
||||||
|
--- @return BoundingBox
|
||||||
|
function AABB.clone(aabb)
|
||||||
|
return {
|
||||||
|
left_top = { x = aabb.left_top.x, y = aabb.left_top.y },
|
||||||
|
right_bottom = { x = aabb.right_bottom.x, y = aabb.right_bottom.y },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Expand an area to be integer aligned, expanding away from 0
|
||||||
|
--- @param aabb BoundingBox
|
||||||
|
--- @return BoundingBox
|
||||||
|
function AABB.expand(aabb)
|
||||||
|
return {
|
||||||
|
left_top = { x = floor(aabb.left_top.x), y = floor(aabb.left_top.y) },
|
||||||
|
right_bottom = { x = ceil(aabb.right_bottom.x), y = ceil(aabb.right_bottom.y) },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Contract an area to be integer aligned, contracting towards 0
|
||||||
|
--- @param aabb BoundingBox
|
||||||
|
--- @return BoundingBox
|
||||||
|
function AABB.contract(aabb)
|
||||||
|
return {
|
||||||
|
left_top = { x = ceil(aabb.left_top.x), y = ceil(aabb.left_top.y) },
|
||||||
|
right_bottom = { x = floor(aabb.right_bottom.x), y = floor(aabb.right_bottom.y) },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Expand an area to include all other areas
|
||||||
|
--- @param aabb BoundingBox
|
||||||
|
--- @param ... BoundingBox
|
||||||
|
--- @return BoundingBox
|
||||||
|
function AABB.union(aabb, ...)
|
||||||
|
local rtn = AABB.clone(aabb)
|
||||||
|
for _, next_aabb in ipairs{ ... } do
|
||||||
|
rtn.left_top.x = min(rtn.left_top.x, next_aabb.left_top.x)
|
||||||
|
rtn.left_top.y = min(rtn.left_top.y, next_aabb.left_top.y)
|
||||||
|
rtn.right_bottom.x = max(rtn.right_bottom.x, next_aabb.right_bottom.x)
|
||||||
|
rtn.right_bottom.y = max(rtn.right_bottom.y, next_aabb.right_bottom.y)
|
||||||
|
end
|
||||||
|
return rtn
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Contract an area to include to the overlap of all areas
|
||||||
|
--- @param aabb BoundingBox
|
||||||
|
--- @param ... BoundingBox
|
||||||
|
--- @return BoundingBox? # Nil if there is no intersection
|
||||||
|
function AABB.intersect(aabb, ...)
|
||||||
|
local rtn = AABB.clone(aabb)
|
||||||
|
for _, next_aabb in ipairs{ ... } do
|
||||||
|
rtn.left_top.x = max(rtn.left_top.x, next_aabb.left_top.x)
|
||||||
|
rtn.left_top.y = max(rtn.left_top.y, next_aabb.left_top.y)
|
||||||
|
rtn.right_bottom.x = min(rtn.right_bottom.x, next_aabb.right_bottom.x)
|
||||||
|
rtn.right_bottom.y = min(rtn.right_bottom.y, next_aabb.right_bottom.y)
|
||||||
|
if not AABB.valid(rtn) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return rtn
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if a point is contained within an area
|
||||||
|
--- @param aabb BoundingBox
|
||||||
|
--- @param point MapPosition
|
||||||
|
--- @return boolean # True if the point is within or on the edge of the bounding box
|
||||||
|
function AABB.contains_point(aabb, point)
|
||||||
|
return point.x >= aabb.left_top.x and point.y >= aabb.left_top.y
|
||||||
|
and point.x <= aabb.right_bottom.x and point.y <= aabb.right_bottom.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check if an area is fulling contained within another area
|
||||||
|
--- @param aabb BoundingBox
|
||||||
|
--- @param other BoundingBox
|
||||||
|
--- @return boolean # True if the point is within or on the edge of the bounding box
|
||||||
|
function AABB.contains_area(aabb, other)
|
||||||
|
return AABB.contains_point(aabb, other.left_top) and AABB.contains_point(aabb, other.right_bottom)
|
||||||
|
end
|
||||||
|
|
||||||
|
return AABB
|
||||||
@@ -81,18 +81,20 @@ fill_table_async({}, "foo", 10):return_to(print_table_size_async)
|
|||||||
]]
|
]]
|
||||||
|
|
||||||
local ExpUtil = require("modules/exp_util")
|
local ExpUtil = require("modules/exp_util")
|
||||||
local Clustorio = require("modules/clusterio/api")
|
|
||||||
|
|
||||||
|
--- @class ExpUtil_Async
|
||||||
local Async = {
|
local Async = {
|
||||||
status = {}, -- Stores the allowed return types from a async function
|
_queue_pressure = {}, --- @type table<string, number> Stores the count of each function in the queue to avoid queue iteration during start_task
|
||||||
events = {}, -- Stores all event handlers for this module
|
_registered = {}, --- @type table<string, AsyncFunctionOpen> Stores a reference to all registered functions
|
||||||
_queue_pressure = {}, -- Stores the count of each function in the queue to avoid queue iteration during start_task
|
|
||||||
_functions = {}, -- Stores a reference to all registered functions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--- @class ExpUtil_Async.status: table<string, Async.Status>
|
||||||
|
--- Stores the allowed return types from a async function
|
||||||
|
Async.status = {}
|
||||||
|
|
||||||
--- @class Async.AsyncFunction
|
--- @class Async.AsyncFunction
|
||||||
--- @field id number The id of this async function
|
--- @field id number The id of this async function
|
||||||
--- @operator call: function
|
--- @operator call: Async.AsyncReturn
|
||||||
Async._function_prototype = {}
|
Async._function_prototype = {}
|
||||||
|
|
||||||
Async._function_metatable = {
|
Async._function_metatable = {
|
||||||
@@ -170,15 +172,17 @@ end
|
|||||||
--- Async Function.
|
--- Async Function.
|
||||||
-- Functions which can be put in storage and used as tasks to be completed over multiple ticks
|
-- Functions which can be put in storage and used as tasks to be completed over multiple ticks
|
||||||
|
|
||||||
|
--- @alias AsyncFunctionOpen fun(...: any): Async.Status?, any?, any?
|
||||||
|
|
||||||
--- Register a new async function
|
--- Register a new async function
|
||||||
--- @param func function The function which becomes the async function
|
--- @param func AsyncFunctionOpen The function which becomes the async function
|
||||||
--- @return Async.AsyncFunction # The newly registered async function
|
--- @return Async.AsyncFunction # The newly registered async function
|
||||||
function Async.register(func)
|
function Async.register(func)
|
||||||
ExpUtil.assert_not_runtime()
|
ExpUtil.assert_not_runtime()
|
||||||
ExpUtil.assert_argument_type(func, "function", 1, "func")
|
ExpUtil.assert_argument_type(func, "function", 1, "func")
|
||||||
|
|
||||||
local id = ExpUtil.get_function_name(func)
|
local id = ExpUtil.get_function_name(func)
|
||||||
Async._functions[id] = func
|
Async._registered[id] = func
|
||||||
Async._queue_pressure[id] = 0
|
Async._queue_pressure[id] = 0
|
||||||
|
|
||||||
return setmetatable({ id = id }, Async._function_metatable)
|
return setmetatable({ id = id }, Async._function_metatable)
|
||||||
@@ -188,7 +192,7 @@ end
|
|||||||
--- @param ... any The arguments to call the function with
|
--- @param ... any The arguments to call the function with
|
||||||
--- @return Async.AsyncReturn
|
--- @return Async.AsyncReturn
|
||||||
function Async._function_prototype:start_soon(...)
|
function Async._function_prototype:start_soon(...)
|
||||||
assert(Async._functions[self.id], "Async function is not registered")
|
assert(Async._registered[self.id], "Async function is not registered")
|
||||||
Async._queue_pressure[self.id] = Async._queue_pressure[self.id] + 1
|
Async._queue_pressure[self.id] = Async._queue_pressure[self.id] + 1
|
||||||
return add_to_next_tick(setmetatable({
|
return add_to_next_tick(setmetatable({
|
||||||
func_id = self.id,
|
func_id = self.id,
|
||||||
@@ -203,7 +207,7 @@ end
|
|||||||
function Async._function_prototype:start_after(ticks, ...)
|
function Async._function_prototype:start_after(ticks, ...)
|
||||||
ExpUtil.assert_argument_type(ticks, "number", 1, "ticks")
|
ExpUtil.assert_argument_type(ticks, "number", 1, "ticks")
|
||||||
assert(ticks > 0, "Ticks must be a positive number")
|
assert(ticks > 0, "Ticks must be a positive number")
|
||||||
assert(Async._functions[self.id], "Async function is not registered")
|
assert(Async._registered[self.id], "Async function is not registered")
|
||||||
Async._queue_pressure[self.id] = Async._queue_pressure[self.id] + 1
|
Async._queue_pressure[self.id] = Async._queue_pressure[self.id] + 1
|
||||||
return add_to_resolve_queue(setmetatable({
|
return add_to_resolve_queue(setmetatable({
|
||||||
func_id = self.id,
|
func_id = self.id,
|
||||||
@@ -216,7 +220,7 @@ end
|
|||||||
--- @param ... any The arguments to call the function with
|
--- @param ... any The arguments to call the function with
|
||||||
--- @return Async.AsyncReturn | nil
|
--- @return Async.AsyncReturn | nil
|
||||||
function Async._function_prototype:start_task(...)
|
function Async._function_prototype:start_task(...)
|
||||||
assert(Async._functions[self.id], "Async function is not registered")
|
assert(Async._registered[self.id], "Async function is not registered")
|
||||||
if Async._queue_pressure[self.id] > 0 then return end
|
if Async._queue_pressure[self.id] > 0 then return end
|
||||||
return self:start_soon(...)
|
return self:start_soon(...)
|
||||||
end
|
end
|
||||||
@@ -225,8 +229,8 @@ end
|
|||||||
--- @param ... any The arguments to call the function with
|
--- @param ... any The arguments to call the function with
|
||||||
--- @return Async.AsyncReturn
|
--- @return Async.AsyncReturn
|
||||||
function Async._function_prototype:start_now(...)
|
function Async._function_prototype:start_now(...)
|
||||||
assert(Async._functions[self.id], "Async function is not registered")
|
assert(Async._registered[self.id], "Async function is not registered")
|
||||||
local status, rtn1, rtn2 = Async._functions[self.id](...)
|
local status, rtn1, rtn2 = Async._registered[self.id](...)
|
||||||
if status == Async.status.continue then
|
if status == Async.status.continue then
|
||||||
return self:start_soon(table.unpack(rtn1))
|
return self:start_soon(table.unpack(rtn1))
|
||||||
elseif status == Async.status.delay then
|
elseif status == Async.status.delay then
|
||||||
@@ -297,7 +301,7 @@ local new_next, new_queue = {}, {} -- File scope to allow for reuse
|
|||||||
--- @param pending Async.AsyncReturn
|
--- @param pending Async.AsyncReturn
|
||||||
--- @param tick number
|
--- @param tick number
|
||||||
local function exec(pending, tick)
|
local function exec(pending, tick)
|
||||||
local async_func = Async._functions[pending.func_id]
|
local async_func = Async._registered[pending.func_id]
|
||||||
if pending.canceled or async_func == nil then return end
|
if pending.canceled or async_func == nil then return end
|
||||||
local status, rtn1, rtn2 = async_func(table.unpack(pending.args))
|
local status, rtn1, rtn2 = async_func(table.unpack(pending.args))
|
||||||
if status == Async.status.continue then
|
if status == Async.status.continue then
|
||||||
@@ -363,6 +367,7 @@ local function on_tick()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- On load, check the queue status and update the pressure values
|
--- On load, check the queue status and update the pressure values
|
||||||
|
--- @package
|
||||||
function Async.on_load()
|
function Async.on_load()
|
||||||
if storage.exp_async_next == nil then return end
|
if storage.exp_async_next == nil then return end
|
||||||
resolve_next = storage.exp_async_next
|
resolve_next = storage.exp_async_next
|
||||||
@@ -389,6 +394,7 @@ function Async.on_load()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- On init and server startup initialise the storage data
|
--- On init and server startup initialise the storage data
|
||||||
|
--- @package
|
||||||
function Async.on_init()
|
function Async.on_init()
|
||||||
if storage.exp_async_next == nil then
|
if storage.exp_async_next == nil then
|
||||||
--- @type Async.AsyncReturn[]
|
--- @type Async.AsyncReturn[]
|
||||||
@@ -399,7 +405,16 @@ function Async.on_init()
|
|||||||
Async.on_load()
|
Async.on_load()
|
||||||
end
|
end
|
||||||
|
|
||||||
Async.events[defines.events.on_tick] = on_tick
|
local e = defines.events
|
||||||
Async.events[Clustorio.events.on_server_startup] = Async.on_init
|
local events = {
|
||||||
|
[e.on_tick] = on_tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
local Clustorio = ExpUtil.optional_require("modules/clusterio/api")
|
||||||
|
if Clustorio then
|
||||||
|
events[Clustorio.events.on_server_startup] = Async.on_init
|
||||||
|
end
|
||||||
|
|
||||||
Async._function_metatable.__call = Async._function_prototype.start_soon
|
Async._function_metatable.__call = Async._function_prototype.start_soon
|
||||||
|
Async.events = events --- @package
|
||||||
return Async
|
return Async
|
||||||
|
|||||||
@@ -1,585 +0,0 @@
|
|||||||
--[[-- Util Module - Common
|
|
||||||
Adds some commonly used functions used in many modules
|
|
||||||
]]
|
|
||||||
|
|
||||||
local type = type
|
|
||||||
local assert = assert
|
|
||||||
local getmetatable = getmetatable
|
|
||||||
local getinfo = debug.getinfo
|
|
||||||
local traceback = debug.traceback
|
|
||||||
local floor = math.floor
|
|
||||||
local round = math.round
|
|
||||||
local concat = table.concat
|
|
||||||
local inspect = table.inspect
|
|
||||||
local format_string = string.format
|
|
||||||
local table_to_json = helpers.table_to_json
|
|
||||||
local write_file = helpers.write_file
|
|
||||||
|
|
||||||
local Common = {
|
|
||||||
--- A large mapping of colour rgb values by their common name
|
|
||||||
color = require("modules/exp_util/include/color"),
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Raise an error if we are not in runtime
|
|
||||||
function Common.assert_not_runtime()
|
|
||||||
assert(package.lifecycle ~= package.lifecycle_stage.runtime, "Can not be called during runtime")
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[local assert_not_closure_fmt = "Can not be called with the closure %s at runtime"
|
|
||||||
--- Raise an error if a function is a closure and we are in runtime
|
|
||||||
-- @tparam function func The function to assert is not a closure if we are in runtime
|
|
||||||
function Common.assert_not_closure(func)
|
|
||||||
assert(package.lifecycle ~= package.lifecycle_stage.runtime, "Can not be called during runtime")
|
|
||||||
local info = getinfo(2, "nu")
|
|
||||||
for i = 1, info.nups do
|
|
||||||
if getupvalue(func, i) ~= "_ENV" then
|
|
||||||
error(assert_not_closure_fmt:format(info.name or "<anonymous>"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end]]
|
|
||||||
|
|
||||||
--- Check the type of a value, also considers LuaObject.object_name and metatable.__class
|
|
||||||
--- Returns true when the check failed and an error should be raised
|
|
||||||
--- @param value any The value to check the type of
|
|
||||||
--- @param type_name string The type name the value should be
|
|
||||||
--- @return boolean failed True if the check failed and an error should be raised
|
|
||||||
--- @return string actual_type The actual type of the value
|
|
||||||
local function check_type(value, type_name)
|
|
||||||
local value_type = type(value) --[[@as string]]
|
|
||||||
if value_type == "userdata" then
|
|
||||||
if type_name == "userdata" then
|
|
||||||
return false, value_type
|
|
||||||
end
|
|
||||||
value_type = value.object_name
|
|
||||||
elseif value_type == "table" then
|
|
||||||
if type_name == "table" then
|
|
||||||
return false, value_type
|
|
||||||
end
|
|
||||||
local mt = getmetatable(value)
|
|
||||||
if mt and mt.__class then
|
|
||||||
value_type = mt.__class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return value == nil or value_type ~= type_name, value_type
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Get the name of a class or object, better than just using type
|
|
||||||
--- @param value any The value to get the class of
|
|
||||||
--- @return string # One of type, object_name, __class
|
|
||||||
function Common.get_class_name(value)
|
|
||||||
local value_type = type(value) --[[@as string]]
|
|
||||||
if value_type == "userdata" then
|
|
||||||
return value.object_name
|
|
||||||
elseif value_type == "table" then
|
|
||||||
local mt = getmetatable(value)
|
|
||||||
if mt and mt.__class then
|
|
||||||
return mt.__class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return value_type
|
|
||||||
end
|
|
||||||
|
|
||||||
local assert_type_fmt = "%s expected to be of type %s but got %s"
|
|
||||||
--- Raise an error if the type of a value is not as expected
|
|
||||||
--- @param value any The value to assert the type of
|
|
||||||
--- @param type_name string The name of the type that value is expected to be
|
|
||||||
--- @param value_name string? The name of the value being tested, this is included in the error message
|
|
||||||
function Common.assert_type(value, type_name, value_name)
|
|
||||||
local failed, actual_type = check_type(value, type_name)
|
|
||||||
if failed then
|
|
||||||
error(assert_type_fmt:format(value_name or "Value", type_name, actual_type), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local assert_argument_fmt = "Bad argument #%d to %s; %s expected to be of type %s but got %s"
|
|
||||||
--- Raise an error if the type of any argument is not as expected, more performant than assert_argument_types, but requires more manual input
|
|
||||||
--- @param arg_value any The argument to assert the type of
|
|
||||||
--- @param type_name string The name of the type that value is expected to be
|
|
||||||
--- @param arg_index number The index of the argument being tested, this is included in the error message
|
|
||||||
--- @param arg_name string? The name of the argument being tested, this is included in the error message
|
|
||||||
function Common.assert_argument_type(arg_value, type_name, arg_index, arg_name)
|
|
||||||
local failed, actual_type = check_type(arg_value, type_name)
|
|
||||||
if failed then
|
|
||||||
local func_name = getinfo(2, "n").name or "<anonymous>"
|
|
||||||
error(assert_argument_fmt:format(arg_index, func_name, arg_name or "Argument", type_name, actual_type), 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Write a luu table to a file as a json string, note the defaults are different to write_file
|
|
||||||
--- @param path string The path to write the json to
|
|
||||||
--- @param tbl table The table to write to file
|
|
||||||
--- @param overwrite boolean? When true the json replaces the full contents of the file
|
|
||||||
--- @param player_index number? The player's machine to write on, -1 means all, 0 is default means host only
|
|
||||||
--- @return nil
|
|
||||||
function Common.write_json(path, tbl, overwrite, player_index)
|
|
||||||
if player_index == -1 then
|
|
||||||
return write_file(path, table_to_json(tbl) .. "\n", not overwrite)
|
|
||||||
end
|
|
||||||
return write_file(path, table_to_json(tbl) .. "\n", not overwrite, player_index or 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Clear a file by replacing its contents with an empty string
|
|
||||||
--- @param path string The path to clear the contents of
|
|
||||||
--- @param player_index number? The player's machine to write on, -1 means all, 0 is default and means host only
|
|
||||||
--- @return nil
|
|
||||||
function Common.clear_file(path, player_index)
|
|
||||||
if player_index == -1 then
|
|
||||||
return write_file(path, "", false)
|
|
||||||
end
|
|
||||||
return write_file(path, "", false, player_index or 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Same as require but will return nil if the module does not exist, all other errors will propagate to the caller
|
|
||||||
--- @param module_path string The path to the module to require, same syntax as normal require
|
|
||||||
--- @return any # The contents of the module, or nil if the module does not exist or did not return a value
|
|
||||||
--- @deprecated
|
|
||||||
function Common.optional_require(module_path)
|
|
||||||
local success, rtn = xpcall(require, traceback, module_path)
|
|
||||||
if success then return rtn end
|
|
||||||
if not rtn:find("no such file", 0, true) then
|
|
||||||
error(rtn, 2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns a desync sale filepath for a given stack frame, default is the current file
|
|
||||||
--- @param level number? The level of the stack to get the file of, a value of 1 is the caller of this function
|
|
||||||
--- @return string # The relative filepath of the given stack frame
|
|
||||||
function Common.safe_file_path(level)
|
|
||||||
local debug_info = getinfo((level or 1) + 1, "Sn")
|
|
||||||
local safe_source = debug_info.source:find("@__level__")
|
|
||||||
return safe_source == 1 and debug_info.short_src:sub(10, -5) or debug_info.source
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the name of your module, this assumes your module is stored within /modules (which it is for clustorio)
|
|
||||||
--- @param level number? The level of the stack to get the module of, a value of 1 is the caller of this function
|
|
||||||
--- @return string # The name of the module at the given stack frame
|
|
||||||
function Common.get_module_name(level)
|
|
||||||
local file_within_module = getinfo((level or 1) + 1, "S").short_src:sub(18, -5)
|
|
||||||
local next_slash = file_within_module:find("/")
|
|
||||||
if next_slash then
|
|
||||||
return file_within_module:sub(1, next_slash - 1)
|
|
||||||
else
|
|
||||||
return file_within_module
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the name of a function in a safe and consistent format
|
|
||||||
--- @param func number | function The level of the stack to get the name of, a value of 1 is the caller of this function
|
|
||||||
--- @param raw boolean? When true there will not be any < > around the name
|
|
||||||
--- @return string # The name of the function at the given stack frame or provided as an argument
|
|
||||||
function Common.get_function_name(func, raw)
|
|
||||||
local debug_info = getinfo(func, "Sn")
|
|
||||||
local safe_source = debug_info.source:find("@__level__")
|
|
||||||
local file_name = safe_source == 1 and debug_info.short_src:sub(10, -5) or debug_info.source
|
|
||||||
local func_name = debug_info.name or debug_info.linedefined
|
|
||||||
if raw then return file_name .. ":" .. func_name end
|
|
||||||
return "<" .. file_name .. ":" .. func_name .. ">"
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Attempt a simple autocomplete search from a set of options
|
|
||||||
--- @param options table The table representing the possible options which can be selected
|
|
||||||
--- @param input string The user input string which should be matched to an option
|
|
||||||
--- @param use_key boolean? When true the keys will be searched, when false the values will be searched
|
|
||||||
--- @param rtn_key boolean? When true the selected key will be returned, when false the selected value will be returned
|
|
||||||
--- @return any # The selected key or value which first matches the input text
|
|
||||||
function Common.auto_complete(options, input, use_key, rtn_key)
|
|
||||||
input = input:lower()
|
|
||||||
if use_key then
|
|
||||||
for k, v in pairs(options) do
|
|
||||||
if k:lower():find(input) then
|
|
||||||
if rtn_key then return k else return v end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for k, v in pairs(options) do
|
|
||||||
if v:lower():find(input) then
|
|
||||||
if rtn_key then return k else return v end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Formats any value into a safe representation, useful with inspect
|
|
||||||
--- @param value any The value to be formatted
|
|
||||||
--- @return LocalisedString # The formatted version of the value
|
|
||||||
--- @return boolean # True if value is a locale string, nil otherwise
|
|
||||||
function Common.safe_value(value)
|
|
||||||
if type(value) == "table" then
|
|
||||||
local v1 = value[1]
|
|
||||||
local str = tostring(value)
|
|
||||||
if type(v1) == "string" and not v1:find("%s")
|
|
||||||
and (v1 == "" or v1 == "?" or v1:find(".+[.].+")) then
|
|
||||||
return value, true -- locale string
|
|
||||||
elseif str ~= "table" then
|
|
||||||
return str, false -- has __tostring metamethod
|
|
||||||
else -- plain table
|
|
||||||
return value, false
|
|
||||||
end
|
|
||||||
elseif type(value) == "function" then -- function
|
|
||||||
return "<function:" .. Common.get_function_name(value, true) .. ">", false
|
|
||||||
else -- not: table or function
|
|
||||||
return tostring(value), false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @class Common.format_any_param
|
|
||||||
--- @field as_json boolean? If table values should be returned as json
|
|
||||||
--- @field max_line_count number? If table newline count exceeds provided then it will be inlined, if 0 then always inline
|
|
||||||
--- @field no_locale_strings boolean? If value is a locale string it will be treated like a normal table
|
|
||||||
--- @field depth number? The max depth to process tables to, the default is 5
|
|
||||||
|
|
||||||
--- Formats any value to be presented in a safe and human readable format
|
|
||||||
--- @param value any The value to be formatted
|
|
||||||
--- @param options Common.format_any_param? Options for the formatter
|
|
||||||
--- @return LocalisedString # The formatted version of the value
|
|
||||||
function Common.format_any(value, options)
|
|
||||||
options = options or {}
|
|
||||||
local formatted, is_locale_string = Common.safe_value(value)
|
|
||||||
if type(formatted) == "table" and (not is_locale_string or options.no_locale_strings) then
|
|
||||||
if options.as_json then
|
|
||||||
local success, rtn = pcall(table_to_json, value)
|
|
||||||
if success then return rtn end
|
|
||||||
end
|
|
||||||
if options.max_line_count ~= 0 then
|
|
||||||
local rtn = inspect(value, { depth = options.depth or 5, indent = " ", newline = "\n", process = Common.safe_value })
|
|
||||||
if options.max_line_count == nil or select(2, rtn:gsub("\n", "")) < options.max_line_count then return rtn end
|
|
||||||
end
|
|
||||||
return inspect(value, { depth = options.depth or 5, indent = "", newline = "", process = Common.safe_value })
|
|
||||||
end
|
|
||||||
return formatted
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @alias Common.format_time_param_format "short" | "long" | "clock"
|
|
||||||
|
|
||||||
--- @class Common.format_time_param_units
|
|
||||||
--- @field days boolean? True if days are included
|
|
||||||
--- @field hours boolean? True if hours are included
|
|
||||||
--- @field minutes boolean? True if minutes are included
|
|
||||||
--- @field seconds boolean? True if seconds are included
|
|
||||||
|
|
||||||
--- Format a tick value into one of a selection of pre-defined formats (short, long, clock)
|
|
||||||
--- @param ticks number|nil The number of ticks which will be represented, can be any duration or time value
|
|
||||||
--- @param format Common.format_time_param_format format to display, must be one of: short, long, clock
|
|
||||||
--- @param units Common.format_time_param_units A table selecting which units should be displayed, options are: days, hours, minutes, seconds
|
|
||||||
--- @return string # The ticks formatted into a string of the desired format
|
|
||||||
function Common.format_time(ticks, format, units)
|
|
||||||
--- @type string | number, string | number, string | number, string | number
|
|
||||||
local rtn_days, rtn_hours, rtn_minutes, rtn_seconds = "--", "--", "--", "--"
|
|
||||||
|
|
||||||
if ticks ~= nil then
|
|
||||||
-- Calculate the values to be determine the display values
|
|
||||||
local max_days, max_hours, max_minutes, max_seconds = ticks / 5184000, ticks / 216000, ticks / 3600, ticks / 60
|
|
||||||
local days, hours = max_days, max_hours - floor(max_days) * 24
|
|
||||||
local minutes, seconds = max_minutes - floor(max_hours) * 60, max_seconds - floor(max_minutes) * 60
|
|
||||||
|
|
||||||
-- Calculate rhw units to be displayed
|
|
||||||
rtn_days, rtn_hours, rtn_minutes, rtn_seconds = floor(days), floor(hours), floor(minutes), floor(seconds)
|
|
||||||
if not units.days then rtn_hours = rtn_hours + rtn_days * 24 end
|
|
||||||
if not units.hours then rtn_minutes = rtn_minutes + rtn_hours * 60 end
|
|
||||||
if not units.minutes then rtn_seconds = rtn_seconds + rtn_minutes * 60 end
|
|
||||||
--- @diagnostic enable: cast-local-type
|
|
||||||
end
|
|
||||||
|
|
||||||
local rtn = {}
|
|
||||||
if format == "clock" then
|
|
||||||
-- Example 12:34:56 or --:--:--
|
|
||||||
if units.days then rtn[#rtn + 1] = rtn_days end
|
|
||||||
if units.hours then rtn[#rtn + 1] = rtn_hours end
|
|
||||||
if units.minutes then rtn[#rtn + 1] = rtn_minutes end
|
|
||||||
if units.seconds then rtn[#rtn + 1] = rtn_seconds end
|
|
||||||
return concat(rtn, ":")
|
|
||||||
elseif format == "short" then
|
|
||||||
-- Example 12d 34h 56m or --d --h --m
|
|
||||||
if units.days then rtn[#rtn + 1] = rtn_days .. "d" end
|
|
||||||
if units.hours then rtn[#rtn + 1] = rtn_hours .. "h" end
|
|
||||||
if units.minutes then rtn[#rtn + 1] = rtn_minutes .. "m" end
|
|
||||||
if units.seconds then rtn[#rtn + 1] = rtn_seconds .. "s" end
|
|
||||||
return concat(rtn, " ")
|
|
||||||
else
|
|
||||||
-- Example 12 days, 34 hours, and 56 minutes or -- days, -- hours, and -- minutes
|
|
||||||
if units.days then rtn[#rtn + 1] = rtn_days .. " days" end
|
|
||||||
if units.hours then rtn[#rtn + 1] = rtn_hours .. " hours" end
|
|
||||||
if units.minutes then rtn[#rtn + 1] = rtn_minutes .. " minutes" end
|
|
||||||
if units.seconds then rtn[#rtn + 1] = rtn_seconds .. " seconds" end
|
|
||||||
rtn[#rtn] = "and " .. rtn[#rtn]
|
|
||||||
return concat(rtn, ", ")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Format a tick value into one of a selection of pre-defined formats (short, long, clock)
|
|
||||||
--- @param ticks number|nil The number of ticks which will be represented, can be any duration or time value
|
|
||||||
--- @param format Common.format_time_param_format format to display, must be one of: short, long, clock
|
|
||||||
--- @param units Common.format_time_param_units A table selecting which units should be displayed, options are: days, hours, minutes, seconds
|
|
||||||
--- @return LocalisedString # The ticks formatted into a string of the desired format
|
|
||||||
function Common.format_time_locale(ticks, format, units)
|
|
||||||
--- @type string | number, string | number, string | number, string | number
|
|
||||||
local rtn_days, rtn_hours, rtn_minutes, rtn_seconds = "--", "--", "--", "--"
|
|
||||||
|
|
||||||
if ticks ~= nil then
|
|
||||||
-- Calculate the values to be determine the display values
|
|
||||||
local max_days, max_hours, max_minutes, max_seconds = ticks / 5184000, ticks / 216000, ticks / 3600, ticks / 60
|
|
||||||
local days, hours = max_days, max_hours - floor(max_days) * 24
|
|
||||||
local minutes, seconds = max_minutes - floor(max_hours) * 60, max_seconds - floor(max_minutes) * 60
|
|
||||||
|
|
||||||
-- Calculate rhw units to be displayed
|
|
||||||
rtn_days, rtn_hours, rtn_minutes, rtn_seconds = floor(days), floor(hours), floor(minutes), floor(seconds)
|
|
||||||
if not units.days then rtn_hours = rtn_hours + rtn_days * 24 end
|
|
||||||
if not units.hours then rtn_minutes = rtn_minutes + rtn_hours * 60 end
|
|
||||||
if not units.minutes then rtn_seconds = rtn_seconds + rtn_minutes * 60 end
|
|
||||||
end
|
|
||||||
|
|
||||||
local rtn = {}
|
|
||||||
local join = ", " --- @type LocalisedString
|
|
||||||
if format == "clock" then
|
|
||||||
-- Example 12:34:56 or --:--:--
|
|
||||||
if units.days then rtn[#rtn + 1] = rtn_days end
|
|
||||||
if units.hours then rtn[#rtn + 1] = rtn_hours end
|
|
||||||
if units.minutes then rtn[#rtn + 1] = rtn_minutes end
|
|
||||||
if units.seconds then rtn[#rtn + 1] = rtn_seconds end
|
|
||||||
join = { "colon" }
|
|
||||||
elseif format == "short" then
|
|
||||||
-- Example 12d 34h 56m or --d --h --m
|
|
||||||
if units.days then rtn[#rtn + 1] = { "?", { "time-symbol-days-short", rtn_days }, rtn_days .. "d" } end
|
|
||||||
if units.hours then rtn[#rtn + 1] = { "time-symbol-hours-short", rtn_hours } end
|
|
||||||
if units.minutes then rtn[#rtn + 1] = { "time-symbol-minutes-short", rtn_minutes } end
|
|
||||||
if units.seconds then rtn[#rtn + 1] = { "time-symbol-seconds-short", rtn_seconds } end
|
|
||||||
join = " "
|
|
||||||
else
|
|
||||||
-- Example 12 days, 34 hours, and 56 minutes or -- days, -- hours, and -- minutes
|
|
||||||
if units.days then rtn[#rtn + 1] = { "days", rtn_days } end
|
|
||||||
if units.hours then rtn[#rtn + 1] = { "hours", rtn_hours } end
|
|
||||||
if units.minutes then rtn[#rtn + 1] = { "minutes", rtn_minutes } end
|
|
||||||
if units.seconds then rtn[#rtn + 1] = { "seconds", rtn_seconds } end
|
|
||||||
rtn[#rtn] = { "", { "and" }, " ", rtn[#rtn] }
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @type LocalisedString
|
|
||||||
local joined = { "" }
|
|
||||||
for k, v in ipairs(rtn) do
|
|
||||||
joined[2 * k] = v
|
|
||||||
joined[2 * k + 1] = join
|
|
||||||
end
|
|
||||||
|
|
||||||
return joined
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @class Common.format_time_factory_param: Common.format_time_param_units
|
|
||||||
--- @field format Common.format_time_param_format The format to use
|
|
||||||
--- @field coefficient number? If present will multiply the input by this amount before formatting
|
|
||||||
|
|
||||||
--- Create a formatter to format a tick value into one of a selection of pre-defined formats (short, long, clock)
|
|
||||||
--- @param options Common.format_time_factory_param
|
|
||||||
--- @return fun(ticks: number|nil): string
|
|
||||||
function Common.format_time_factory(options)
|
|
||||||
local formatter, format, coefficient = Common.format_time, options.format, options.coefficient
|
|
||||||
if coefficient then
|
|
||||||
return function(ticks) return formatter(ticks and ticks * coefficient or nil, format, options) end
|
|
||||||
end
|
|
||||||
return function(ticks) return formatter(ticks, format, options) end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Create a formatter to format a tick value into one of a selection of pre-defined formats (short, long, clock)
|
|
||||||
--- @param options Common.format_time_factory_param
|
|
||||||
--- @return fun(ticks: number|nil): LocalisedString
|
|
||||||
function Common.format_time_factory_locale(options)
|
|
||||||
local formatter, format, coefficient = Common.format_time_locale, options.format, options.coefficient
|
|
||||||
if coefficient then
|
|
||||||
return function(ticks) return formatter(ticks and ticks * coefficient or nil, format, options) end
|
|
||||||
end
|
|
||||||
return function(ticks) return formatter(ticks, format, options) end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @class Common.get_or_create_storage_cache
|
|
||||||
--- @field entities LuaEntity[] Array of found entities matching the search
|
|
||||||
--- @field current number The current index within the entity array
|
|
||||||
--- @field count number The number of entities found
|
|
||||||
|
|
||||||
--- @class Common.get_or_create_storage_param: EntitySearchFilters
|
|
||||||
--- @field item ItemStackIdentification The item stack that must be insertable
|
|
||||||
--- @field surface LuaSurface The surface to search for targets on
|
|
||||||
--- @field allow_creation boolean? If new entities can be create to store the items
|
|
||||||
--- @field cache Common.get_or_create_storage_cache? Internal search cache passed between subsequent calls
|
|
||||||
|
|
||||||
--- Find, or optionally create, a storage entity which a stack can be inserted into
|
|
||||||
--- @param options Common.get_or_create_storage_param
|
|
||||||
--- @return LuaEntity
|
|
||||||
function Common.get_storage_for_stack(options)
|
|
||||||
local surface = assert(options.surface, "A surface must be provided")
|
|
||||||
local item = assert(options.item, "An item stack must be provided")
|
|
||||||
|
|
||||||
-- Perform a search if on has not been done already
|
|
||||||
local cache = options.cache
|
|
||||||
if cache then
|
|
||||||
local entities = surface.find_entities_filtered(options)
|
|
||||||
cache = {
|
|
||||||
entities = entities,
|
|
||||||
count = #entities,
|
|
||||||
current = 0,
|
|
||||||
}
|
|
||||||
options.cache = cache
|
|
||||||
end
|
|
||||||
--- @cast cache -nil
|
|
||||||
|
|
||||||
-- Find a valid entity from the search results
|
|
||||||
local current, count, entities = cache.current, cache.count, cache.entities
|
|
||||||
for i = 1, cache.count do
|
|
||||||
local entity = entities[((current + i - 1) % count) + 1]
|
|
||||||
if entity.can_insert(item) then
|
|
||||||
cache.current = current + 1
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- No entity was found so one needs to be created
|
|
||||||
assert(options.allow_creation, "Unable to find valid entity, consider enabling allow_creation")
|
|
||||||
assert(options.name, "Name must be provided to allow creation of new entities")
|
|
||||||
|
|
||||||
local position
|
|
||||||
if options.position then
|
|
||||||
position = surface.find_non_colliding_position(options.name, options.position, options.radius or 0, 1, true)
|
|
||||||
elseif options.area then
|
|
||||||
position = surface.find_non_colliding_position_in_box(options.name, options.area, 1, true)
|
|
||||||
else
|
|
||||||
position = surface.find_non_colliding_position(options.name, { 0, 0 }, 0, 1, true)
|
|
||||||
end
|
|
||||||
assert(position, "Failed to find valid location")
|
|
||||||
|
|
||||||
local entity = surface.create_entity{ name = options.name, position = position, force = options.force or "neutral" }
|
|
||||||
assert(entity, "Failed to create a new entity")
|
|
||||||
|
|
||||||
cache.count = count + 1
|
|
||||||
entities[count] = entity
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @class Common.copy_items_to_surface_param: Common.get_or_create_storage_param
|
|
||||||
--- @field items ItemStackIdentification[] | LuaInventory The item stacks to copy
|
|
||||||
|
|
||||||
--- Insert a copy of the given items into the found entities. If no entities are found then they will be created if possible.
|
|
||||||
--- @param options Common.copy_items_to_surface_param
|
|
||||||
--- @return LuaEntity # The last entity inserted into
|
|
||||||
function Common.copy_items_to_surface(options)
|
|
||||||
local entity
|
|
||||||
for item_index = 1, #options.items do
|
|
||||||
options.item = options.items[item_index]
|
|
||||||
entity = Common.get_storage_for_stack(options)
|
|
||||||
entity.insert(options.item)
|
|
||||||
end
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @class Common.move_items_to_surface_param: Common.get_or_create_storage_param
|
|
||||||
--- @field items LuaItemStack[] The item stacks to move
|
|
||||||
|
|
||||||
--- Insert a copy of the given items into the found entities. If no entities are found then they will be created if possible.
|
|
||||||
--- @param options Common.move_items_to_surface_param
|
|
||||||
--- @return LuaEntity # The last entity inserted into
|
|
||||||
function Common.move_items_to_surface(options)
|
|
||||||
local entity
|
|
||||||
for item_index = 1, #options.items do
|
|
||||||
options.item = options.items[item_index]
|
|
||||||
entity = Common.get_storage_for_stack(options)
|
|
||||||
entity.insert(options.item)
|
|
||||||
options.item.clear()
|
|
||||||
end
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
|
|
||||||
--- @class Common.transfer_inventory_to_surface_param: Common.copy_items_to_surface_param
|
|
||||||
--- @field inventory LuaInventory The inventory to transfer
|
|
||||||
|
|
||||||
--- Move the given inventory into the found entities. If no entities are found then they will be created if possible.
|
|
||||||
--- @param options Common.transfer_inventory_to_surface_param
|
|
||||||
--- @return LuaEntity # The last entity inserted into
|
|
||||||
function Common.transfer_inventory_to_surface(options)
|
|
||||||
options.items = options.inventory
|
|
||||||
local entity = Common.copy_items_to_surface(options)
|
|
||||||
options.inventory.clear()
|
|
||||||
return entity
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Create an enum table from a set of strings, can use custom indexes to change base
|
|
||||||
--- @param values { [number]: string }
|
|
||||||
--- @return { [string | number]: string | number }
|
|
||||||
function Common.enum(values)
|
|
||||||
local enum = {}
|
|
||||||
|
|
||||||
local index = 0 -- Real index within values
|
|
||||||
local offset = 0 -- Offset from base for next index
|
|
||||||
local base = 0 -- Start point for offset
|
|
||||||
for k, v in pairs(values) do
|
|
||||||
index = index + 1
|
|
||||||
if k ~= index then
|
|
||||||
offset = 0
|
|
||||||
base = k
|
|
||||||
end
|
|
||||||
enum[base + offset] = v
|
|
||||||
offset = offset + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
for k, v in pairs(enum) do
|
|
||||||
if type(k) == "number" then
|
|
||||||
enum[v] = k
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return enum
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns a string for a number with comma separators
|
|
||||||
--- @param n number
|
|
||||||
--- @return string
|
|
||||||
function Common.comma_value(n) -- credit http://richard.warburton.it
|
|
||||||
local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$")
|
|
||||||
return left .. (num:reverse():gsub("(%d%d%d)", "%1, "):reverse()) .. right
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns a message formatted for game chat using rich text colour tags
|
|
||||||
--- @param message string
|
|
||||||
--- @param color Color
|
|
||||||
--- @return string
|
|
||||||
function Common.format_rich_text_color(message, color)
|
|
||||||
return format_string(
|
|
||||||
"[color=%s,%s,%s]%s[/color]",
|
|
||||||
round(color.r or color[1] or 0, 3),
|
|
||||||
round(color.g or color[2] or 0, 3),
|
|
||||||
round(color.b or color[3] or 0, 3),
|
|
||||||
message
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns a message formatted for game chat using rich text colour tags
|
|
||||||
--- @param message string
|
|
||||||
--- @param color Color
|
|
||||||
--- @return LocalisedString
|
|
||||||
function Common.format_rich_text_color_locale(message, color)
|
|
||||||
return {
|
|
||||||
"color-tag",
|
|
||||||
round(color.r or color[1] or 0, 3),
|
|
||||||
round(color.g or color[2] or 0, 3),
|
|
||||||
round(color.b or color[3] or 0, 3),
|
|
||||||
message
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Formats a players name using rich text color
|
|
||||||
--- @param player PlayerIdentification?
|
|
||||||
--- @return string
|
|
||||||
function Common.format_player_name(player)
|
|
||||||
local valid_player = type(player) == "userdata" and player or game.get_player(player --[[@as string|number]]) --[[@as LuaPlayer?]]
|
|
||||||
local player_name = valid_player and valid_player.name or "<Server>"
|
|
||||||
local player_chat_colour = valid_player and valid_player.chat_color or Common.color.white
|
|
||||||
return Common.format_rich_text_color(player_name, player_chat_colour)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Formats a players name using rich text color
|
|
||||||
--- @param player PlayerIdentification?
|
|
||||||
--- @return LocalisedString
|
|
||||||
function Common.format_player_name_locale(player)
|
|
||||||
local valid_player = type(player) == "userdata" and player or game.get_player(player --[[@as string|number]]) --[[@as LuaPlayer?]]
|
|
||||||
local player_name = valid_player and valid_player.name or "<Server>"
|
|
||||||
local player_chat_colour = valid_player and valid_player.chat_color or Common.color.white
|
|
||||||
return Common.format_rich_text_color_locale(player_name, player_chat_colour)
|
|
||||||
end
|
|
||||||
|
|
||||||
return Common
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
--[[-- Util Module - FlyingText
|
--[[-- ExpUtil - FlyingText
|
||||||
Provides a method of creating floating text and tags in the world
|
Provides a method of creating floating text and tags in the world
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
--- @class ExpUtil_FlyingText
|
||||||
local FlyingText = {}
|
local FlyingText = {}
|
||||||
|
|
||||||
FlyingText.color = require("modules/exp_util/include/color")
|
FlyingText.color = require("modules/exp_util/include/color")
|
||||||
|
|
||||||
--- @class FlyingText.create_param:LuaPlayer.create_local_flying_text_param
|
--- @class FlyingText.create_param:LuaPlayer.create_local_flying_text_param
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
--- @diagnostic disable
|
||||||
|
|
||||||
local inspect = {
|
local inspect = {
|
||||||
_VERSION = "inspect.lua 3.1.0",
|
_VERSION = "inspect.lua 3.1.0",
|
||||||
_URL = "http://github.com/kikito/inspect.lua",
|
_URL = "http://github.com/kikito/inspect.lua",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package.lifecycle_stage = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
--- Stores the current lifecycle stage we are in, compare values against package.lifecycle_stage
|
--- Stores the current lifecycle stage we are in, compare values against package.lifecycle_stage
|
||||||
|
--- See also: https://forums.factorio.com/viewtopic.php?f=28&t=115622
|
||||||
package.lifecycle = package.lifecycle_stage.control
|
package.lifecycle = package.lifecycle_stage.control
|
||||||
|
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
@@ -25,6 +26,7 @@ return setmetatable({
|
|||||||
on_configuration_changed = function() package.lifecycle = package.lifecycle_stage.config_change end,
|
on_configuration_changed = function() package.lifecycle = package.lifecycle_stage.config_change end,
|
||||||
events = {
|
events = {
|
||||||
-- TODO find a reliable way to set to runtime because currently it will desync if accessed before player joined
|
-- TODO find a reliable way to set to runtime because currently it will desync if accessed before player joined
|
||||||
|
-- TODO make clusterio optional dependency
|
||||||
[defines.events.on_player_joined_game] = function() package.lifecycle = package.lifecycle_stage.runtime end,
|
[defines.events.on_player_joined_game] = function() package.lifecycle = package.lifecycle_stage.runtime end,
|
||||||
[Clustorio.events.on_server_startup] = function() package.lifecycle = package.lifecycle_stage.runtime end,
|
[Clustorio.events.on_server_startup] = function() package.lifecycle = package.lifecycle_stage.runtime end,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
-- luacheck:ignore global require
|
-- luacheck:ignore global require
|
||||||
|
--- @diagnostic disable
|
||||||
|
|
||||||
local package = require("modules/exp_util/include/package")
|
local package = require("modules/exp_util/include/package")
|
||||||
local loaded = package.loaded
|
local loaded = package.loaded
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
---@diagnostic disable: duplicate-set-field
|
--- @diagnostic disable: duplicate-set-field
|
||||||
-- luacheck:ignore global table
|
-- luacheck:ignore global table
|
||||||
|
|
||||||
local random = math.random
|
local random = math.random
|
||||||
local floor = math.floor
|
local floor = math.floor
|
||||||
local remove = table.remove
|
local remove = table.remove
|
||||||
|
local unpack = table.unpack
|
||||||
local tonumber = tonumber
|
local tonumber = tonumber
|
||||||
local pairs = pairs
|
local pairs = pairs
|
||||||
local table_size = table_size
|
local table_size = table_size
|
||||||
|
|
||||||
--- Adds all keys of the source table to destination table as a shallow copy
|
--- Adds all keys of the source table to destination table as a shallow copy
|
||||||
-- @tparam table dst The table to insert into
|
--- @generic K, V
|
||||||
-- @tparam table src The table to insert from
|
--- @param dst table<K, V> Table to insert into
|
||||||
|
--- @param src table<K, V> Table to insert from
|
||||||
|
--- @return table<K, V> # Table that was passed as the first argument
|
||||||
function table.merge(dst, src)
|
function table.merge(dst, src)
|
||||||
local dst_len = #dst
|
local dst_len = #dst
|
||||||
for k, v in pairs(src) do
|
for k, v in pairs(src) do
|
||||||
@@ -21,75 +24,70 @@ function table.merge(dst, src)
|
|||||||
dst[k] = v
|
dst[k] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return dst
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[-- Much faster method for inserting items into an array
|
--- Much faster method for inserting items into an array
|
||||||
@tparam table tbl the table that will have the values added to it
|
--- @generic V
|
||||||
@tparam[opt] number start_index the index at which values will be added, nil means end of the array
|
--- @param dst V[] Array that will have the values added to it
|
||||||
@tparam table values the new values that will be added to the table
|
--- @param start_index number|table|nil Index at which values will be added, nil means end of the array
|
||||||
@treturn table the table that was passed as the first argument
|
--- @param src V[]? Array of values that will be added
|
||||||
@usage-- Adding 1000 values into the middle of the array
|
--- @return V[] # Array that was passed as the first argument
|
||||||
local tbl = {}
|
function table.insert_array(dst, start_index, src)
|
||||||
local values = {}
|
if not src then
|
||||||
for i = 1, 1000 do tbl[i] = i values[i] = i end
|
assert(type(start_index) == "table")
|
||||||
table.insert_array(tbl, 500, values) -- around 0.4ms
|
src = start_index
|
||||||
]]
|
|
||||||
function table.insert_array(tbl, start_index, values)
|
|
||||||
if not values then
|
|
||||||
values = start_index
|
|
||||||
start_index = nil
|
start_index = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if start_index then
|
if start_index then
|
||||||
local starting_length = #tbl
|
local starting_length = #dst
|
||||||
local adding_length = #values
|
local adding_length = #src
|
||||||
local move_to = start_index + adding_length + 1
|
local move_to = start_index + adding_length + 1
|
||||||
for offset = starting_length - start_index, 0, -1 do
|
for offset = starting_length - start_index, 0, -1 do
|
||||||
tbl[move_to + offset] = tbl[starting_length + offset]
|
dst[move_to + offset] = dst[starting_length + offset]
|
||||||
end
|
end
|
||||||
|
|
||||||
start_index = start_index - 1
|
start_index = start_index - 1
|
||||||
else
|
else
|
||||||
start_index = #tbl
|
start_index = #dst
|
||||||
end
|
end
|
||||||
|
|
||||||
for offset, item in ipairs(values) do
|
for offset, item in ipairs(src) do
|
||||||
tbl[start_index + offset] = item
|
dst[start_index + offset] = item
|
||||||
end
|
end
|
||||||
|
|
||||||
return tbl
|
return dst
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[-- Much faster method for inserting keys into a table
|
--- Much faster method for inserting keys into a table
|
||||||
@tparam table tbl the table that will have keys added to it
|
--- @generic K, V
|
||||||
@tparam[opt] number start_index the index at which values will be added, nil means end of the array, numbered indexs only
|
--- @param dst table<K, V> Table that will have keys added to it
|
||||||
@tparam table tbl2 the table that may contain both string and numbered keys
|
--- @param start_index number|table|nil Index at which values will be added, nil means end of the array, numbered indexes only
|
||||||
@treturn table the table passed as the first argument
|
--- @param src table<K, V> ? Table that may contain both string and numbered keys
|
||||||
@usage-- Merging two tables
|
--- @return table<K, V> # Table passed as the first argument
|
||||||
local tbl = {}
|
function table.insert_table(dst, start_index, src)
|
||||||
local tbl2 = {}
|
if not src then
|
||||||
for i = 1, 100 do tbl[i] = i tbl['_'..i] = i tbl2[i] = i tbl2['__'..i] = i end
|
assert(type(start_index) == "table")
|
||||||
table.insert_table(tbl, 50, tbl2)
|
src = start_index
|
||||||
]]
|
|
||||||
function table.insert_table(tbl, start_index, tbl2)
|
|
||||||
if not tbl2 then
|
|
||||||
tbl2 = start_index
|
|
||||||
start_index = nil
|
start_index = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert_array(tbl, start_index, tbl2)
|
table.insert_array(dst, start_index, src)
|
||||||
for key, value in pairs(tbl2) do
|
for key, value in pairs(src) do
|
||||||
if not tonumber(key) then
|
if not tonumber(key) then
|
||||||
tbl[key] = value
|
dst[key] = value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return tbl
|
return dst
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Searches an array to remove a specific element without an index
|
--- Searches a table to remove a specific element without an index
|
||||||
-- @tparam table tbl The array to remove the element from
|
--- @generic V
|
||||||
-- @param element The element to search for
|
--- @param tbl table<any, V> Table to remove the element from
|
||||||
|
--- @param element V Element to remove
|
||||||
function table.remove_element(tbl, element)
|
function table.remove_element(tbl, element)
|
||||||
for k, v in pairs(tbl) do
|
for k, v in pairs(tbl) do
|
||||||
if v == element then
|
if v == element then
|
||||||
@@ -100,22 +98,27 @@ function table.remove_element(tbl, element)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Removes an item from an array in O(1) time. Does not guarantee the order of elements.
|
--- Removes an item from an array in O(1) time. Does not guarantee the order of elements.
|
||||||
-- @tparam table tbl The array to remove the element from
|
--- @generic V
|
||||||
-- @tparam number index Must be >= 0. The case where index > #tbl is handled.
|
--- @param tbl V[] Array to remove the element from
|
||||||
|
--- @param index number Must be >= 0. The case where index > #tbl is handled.
|
||||||
|
--- @return V? # Element which was removed or nil
|
||||||
function table.remove_index(tbl, index)
|
function table.remove_index(tbl, index)
|
||||||
local count = #tbl
|
local count = #tbl
|
||||||
if index > count then
|
if index > count then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local rtn = tbl[count]
|
||||||
tbl[index] = tbl[count]
|
tbl[index] = tbl[count]
|
||||||
tbl[count] = nil
|
tbl[count] = nil
|
||||||
|
return rtn
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Return the key which holds this element element
|
--- Return the key which holds this element element
|
||||||
-- @tparam table tbl The table to search
|
--- @generic K, V
|
||||||
-- @param element The element to find
|
--- @param tbl table<K, V> Table to search
|
||||||
-- @return The key of the element or nil
|
--- @param element V Element to find
|
||||||
|
--- @return K? # Key of the element or nil
|
||||||
function table.get_key(tbl, element)
|
function table.get_key(tbl, element)
|
||||||
for k, v in pairs(tbl) do
|
for k, v in pairs(tbl) do
|
||||||
if v == element then
|
if v == element then
|
||||||
@@ -127,9 +130,10 @@ function table.get_key(tbl, element)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Checks if the arrayed portion of a table contains an element
|
--- Checks if the arrayed portion of a table contains an element
|
||||||
-- @tparam table tbl The table to search
|
--- @generic V
|
||||||
-- @param element The element to find
|
--- @param tbl V[] Table to search
|
||||||
-- @treturn ?number The index of the element or nil
|
--- @param element V Element to find
|
||||||
|
--- @return number? # Index of the element or nil
|
||||||
function table.get_index(tbl, element)
|
function table.get_index(tbl, element)
|
||||||
for i = 1, #tbl do
|
for i = 1, #tbl do
|
||||||
if tbl[i] == element then
|
if tbl[i] == element then
|
||||||
@@ -141,47 +145,48 @@ function table.get_index(tbl, element)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Checks if a table contains an element
|
--- Checks if a table contains an element
|
||||||
-- @tparam table tbl The table to search
|
--- @generic V
|
||||||
-- @param e The element to find
|
--- @param tbl table<any, V> Table to search
|
||||||
-- @treturn boolean True if the element was found
|
--- @param element V Element to find
|
||||||
|
--- @return boolean # True if the element was found
|
||||||
function table.contains(tbl, element)
|
function table.contains(tbl, element)
|
||||||
return table.get_key(tbl, element) and true or false
|
return table.get_key(tbl, element) and true or false
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Checks if the arrayed portion of a table contains an element
|
--- Checks if the arrayed portion of a table contains an element
|
||||||
-- @tparam table tbl The table to search
|
--- @generic V
|
||||||
-- @param e The element to find
|
--- @param tbl V[] Table to search
|
||||||
-- @treturn boolean True if the element was found
|
--- @param element V Element to find
|
||||||
|
--- @return boolean # True if the element was found
|
||||||
function table.array_contains(tbl, element)
|
function table.array_contains(tbl, element)
|
||||||
return table.get_index(tbl, element) and true or false
|
return table.get_index(tbl, element) and true or false
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[-- Extracts certain keys from a table, similar to deconstruction in other languages
|
--- Extracts certain keys from a table, similar to deconstruction in other languages
|
||||||
@tparam table tbl table the which contains the keys
|
--- @generic K, V
|
||||||
@tparam string ... the names of the keys you want extracted
|
--- @param tbl table<K, V> Table the which contains the keys
|
||||||
@return the keys in the order given
|
--- @param ... K Keys to extracted
|
||||||
@usage -- Deconstruction of a required module
|
--- @return V ... Values in the order given
|
||||||
local format_number, distance = table.deconstruct(require('util'), 'format_number', 'distance')
|
|
||||||
]]
|
|
||||||
function table.deconstruct(tbl, ...)
|
function table.deconstruct(tbl, ...)
|
||||||
local values = {}
|
local values = {}
|
||||||
for _, key in pairs{ ... } do
|
for index, key in pairs{ ... } do
|
||||||
table.insert(values, tbl[key])
|
values[index] = tbl[key]
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.unpack(values)
|
return unpack(values)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Chooses a random entry from a table, can only be used during runtime
|
--- Chooses a random entry from a table, can only be used during runtime
|
||||||
-- @tparam table tbl The table to select from
|
--- @generic K, V
|
||||||
-- @tparam[opt=false] boolean key When true the key will be returned rather than the value
|
--- @param tbl table<K, V> Table to select from
|
||||||
-- @return The selected element from the table
|
--- @param rtn_key boolean? True when the key will be returned rather than the value
|
||||||
function table.get_random(tbl, key)
|
--- @return K | V # Selected element from the table
|
||||||
|
function table.get_random(tbl, rtn_key)
|
||||||
local target_index = random(1, table_size(tbl))
|
local target_index = random(1, table_size(tbl))
|
||||||
local count = 1
|
local count = 1
|
||||||
for k, v in pairs(tbl) do
|
for k, v in pairs(tbl) do
|
||||||
if target_index == count then
|
if target_index == count then
|
||||||
if key then
|
if rtn_key then
|
||||||
return k
|
return k
|
||||||
else
|
else
|
||||||
return v
|
return v
|
||||||
@@ -189,16 +194,18 @@ function table.get_random(tbl, key)
|
|||||||
end
|
end
|
||||||
count = count + 1
|
count = count + 1
|
||||||
end
|
end
|
||||||
|
error("Unreachable")
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Chooses a random entry from a weighted table, can only be used during runtime
|
--- Chooses a random entry from a weighted table, can only be used during runtime
|
||||||
-- @tparam table weighted_table The table of items and their weights
|
--- @generic V, VK, WK
|
||||||
-- @param[opt=1] item_key The index / key of items within each element
|
--- @param weighted_table table<any, { [VK]: V, [WK]: number }> Table of items and their weights
|
||||||
-- @param[opt=2] weight_key The index / key of the weights within each element
|
--- @param value_key VK? Index / key of value to within each element
|
||||||
-- @return The selected element from the table
|
--- @param weight_index WK? Index / key of the weights within each element
|
||||||
function table.get_random_weighted(weighted_table, item_key, weight_index)
|
--- @return V # Selected element from the table
|
||||||
|
function table.get_random_weighted(weighted_table, value_key, weight_index)
|
||||||
local total_weight = 0
|
local total_weight = 0
|
||||||
item_key = item_key or 1
|
value_key = value_key or 1
|
||||||
weight_index = weight_index or 2
|
weight_index = weight_index or 2
|
||||||
|
|
||||||
for _, w in pairs(weighted_table) do
|
for _, w in pairs(weighted_table) do
|
||||||
@@ -210,22 +217,23 @@ function table.get_random_weighted(weighted_table, item_key, weight_index)
|
|||||||
for _, w in pairs(weighted_table) do
|
for _, w in pairs(weighted_table) do
|
||||||
weight_sum = weight_sum + w[weight_index]
|
weight_sum = weight_sum + w[weight_index]
|
||||||
if weight_sum >= index then
|
if weight_sum >= index then
|
||||||
return w[item_key]
|
return w[value_key]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
error("Unreachable")
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Clears all existing entries in a table
|
--- Clears all existing entries in a table
|
||||||
-- @tparam table tbl The table to clear
|
--- @param tbl table Table to clear
|
||||||
-- @tparam[opt=false] boolean array When true only the array portion of the table is cleared
|
--- @param array boolean? True when only the array portion of the table is cleared
|
||||||
function table.clear(t, array)
|
function table.clear(tbl, array)
|
||||||
if array then
|
if array then
|
||||||
for i = 1, #t do
|
for i = 1, #tbl do
|
||||||
t[i] = nil
|
tbl[i] = nil
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for i in pairs(t) do
|
for i in pairs(tbl) do
|
||||||
t[i] = nil
|
tbl[i] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -233,36 +241,31 @@ end
|
|||||||
--- Creates a fisher-yates shuffle of a sequential number-indexed table
|
--- Creates a fisher-yates shuffle of a sequential number-indexed table
|
||||||
-- because this uses math.random, it cannot be used outside of events if no rng is supplied
|
-- because this uses math.random, it cannot be used outside of events if no rng is supplied
|
||||||
-- from: http://www.sdknews.com/cross-platform/corona/tutorial-how-to-shuffle-table-items
|
-- from: http://www.sdknews.com/cross-platform/corona/tutorial-how-to-shuffle-table-items
|
||||||
-- @tparam table tbl The table to shuffle
|
--- @generic K
|
||||||
-- @tparam[opt=math.random] function rng The function to provide random numbers
|
--- @param tbl table<K, any> Table to shuffle
|
||||||
function table.shuffle(t, rng)
|
--- @param rng fun(iter: number): K Function to provide random numbers
|
||||||
|
function table.shuffle(tbl, rng)
|
||||||
local rand = rng or math.random
|
local rand = rng or math.random
|
||||||
local iterations = #t
|
local iterations = assert(#tbl > 0, "Not a sequential table")
|
||||||
if iterations == 0 then
|
|
||||||
error("Not a sequential table")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local j
|
local j
|
||||||
|
|
||||||
for i = iterations, 2, -1 do
|
for i = iterations, 2, -1 do
|
||||||
j = rand(i)
|
j = rand(i)
|
||||||
t[i], t[j] = t[j], t[i]
|
tbl[i], tbl[j] = tbl[j], tbl[i]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Default table comparator sort function.
|
--- Default table comparator sort function.
|
||||||
-- @local
|
--- @param lhs any LHS comparator operand
|
||||||
-- @param x one comparator operand
|
--- @param rhs any RHS comparator operand
|
||||||
-- @param y the other comparator operand
|
--- @return boolean True if lhs logically comes before rhs in a list, false otherwise
|
||||||
-- @return true if x logically comes before y in a list, false otherwise
|
local function sort_func(lhs, rhs) -- sorts tables with mixed index types.
|
||||||
local function sort_func(x, y) -- sorts tables with mixed index types.
|
local tx = type(lhs)
|
||||||
local tx = type(x)
|
local ty = type(rhs)
|
||||||
local ty = type(y)
|
|
||||||
if tx == ty then
|
if tx == ty then
|
||||||
if type(x) == "string" then
|
if type(lhs) == "string" then
|
||||||
return string.lower(x) < string.lower(y)
|
return string.lower(lhs) < string.lower(rhs)
|
||||||
else
|
else
|
||||||
return x < y
|
return lhs < rhs
|
||||||
end
|
end
|
||||||
elseif tx == "number" then
|
elseif tx == "number" then
|
||||||
return true -- only x is a number and goes first
|
return true -- only x is a number and goes first
|
||||||
@@ -272,108 +275,103 @@ local function sort_func(x, y) -- sorts tables with mixed index types.
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Returns a copy of all of the values in the table.
|
--- Returns a copy of all of the values in the table.
|
||||||
-- @tparam table tbl the to copy the keys from, or an empty table if tbl is nil
|
--- @generic V
|
||||||
-- @tparam[opt] boolean sorted whether to sort the keys (slower) or keep the random order from pairs()
|
--- @param tbl table<any, V> Table to copy the values from, or an empty table if tbl is nil
|
||||||
-- @tparam[opt] boolean as_string whether to try and parse the values as strings, or leave them as their existing type
|
--- @param sorted boolean? True to sort the keys (slower) or keep the random order from pairs()
|
||||||
-- @treturn array an array with a copy of all the values in the table
|
--- @param as_string boolean? True to try and parse the values as strings, or leave them as their existing type
|
||||||
|
--- @return V[] # Array with a copy of all the values in the table
|
||||||
function table.get_values(tbl, sorted, as_string)
|
function table.get_values(tbl, sorted, as_string)
|
||||||
if not tbl then return {} end
|
if not tbl then return {} end
|
||||||
local valueset = {}
|
local value_set = {}
|
||||||
local n = 0
|
local n = 0
|
||||||
if as_string then -- checking as_string /before/ looping is faster
|
if as_string then -- checking as_string before looping is faster
|
||||||
for _, v in pairs(tbl) do
|
for _, v in pairs(tbl) do
|
||||||
n = n + 1
|
n = n + 1
|
||||||
valueset[n] = tostring(v)
|
value_set[n] = tostring(v)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for _, v in pairs(tbl) do
|
for _, v in pairs(tbl) do
|
||||||
n = n + 1
|
n = n + 1
|
||||||
valueset[n] = v
|
value_set[n] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if sorted then
|
if sorted then
|
||||||
table.sort(valueset, sort_func)
|
table.sort(value_set, sort_func)
|
||||||
end
|
end
|
||||||
return valueset
|
return value_set
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns a copy of all of the keys in the table.
|
--- Returns a copy of all of the keys in the table.
|
||||||
-- @tparam table tbl the to copy the keys from, or an empty table if tbl is nil
|
--- @generic K
|
||||||
-- @tparam[opt] boolean sorted whether to sort the keys (slower) or keep the random order from pairs()
|
--- @param tbl table<K, any> Table to copy the keys from, or an empty table if tbl is nil
|
||||||
-- @tparam[opt] boolean as_string whether to try and parse the keys as strings, or leave them as their existing type
|
--- @param sorted boolean? True to sort the keys (slower) or keep the random order from pairs()
|
||||||
-- @treturn array an array with a copy of all the keys in the table
|
--- @param as_string boolean? True to try and parse the keys as strings, or leave them as their existing type
|
||||||
|
--- @return K[] # Array with a copy of all the keys in the table
|
||||||
function table.get_keys(tbl, sorted, as_string)
|
function table.get_keys(tbl, sorted, as_string)
|
||||||
if not tbl then return {} end
|
if not tbl then return {} end
|
||||||
local keyset = {}
|
local key_set = {}
|
||||||
local n = 0
|
local n = 0
|
||||||
if as_string then -- checking as_string /before/ looping is faster
|
if as_string then -- checking as_string /before/ looping is faster
|
||||||
for k, _ in pairs(tbl) do
|
for k, _ in pairs(tbl) do
|
||||||
n = n + 1
|
n = n + 1
|
||||||
keyset[n] = tostring(k)
|
key_set[n] = tostring(k)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for k, _ in pairs(tbl) do
|
for k, _ in pairs(tbl) do
|
||||||
n = n + 1
|
n = n + 1
|
||||||
keyset[n] = k
|
key_set[n] = k
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if sorted then
|
if sorted then
|
||||||
table.sort(keyset, sort_func)
|
table.sort(key_set, sort_func)
|
||||||
end
|
end
|
||||||
return keyset
|
return key_set
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the list is a sorted way that would be expected by people (this is by key)
|
--- Returns the list is a sorted way that would be expected by people (this is by key)
|
||||||
-- @tparam table tbl the table to be sorted
|
--- @generic K, V
|
||||||
-- @treturn table the sorted table
|
--- @param tbl table<K, V> Table to be sorted
|
||||||
|
--- @return table<K, V> # Sorted table
|
||||||
function table.alphanum_sort(tbl)
|
function table.alphanum_sort(tbl)
|
||||||
local o = table.get_keys(tbl)
|
local o = table.get_keys(tbl)
|
||||||
local function padnum(d)
|
local function padnum(d)
|
||||||
local dec, n = string.match(d, "(%.?)0*(.+)")
|
local dec, n = string.match(d, "(%.?)0*(.+)")
|
||||||
return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n)
|
return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n)
|
||||||
end
|
end
|
||||||
|
|
||||||
table.sort(o, function(a, b)
|
table.sort(o, function(a, b)
|
||||||
return tostring(a):gsub("%.?%d+", padnum) .. ("%3d"):format(#b)
|
return tostring(a):gsub("%.?%d+", padnum) .. ("%3d"):format(#b)
|
||||||
< tostring(b):gsub("%.?%d+", padnum) .. ("%3d"):format(#a)
|
< tostring(b):gsub("%.?%d+", padnum) .. ("%3d"):format(#a)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local _tbl = {}
|
local _tbl = {}
|
||||||
for _, k in pairs(o) do _tbl[k] = tbl[k] end
|
for _, k in pairs(o) do _tbl[k] = tbl[k] end
|
||||||
|
|
||||||
return _tbl
|
return _tbl
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns the list is a sorted way that would be expected by people (this is by key) (faster alternative than above)
|
--- Returns the list is a sorted way that would be expected by people (this is by key) (faster alternative than above)
|
||||||
-- @tparam table tbl the table to be sorted
|
--- @generic K, V
|
||||||
-- @treturn table the sorted table
|
--- @param tbl table<K, V> Table to be sorted
|
||||||
|
--- @return table<K, V> # Sorted table
|
||||||
function table.key_sort(tbl)
|
function table.key_sort(tbl)
|
||||||
local o = table.get_keys(tbl, true)
|
local o = table.get_keys(tbl, true)
|
||||||
local _tbl = {}
|
local _tbl = {}
|
||||||
for _, k in pairs(o) do _tbl[k] = tbl[k] end
|
for _, k in pairs(o) do _tbl[k] = tbl[k] end
|
||||||
|
|
||||||
return _tbl
|
return _tbl
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--- Returns the index where t[index] == target.
|
||||||
Returns the index where t[index] == target.
|
-- If there is no such index, returns a negative value such that bit32.bnot(value) is
|
||||||
If there is no such index, returns a negative value such that bit32.bnot(value) is
|
-- the index that the value should be inserted to keep the list ordered.
|
||||||
the index that the value should be inserted to keep the list ordered.
|
-- It must be a list in ascending order for the return value to be valid.
|
||||||
t must be a list in ascending order for the return value to be valid.
|
--- @generic V
|
||||||
|
--- @param tbl V[] Array to search
|
||||||
Usage example:
|
--- @param target V Target to find
|
||||||
local t = {1, 3,5, 7,9}
|
--- @return number # Index the value was at
|
||||||
local x = 5
|
function table.binary_search(tbl, target)
|
||||||
local index = table.binary_search(t, x)
|
|
||||||
if index < 0 then
|
|
||||||
game.print("value not found, smallest index where t[index] > x is: " .. bit32.bnot(index))
|
|
||||||
else
|
|
||||||
game.print("value found at index: " .. index)
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
function table.binary_search(t, target)
|
|
||||||
-- For some reason bit32.bnot doesn't return negative numbers so I'm using ~x = -1 - x instead.
|
-- For some reason bit32.bnot doesn't return negative numbers so I'm using ~x = -1 - x instead.
|
||||||
|
|
||||||
local lower = 1
|
local lower = 1
|
||||||
local upper = #t
|
local upper = #tbl
|
||||||
|
|
||||||
if upper == 0 then
|
if upper == 0 then
|
||||||
return -2 -- ~1
|
return -2 -- ~1
|
||||||
@@ -381,7 +379,7 @@ function table.binary_search(t, target)
|
|||||||
|
|
||||||
repeat
|
repeat
|
||||||
local mid = floor((lower + upper) * 0.5)
|
local mid = floor((lower + upper) * 0.5)
|
||||||
local value = t[mid]
|
local value = tbl[mid]
|
||||||
if value == target then
|
if value == target then
|
||||||
return mid
|
return mid
|
||||||
elseif value < target then
|
elseif value < target then
|
||||||
@@ -394,38 +392,25 @@ function table.binary_search(t, target)
|
|||||||
return -1 - lower -- ~lower
|
return -1 - lower -- ~lower
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add table-related functions that exist in base factorio/util to the 'table' table
|
-- Add table-related functions that exist in base factorio/util to the 'table' table
|
||||||
require "util"
|
require "util"
|
||||||
|
|
||||||
--- Similar to serpent.block, returns a string with a pretty representation of a table.
|
--- Similar to serpent.block, returns a string with a pretty representation of a table.
|
||||||
-- Notice: This method is not appropriate for saving/restoring tables. It is meant to be used by the programmer mainly while debugging a program.
|
-- Notice: This method is not appropriate for saving/restoring tables. It is meant to be used by the programmer mainly while debugging a program.
|
||||||
-- @tparam table root tTe table to serialize
|
table.inspect = require("modules/exp_util/include/inspect").inspect
|
||||||
-- @tparam table options Options are depth, newline, indent, process
|
|
||||||
-- depth sets the maximum depth that will be printed out. When the max depth is reached, inspect will stop parsing tables and just return {...}
|
|
||||||
-- process is a function which allow altering the passed object before transforming it into a string.
|
|
||||||
-- A typical way to use it would be to remove certain values so that they don't appear at all.
|
|
||||||
-- return <string> the prettied table
|
|
||||||
table.inspect = require "modules.exp_util.include.inspect" --- @dep modules.exp_util.includes.inspect
|
|
||||||
|
|
||||||
--- Takes a table and returns the number of entries in the table. (Slower than #table, faster than iterating via pairs)
|
--- Takes a table and returns the number of entries in the table. (Slower than #table, faster than iterating via pairs)
|
||||||
table.size = table_size
|
table.size = table_size
|
||||||
|
|
||||||
--- Creates a deepcopy of a table. Metatables and LuaObjects inside the table are shallow copies.
|
--- Creates a deepcopy of a table. Metatables and LuaObjects inside the table are shallow copies.
|
||||||
-- Shallow copies meaning it copies the reference to the object instead of the object itself.
|
-- Shallow copies meaning it copies the reference to the object instead of the object itself.
|
||||||
-- @tparam table object The object to copy
|
|
||||||
-- @treturn table The copied object
|
|
||||||
table.deep_copy = table.deepcopy -- added by util
|
table.deep_copy = table.deepcopy -- added by util
|
||||||
|
|
||||||
--- Merges multiple tables. Tables later in the list will overwrite entries from tables earlier in the list.
|
--- Merges multiple tables. Tables later in the list will overwrite entries from tables earlier in the list.
|
||||||
-- Ex. merge({{1, 2, 3}, {[2] = 0}, {[3] = 0}}) will return {1, 0, 0}
|
-- Ex. merge({{1, 2, 3}, {[2] = 0}, {[3] = 0}}) will return {1, 0, 0}
|
||||||
-- @tparam table tables A table of tables to merge
|
|
||||||
-- @treturn table The merged table
|
|
||||||
table.deep_merge = util.merge
|
table.deep_merge = util.merge
|
||||||
|
|
||||||
--- Determines if two tables are structurally equal.
|
--- Determines if two tables are structurally equal.
|
||||||
-- @tparam table tbl1 The first table
|
|
||||||
-- @tparam table tbl2 The second table
|
|
||||||
-- @treturn boolean True if the tables are equal
|
|
||||||
table.equals = table.compare -- added by util
|
table.equals = table.compare -- added by util
|
||||||
|
|
||||||
return table
|
return table
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
time-symbol-days-short=__1__d
|
time-symbol-days-short=__1__d
|
||||||
|
rich-text-color-tag=[color=__1__]__2__[/color]
|
||||||
@@ -1 +1,611 @@
|
|||||||
return require("modules/exp_util/common")
|
--[[-- ExpUtil
|
||||||
|
Adds some commonly used functions used in many modules
|
||||||
|
]]
|
||||||
|
|
||||||
|
local type = type
|
||||||
|
local assert = assert
|
||||||
|
local getmetatable = getmetatable
|
||||||
|
local getinfo = debug.getinfo
|
||||||
|
local traceback = debug.traceback
|
||||||
|
local floor = math.floor
|
||||||
|
local round = math.round
|
||||||
|
local concat = table.concat
|
||||||
|
local inspect = table.inspect
|
||||||
|
local format_string = string.format
|
||||||
|
local table_to_json = helpers.table_to_json
|
||||||
|
local write_file = helpers.write_file
|
||||||
|
|
||||||
|
--- @class ExpUtil
|
||||||
|
local ExpUtil = {
|
||||||
|
--- A large mapping of colour rgb values by their common name
|
||||||
|
color = require("modules/exp_util/include/color"),
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Raise an error if we are not in runtime
|
||||||
|
function ExpUtil.assert_not_runtime()
|
||||||
|
assert(package.lifecycle ~= package.lifecycle_stage.runtime, "Can not be called during runtime")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Check the type of a value, also considers LuaObject.object_name and metatable.__class
|
||||||
|
--- Returns true when the check failed and an error should be raised
|
||||||
|
--- @param value any The value to check the type of
|
||||||
|
--- @param type_name string The type name the value should be
|
||||||
|
--- @return boolean failed True if the check failed and an error should be raised
|
||||||
|
--- @return string actual_type The actual type of the value
|
||||||
|
local function check_type(value, type_name)
|
||||||
|
local value_type = type(value) --[[@as string]]
|
||||||
|
if value_type == "userdata" then
|
||||||
|
if type_name == "userdata" then
|
||||||
|
return false, value_type
|
||||||
|
end
|
||||||
|
value_type = value.object_name
|
||||||
|
elseif value_type == "table" then
|
||||||
|
if type_name == "table" then
|
||||||
|
return false, value_type
|
||||||
|
end
|
||||||
|
local mt = getmetatable(value)
|
||||||
|
if mt and mt.__class then
|
||||||
|
value_type = mt.__class
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return value == nil or value_type ~= type_name, value_type
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Get the name of a class or object, better than just using type
|
||||||
|
--- @param value any The value to get the class of
|
||||||
|
--- @return string # One of type, object_name, __class
|
||||||
|
function ExpUtil.get_class_name(value)
|
||||||
|
local value_type = type(value) --[[@as string]]
|
||||||
|
if value_type == "userdata" then
|
||||||
|
return value.object_name
|
||||||
|
elseif value_type == "table" then
|
||||||
|
local mt = getmetatable(value)
|
||||||
|
if mt and mt.__class then
|
||||||
|
return mt.__class
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return value_type
|
||||||
|
end
|
||||||
|
|
||||||
|
local assert_type_fmt = "%s expected to be of type %s but got %s"
|
||||||
|
--- Raise an error if the type of a value is not as expected
|
||||||
|
--- @param value any The value to assert the type of
|
||||||
|
--- @param type_name string The name of the type that value is expected to be
|
||||||
|
--- @param value_name string? The name of the value being tested, this is included in the error message
|
||||||
|
function ExpUtil.assert_type(value, type_name, value_name)
|
||||||
|
local failed, actual_type = check_type(value, type_name)
|
||||||
|
if failed then
|
||||||
|
error(assert_type_fmt:format(value_name or "Value", type_name, actual_type), 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local assert_argument_fmt = "Bad argument #%d to %s; %s expected to be of type %s but got %s"
|
||||||
|
--- Raise an error if the type of any argument is not as expected, more performant than assert_argument_types, but requires more manual input
|
||||||
|
--- @param arg_value any The argument to assert the type of
|
||||||
|
--- @param type_name string The name of the type that value is expected to be
|
||||||
|
--- @param arg_index number The index of the argument being tested, this is included in the error message
|
||||||
|
--- @param arg_name string? The name of the argument being tested, this is included in the error message
|
||||||
|
function ExpUtil.assert_argument_type(arg_value, type_name, arg_index, arg_name)
|
||||||
|
local failed, actual_type = check_type(arg_value, type_name)
|
||||||
|
if failed then
|
||||||
|
local func_name = getinfo(2, "n").name or "<anonymous>"
|
||||||
|
error(assert_argument_fmt:format(arg_index, func_name, arg_name or "Argument", type_name, actual_type), 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Write a luu table to a file as a json string, note the defaults are different to write_file
|
||||||
|
--- @param path string The path to write the json to
|
||||||
|
--- @param tbl table The table to write to file
|
||||||
|
--- @param overwrite boolean? When true the json replaces the full contents of the file
|
||||||
|
--- @param player_index number? The player's machine to write on, -1 means all, 0 is default means host only
|
||||||
|
--- @return nil
|
||||||
|
function ExpUtil.write_json(path, tbl, overwrite, player_index)
|
||||||
|
if player_index == -1 then
|
||||||
|
return write_file(path, table_to_json(tbl) .. "\n", not overwrite)
|
||||||
|
end
|
||||||
|
return write_file(path, table_to_json(tbl) .. "\n", not overwrite, player_index or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Clear a file by replacing its contents with an empty string
|
||||||
|
--- @param path string The path to clear the contents of
|
||||||
|
--- @param player_index number? The player's machine to write on, -1 means all, 0 is default and means host only
|
||||||
|
--- @return nil
|
||||||
|
function ExpUtil.clear_file(path, player_index)
|
||||||
|
if player_index == -1 then
|
||||||
|
return write_file(path, "", false)
|
||||||
|
end
|
||||||
|
return write_file(path, "", false, player_index or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Same as require but will return nil if the module does not exist, all other errors will propagate to the caller
|
||||||
|
--- @param module_path string The path to the module to require, same syntax as normal require
|
||||||
|
--- @return any # The contents of the module, or nil if the module does not exist or did not return a value
|
||||||
|
--- @deprecated
|
||||||
|
function ExpUtil.optional_require(module_path)
|
||||||
|
local success, rtn = xpcall(require, traceback, module_path)
|
||||||
|
if success then return rtn end
|
||||||
|
if not rtn:find("no such file", 0, true) then
|
||||||
|
error(rtn, 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns a desync sale filepath for a given stack frame, default is the current file
|
||||||
|
--- @param level number? The level of the stack to get the file of, a value of 1 is the caller of this function
|
||||||
|
--- @return string # The relative filepath of the given stack frame
|
||||||
|
function ExpUtil.safe_file_path(level)
|
||||||
|
local debug_info = getinfo((level or 1) + 1, "Sn")
|
||||||
|
local safe_source = debug_info.source:find("@__level__")
|
||||||
|
return safe_source == 1 and debug_info.short_src:sub(10, -5) or debug_info.source
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns the name of your module, this assumes your module is stored within /modules (which it is for clustorio)
|
||||||
|
--- @param level number? The level of the stack to get the module of, a value of 1 is the caller of this function
|
||||||
|
--- @return string # The name of the module at the given stack frame
|
||||||
|
function ExpUtil.get_module_name(level)
|
||||||
|
local file_within_module = getinfo((level or 1) + 1, "S").short_src:sub(18, -5)
|
||||||
|
local next_slash = file_within_module:find("/")
|
||||||
|
if next_slash then
|
||||||
|
return file_within_module:sub(1, next_slash - 1)
|
||||||
|
else
|
||||||
|
return file_within_module
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns the name of a function in a safe and consistent format
|
||||||
|
--- @param func number | function The level of the stack to get the name of, a value of 1 is the caller of this function
|
||||||
|
--- @param raw boolean? When true there will not be any < > around the name
|
||||||
|
--- @return string # The name of the function at the given stack frame or provided as an argument
|
||||||
|
function ExpUtil.get_function_name(func, raw)
|
||||||
|
local debug_info = getinfo(func, "Sn")
|
||||||
|
local safe_source = debug_info.source:find("@__level__")
|
||||||
|
local file_name = safe_source == 1 and debug_info.short_src:sub(10, -5) or debug_info.source
|
||||||
|
local func_name = debug_info.name or debug_info.linedefined
|
||||||
|
if raw then return file_name .. ":" .. func_name end
|
||||||
|
return "<" .. file_name .. ":" .. func_name .. ">"
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Attempt a simple autocomplete search from a set of options
|
||||||
|
--- @param options table The table representing the possible options which can be selected
|
||||||
|
--- @param input string The user input string which should be matched to an option
|
||||||
|
--- @param use_key boolean? When true the keys will be searched, when false the values will be searched
|
||||||
|
--- @param rtn_key boolean? When true the selected key will be returned, when false the selected value will be returned
|
||||||
|
--- @return any # The selected key or value which first matches the input text
|
||||||
|
function ExpUtil.auto_complete(options, input, use_key, rtn_key)
|
||||||
|
input = input:lower()
|
||||||
|
if use_key then
|
||||||
|
for k, v in pairs(options) do
|
||||||
|
if k:lower():find(input) then
|
||||||
|
if rtn_key then return k else return v end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for k, v in pairs(options) do
|
||||||
|
if v:lower():find(input) then
|
||||||
|
if rtn_key then return k else return v end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Formats any value into a safe representation, useful with inspect
|
||||||
|
--- @param value any The value to be formatted
|
||||||
|
--- @return LocalisedString # The formatted version of the value
|
||||||
|
--- @return boolean # True if value is a locale string, nil otherwise
|
||||||
|
function ExpUtil.safe_value(value)
|
||||||
|
if type(value) == "table" then
|
||||||
|
local v1 = value[1]
|
||||||
|
local str = tostring(value)
|
||||||
|
if type(v1) == "string" and not v1:find("%s")
|
||||||
|
and (v1 == "" or v1 == "?" or v1:find(".+[.].+")) then
|
||||||
|
return value, true -- locale string
|
||||||
|
elseif str ~= "table" then
|
||||||
|
return str, false -- has __tostring metamethod
|
||||||
|
else -- plain table
|
||||||
|
return value, false
|
||||||
|
end
|
||||||
|
elseif type(value) == "function" then -- function
|
||||||
|
return "<function:" .. ExpUtil.get_function_name(value, true) .. ">", false
|
||||||
|
else -- not: table or function
|
||||||
|
return tostring(value), false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @class Common.format_any_param
|
||||||
|
--- @field as_json boolean? If table values should be returned as json
|
||||||
|
--- @field max_line_count number? If table newline count exceeds provided then it will be inlined, if 0 then always inline
|
||||||
|
--- @field no_locale_strings boolean? If value is a locale string it will be treated like a normal table
|
||||||
|
--- @field depth number? The max depth to process tables to, the default is 5
|
||||||
|
|
||||||
|
--- Formats any value to be presented in a safe and human readable format
|
||||||
|
--- @param value any The value to be formatted
|
||||||
|
--- @param options Common.format_any_param? Options for the formatter
|
||||||
|
--- @return LocalisedString # The formatted version of the value
|
||||||
|
function ExpUtil.format_any(value, options)
|
||||||
|
options = options or {}
|
||||||
|
local formatted, is_locale_string = ExpUtil.safe_value(value)
|
||||||
|
if type(formatted) == "table" and (not is_locale_string or options.no_locale_strings) then
|
||||||
|
if options.as_json then
|
||||||
|
local success, rtn = pcall(table_to_json, value)
|
||||||
|
if success then return rtn end
|
||||||
|
end
|
||||||
|
if options.max_line_count ~= 0 then
|
||||||
|
local rtn = inspect(value, { depth = options.depth or 5, indent = " ", newline = "\n", process = ExpUtil.safe_value })
|
||||||
|
if options.max_line_count == nil or select(2, rtn:gsub("\n", "")) < options.max_line_count then return rtn end
|
||||||
|
end
|
||||||
|
return inspect(value, { depth = options.depth or 5, indent = "", newline = "", process = ExpUtil.safe_value })
|
||||||
|
end
|
||||||
|
return formatted
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @alias Common.format_time_param_format "short" | "long" | "clock"
|
||||||
|
|
||||||
|
--- @class Common.format_time_param_units
|
||||||
|
--- @field days boolean? True if days are included
|
||||||
|
--- @field hours boolean? True if hours are included
|
||||||
|
--- @field minutes boolean? True if minutes are included
|
||||||
|
--- @field seconds boolean? True if seconds are included
|
||||||
|
|
||||||
|
--- Format a tick value into one of a selection of pre-defined formats (short, long, clock)
|
||||||
|
--- @param ticks number|nil The number of ticks which will be represented, can be any duration or time value
|
||||||
|
--- @param format Common.format_time_param_format format to display, must be one of: short, long, clock
|
||||||
|
--- @param units Common.format_time_param_units A table selecting which units should be displayed, options are: days, hours, minutes, seconds
|
||||||
|
--- @return string # The ticks formatted into a string of the desired format
|
||||||
|
function ExpUtil.format_time(ticks, format, units)
|
||||||
|
--- @type string | number, string | number, string | number, string | number
|
||||||
|
local rtn_days, rtn_hours, rtn_minutes, rtn_seconds = "--", "--", "--", "--"
|
||||||
|
|
||||||
|
if ticks ~= nil then
|
||||||
|
-- Calculate the values to be determine the display values
|
||||||
|
local max_days, max_hours, max_minutes, max_seconds = ticks / 5184000, ticks / 216000, ticks / 3600, ticks / 60
|
||||||
|
local days, hours = max_days, max_hours - floor(max_days) * 24
|
||||||
|
local minutes, seconds = max_minutes - floor(max_hours) * 60, max_seconds - floor(max_minutes) * 60
|
||||||
|
|
||||||
|
-- Calculate rhw units to be displayed
|
||||||
|
rtn_days, rtn_hours, rtn_minutes, rtn_seconds = floor(days), floor(hours), floor(minutes), floor(seconds)
|
||||||
|
if not units.days then rtn_hours = rtn_hours + rtn_days * 24 end
|
||||||
|
if not units.hours then rtn_minutes = rtn_minutes + rtn_hours * 60 end
|
||||||
|
if not units.minutes then rtn_seconds = rtn_seconds + rtn_minutes * 60 end
|
||||||
|
--- @diagnostic enable: cast-local-type
|
||||||
|
end
|
||||||
|
|
||||||
|
local rtn = {}
|
||||||
|
if format == "clock" then
|
||||||
|
-- Example 12:34:56 or --:--:--
|
||||||
|
if units.days then rtn[#rtn + 1] = rtn_days end
|
||||||
|
if units.hours then rtn[#rtn + 1] = rtn_hours end
|
||||||
|
if units.minutes then rtn[#rtn + 1] = rtn_minutes end
|
||||||
|
if units.seconds then rtn[#rtn + 1] = rtn_seconds end
|
||||||
|
return concat(rtn, ":")
|
||||||
|
elseif format == "short" then
|
||||||
|
-- Example 12d 34h 56m or --d --h --m
|
||||||
|
if units.days then rtn[#rtn + 1] = rtn_days .. "d" end
|
||||||
|
if units.hours then rtn[#rtn + 1] = rtn_hours .. "h" end
|
||||||
|
if units.minutes then rtn[#rtn + 1] = rtn_minutes .. "m" end
|
||||||
|
if units.seconds then rtn[#rtn + 1] = rtn_seconds .. "s" end
|
||||||
|
return concat(rtn, " ")
|
||||||
|
else
|
||||||
|
-- Example 12 days, 34 hours, and 56 minutes or -- days, -- hours, and -- minutes
|
||||||
|
if units.days then rtn[#rtn + 1] = rtn_days .. " days" end
|
||||||
|
if units.hours then rtn[#rtn + 1] = rtn_hours .. " hours" end
|
||||||
|
if units.minutes then rtn[#rtn + 1] = rtn_minutes .. " minutes" end
|
||||||
|
if units.seconds then rtn[#rtn + 1] = rtn_seconds .. " seconds" end
|
||||||
|
rtn[#rtn] = "and " .. rtn[#rtn]
|
||||||
|
return concat(rtn, ", ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Format a tick value into one of a selection of pre-defined formats (short, long, clock)
|
||||||
|
--- @param ticks number|nil The number of ticks which will be represented, can be any duration or time value
|
||||||
|
--- @param format Common.format_time_param_format format to display, must be one of: short, long, clock
|
||||||
|
--- @param units Common.format_time_param_units A table selecting which units should be displayed, options are: days, hours, minutes, seconds
|
||||||
|
--- @return LocalisedString # The ticks formatted into a string of the desired format
|
||||||
|
function ExpUtil.format_time_locale(ticks, format, units)
|
||||||
|
--- @type string | number, string | number, string | number, string | number
|
||||||
|
local rtn_days, rtn_hours, rtn_minutes, rtn_seconds = "--", "--", "--", "--"
|
||||||
|
|
||||||
|
if ticks ~= nil then
|
||||||
|
-- Calculate the values to be determine the display values
|
||||||
|
local max_days, max_hours, max_minutes, max_seconds = ticks / 5184000, ticks / 216000, ticks / 3600, ticks / 60
|
||||||
|
local days, hours = max_days, max_hours - floor(max_days) * 24
|
||||||
|
local minutes, seconds = max_minutes - floor(max_hours) * 60, max_seconds - floor(max_minutes) * 60
|
||||||
|
|
||||||
|
-- Calculate rhw units to be displayed
|
||||||
|
rtn_days, rtn_hours, rtn_minutes, rtn_seconds = floor(days), floor(hours), floor(minutes), floor(seconds)
|
||||||
|
if not units.days then rtn_hours = rtn_hours + rtn_days * 24 end
|
||||||
|
if not units.hours then rtn_minutes = rtn_minutes + rtn_hours * 60 end
|
||||||
|
if not units.minutes then rtn_seconds = rtn_seconds + rtn_minutes * 60 end
|
||||||
|
end
|
||||||
|
|
||||||
|
local rtn = {}
|
||||||
|
local join = ", " --- @type LocalisedString
|
||||||
|
if format == "clock" then
|
||||||
|
-- Example 12:34:56 or --:--:--
|
||||||
|
if units.days then rtn[#rtn + 1] = rtn_days end
|
||||||
|
if units.hours then rtn[#rtn + 1] = rtn_hours end
|
||||||
|
if units.minutes then rtn[#rtn + 1] = rtn_minutes end
|
||||||
|
if units.seconds then rtn[#rtn + 1] = rtn_seconds end
|
||||||
|
join = { "colon" }
|
||||||
|
elseif format == "short" then
|
||||||
|
-- Example 12d 34h 56m or --d --h --m
|
||||||
|
if units.days then rtn[#rtn + 1] = { "?", { "time-symbol-days-short", rtn_days }, rtn_days .. "d" } end
|
||||||
|
if units.hours then rtn[#rtn + 1] = { "time-symbol-hours-short", rtn_hours } end
|
||||||
|
if units.minutes then rtn[#rtn + 1] = { "time-symbol-minutes-short", rtn_minutes } end
|
||||||
|
if units.seconds then rtn[#rtn + 1] = { "time-symbol-seconds-short", rtn_seconds } end
|
||||||
|
join = " "
|
||||||
|
else
|
||||||
|
-- Example 12 days, 34 hours, and 56 minutes or -- days, -- hours, and -- minutes
|
||||||
|
if units.days then rtn[#rtn + 1] = { "days", rtn_days } end
|
||||||
|
if units.hours then rtn[#rtn + 1] = { "hours", rtn_hours } end
|
||||||
|
if units.minutes then rtn[#rtn + 1] = { "minutes", rtn_minutes } end
|
||||||
|
if units.seconds then rtn[#rtn + 1] = { "seconds", rtn_seconds } end
|
||||||
|
rtn[#rtn] = { "", { "and" }, " ", rtn[#rtn] }
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @type LocalisedString
|
||||||
|
local joined = { "" }
|
||||||
|
for k, v in ipairs(rtn) do
|
||||||
|
joined[2 * k] = v
|
||||||
|
joined[2 * k + 1] = join
|
||||||
|
end
|
||||||
|
|
||||||
|
return joined
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @class Common.format_time_factory_param: Common.format_time_param_units
|
||||||
|
--- @field format Common.format_time_param_format The format to use
|
||||||
|
--- @field coefficient number? If present will multiply the input by this amount before formatting
|
||||||
|
|
||||||
|
--- Create a formatter to format a tick value into one of a selection of pre-defined formats (short, long, clock)
|
||||||
|
--- @param options Common.format_time_factory_param
|
||||||
|
--- @return fun(ticks: number|nil): string
|
||||||
|
function ExpUtil.format_time_factory(options)
|
||||||
|
local formatter, format, coefficient = ExpUtil.format_time, options.format, options.coefficient
|
||||||
|
if coefficient then
|
||||||
|
return function(ticks) return formatter(ticks and ticks * coefficient or nil, format, options) end
|
||||||
|
end
|
||||||
|
return function(ticks) return formatter(ticks, format, options) end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a formatter to format a tick value into one of a selection of pre-defined formats (short, long, clock)
|
||||||
|
--- @param options Common.format_time_factory_param
|
||||||
|
--- @return fun(ticks: number|nil): LocalisedString
|
||||||
|
function ExpUtil.format_time_factory_locale(options)
|
||||||
|
local formatter, format, coefficient = ExpUtil.format_time_locale, options.format, options.coefficient
|
||||||
|
if coefficient then
|
||||||
|
return function(ticks) return formatter(ticks and ticks * coefficient or nil, format, options) end
|
||||||
|
end
|
||||||
|
return function(ticks) return formatter(ticks, format, options) end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @class Common.get_or_create_storage_cache
|
||||||
|
--- @field entities LuaEntity[] Array of found entities matching the search
|
||||||
|
--- @field current number The current index within the entity array
|
||||||
|
--- @field count number The number of entities found
|
||||||
|
|
||||||
|
--- @class Common.get_or_create_storage_param: EntitySearchFilters
|
||||||
|
--- @field item ItemStackIdentification The item stack that must be insertable
|
||||||
|
--- @field surface LuaSurface The surface to search for targets on
|
||||||
|
--- @field allow_creation boolean? If new entities can be create to store the items
|
||||||
|
--- @field cache Common.get_or_create_storage_cache? Internal search cache passed between subsequent calls
|
||||||
|
|
||||||
|
--- Find, or optionally create, a storage entity which a stack can be inserted into
|
||||||
|
--- @param options Common.get_or_create_storage_param
|
||||||
|
--- @return LuaEntity
|
||||||
|
function ExpUtil.get_storage_for_stack(options)
|
||||||
|
local surface = assert(options.surface, "A surface must be provided")
|
||||||
|
local item = assert(options.item, "An item stack must be provided")
|
||||||
|
|
||||||
|
-- Perform a search if on has not been done already
|
||||||
|
local cache = options.cache
|
||||||
|
if cache then
|
||||||
|
local entities = surface.find_entities_filtered(options)
|
||||||
|
cache = {
|
||||||
|
entities = entities,
|
||||||
|
count = #entities,
|
||||||
|
current = 0,
|
||||||
|
}
|
||||||
|
options.cache = cache
|
||||||
|
end
|
||||||
|
--- @cast cache -nil
|
||||||
|
|
||||||
|
-- Find a valid entity from the search results
|
||||||
|
local current, count, entities = cache.current, cache.count, cache.entities
|
||||||
|
for i = 1, cache.count do
|
||||||
|
local entity = entities[((current + i - 1) % count) + 1]
|
||||||
|
if entity.can_insert(item) then
|
||||||
|
cache.current = current + 1
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- No entity was found so one needs to be created
|
||||||
|
assert(options.allow_creation, "Unable to find valid entity, consider enabling allow_creation")
|
||||||
|
assert(options.name, "Name must be provided to allow creation of new entities")
|
||||||
|
|
||||||
|
local position
|
||||||
|
if options.position then
|
||||||
|
position = surface.find_non_colliding_position(options.name, options.position, options.radius or 0, 1, true)
|
||||||
|
elseif options.area then
|
||||||
|
position = surface.find_non_colliding_position_in_box(options.name, options.area, 1, true)
|
||||||
|
else
|
||||||
|
position = surface.find_non_colliding_position(options.name, { 0, 0 }, 0, 1, true)
|
||||||
|
end
|
||||||
|
assert(position, "Failed to find valid location")
|
||||||
|
|
||||||
|
local entity = surface.create_entity{ name = options.name, position = position, force = options.force or "neutral" }
|
||||||
|
assert(entity, "Failed to create a new entity")
|
||||||
|
|
||||||
|
cache.count = count + 1
|
||||||
|
entities[count] = entity
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @class Common.copy_items_to_surface_param: Common.get_or_create_storage_param
|
||||||
|
--- @field items ItemStackIdentification[] | LuaInventory The item stacks to copy
|
||||||
|
|
||||||
|
--- Insert a copy of the given items into the found entities. If no entities are found then they will be created if possible.
|
||||||
|
--- @param options Common.copy_items_to_surface_param
|
||||||
|
--- @return LuaEntity # The last entity inserted into
|
||||||
|
function ExpUtil.copy_items_to_surface(options)
|
||||||
|
local entity
|
||||||
|
for item_index = 1, #options.items do
|
||||||
|
options.item = options.items[item_index]
|
||||||
|
entity = ExpUtil.get_storage_for_stack(options)
|
||||||
|
entity.insert(options.item)
|
||||||
|
end
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @class Common.move_items_to_surface_param: Common.get_or_create_storage_param
|
||||||
|
--- @field items LuaItemStack[] The item stacks to move
|
||||||
|
|
||||||
|
--- Insert a copy of the given items into the found entities. If no entities are found then they will be created if possible.
|
||||||
|
--- @param options Common.move_items_to_surface_param
|
||||||
|
--- @return LuaEntity # The last entity inserted into
|
||||||
|
function ExpUtil.move_items_to_surface(options)
|
||||||
|
local entity
|
||||||
|
for item_index = 1, #options.items do
|
||||||
|
options.item = options.items[item_index]
|
||||||
|
entity = ExpUtil.get_storage_for_stack(options)
|
||||||
|
entity.insert(options.item)
|
||||||
|
options.item.clear()
|
||||||
|
end
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
|
||||||
|
--- @class Common.transfer_inventory_to_surface_param: Common.copy_items_to_surface_param
|
||||||
|
--- @field inventory LuaInventory The inventory to transfer
|
||||||
|
|
||||||
|
--- Move the given inventory into the found entities. If no entities are found then they will be created if possible.
|
||||||
|
--- @param options Common.transfer_inventory_to_surface_param
|
||||||
|
--- @return LuaEntity # The last entity inserted into
|
||||||
|
function ExpUtil.transfer_inventory_to_surface(options)
|
||||||
|
options.items = options.inventory
|
||||||
|
local entity = ExpUtil.copy_items_to_surface(options)
|
||||||
|
options.inventory.clear()
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create an enum table from a set of strings, can use custom indexes to change base
|
||||||
|
--- @param values string[]
|
||||||
|
--- @return table<string | number, string | number>
|
||||||
|
function ExpUtil.enum(values)
|
||||||
|
local enum = {}
|
||||||
|
|
||||||
|
local index = 0 -- Real index within values
|
||||||
|
local offset = 0 -- Offset from base for next index
|
||||||
|
local base = 0 -- Start point for offset
|
||||||
|
for k, v in pairs(values) do
|
||||||
|
index = index + 1
|
||||||
|
if k ~= index then
|
||||||
|
offset = 0
|
||||||
|
base = k
|
||||||
|
end
|
||||||
|
enum[base + offset] = v
|
||||||
|
offset = offset + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in pairs(enum) do
|
||||||
|
if type(k) == "number" then
|
||||||
|
enum[v] = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return enum
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns a string for a number with comma separators
|
||||||
|
--- @param n number
|
||||||
|
--- @return string
|
||||||
|
function ExpUtil.comma_value(n) -- credit http://richard.warburton.it
|
||||||
|
local left, num, right = string.match(n, "^([^%d]*%d)(%d*)(.-)$")
|
||||||
|
return left .. (num:reverse():gsub("(%d%d%d)", "%1, "):reverse()) .. right
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns a message formatted for game chat using rich text colour tags
|
||||||
|
--- @param message string
|
||||||
|
--- @param color Color
|
||||||
|
--- @return string
|
||||||
|
function ExpUtil.format_rich_text_color(message, color)
|
||||||
|
return format_string(
|
||||||
|
"[color=%s,%s,%s]%s[/color]",
|
||||||
|
round(color.r or color[1] or 0, 3),
|
||||||
|
round(color.g or color[2] or 0, 3),
|
||||||
|
round(color.b or color[3] or 0, 3),
|
||||||
|
message
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns a message formatted for game chat using rich text colour tags
|
||||||
|
--- @param message LocalisedString
|
||||||
|
--- @param color Color
|
||||||
|
--- @return LocalisedString
|
||||||
|
function ExpUtil.format_rich_text_color_locale(message, color)
|
||||||
|
return {
|
||||||
|
"rich-text-color-tag",
|
||||||
|
round(color.r or color[1] or 0, 3),
|
||||||
|
round(color.g or color[2] or 0, 3),
|
||||||
|
round(color.b or color[3] or 0, 3),
|
||||||
|
message
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Formats a players name using rich text color
|
||||||
|
--- @param player PlayerIdentification?
|
||||||
|
--- @return string
|
||||||
|
function ExpUtil.format_player_name(player)
|
||||||
|
local valid_player = type(player) == "userdata" and player or game.get_player(player --[[@as string|number]]) --[[@as LuaPlayer?]]
|
||||||
|
local player_name = valid_player and valid_player.name or "<Server>"
|
||||||
|
local player_chat_colour = valid_player and valid_player.chat_color or ExpUtil.color.white
|
||||||
|
return ExpUtil.format_rich_text_color(player_name, player_chat_colour)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Formats a players name using rich text color
|
||||||
|
--- @param player PlayerIdentification?
|
||||||
|
--- @return LocalisedString
|
||||||
|
function ExpUtil.format_player_name_locale(player)
|
||||||
|
local valid_player = type(player) == "userdata" and player or game.get_player(player --[[@as string|number]]) --[[@as LuaPlayer?]]
|
||||||
|
local player_name = valid_player and valid_player.name or "<Server>"
|
||||||
|
local player_chat_colour = valid_player and valid_player.chat_color or ExpUtil.color.white
|
||||||
|
return ExpUtil.format_rich_text_color_locale(player_name, player_chat_colour)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Teleport a player to a position on a surface
|
||||||
|
--- @param player LuaPlayer Player to teleport
|
||||||
|
--- @param surface LuaSurface Destination surface
|
||||||
|
--- @param position MapPosition Destination position
|
||||||
|
--- @param vehicle_behaviour "allow"|"disallow"|"dismount"? How to handle players who are in a vehicle, default is dismount
|
||||||
|
--- @return boolean # True if teleported successfully
|
||||||
|
function ExpUtil.teleport_player(player, surface, position, vehicle_behaviour)
|
||||||
|
local found_position = surface.find_non_colliding_position("character", position, 32, 1)
|
||||||
|
|
||||||
|
-- Return false if no new position
|
||||||
|
if not found_position then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if the player is in a vehicle
|
||||||
|
local vehicle = player.vehicle
|
||||||
|
if not vehicle then
|
||||||
|
return player.teleport(found_position, surface)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle different vehicle behaviour
|
||||||
|
if vehicle_behaviour == "disallow" then
|
||||||
|
return false
|
||||||
|
elseif vehicle_behaviour == "dismount" then
|
||||||
|
player.driving = false
|
||||||
|
return player.teleport(found_position, surface)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Teleport the vehicle, or the player if that fails
|
||||||
|
local vehicle_position = surface.find_non_colliding_position(vehicle.name, position, 32, 1)
|
||||||
|
if not vehicle_position or not vehicle.teleport(vehicle_position, surface) then
|
||||||
|
player.driving = false
|
||||||
|
return player.teleport(found_position, surface)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return ExpUtil
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
--[[ Util Module - Storage
|
--[[ ExpUtil - Storage
|
||||||
Provides a method of using storage with the guarantee that keys will not conflict
|
Provides a method of using storage with the guarantee that keys will not conflict
|
||||||
|
|
||||||
--- Drop in boiler plate:
|
--- Drop in boiler plate:
|
||||||
-- Below is a drop in boiler plate which ensures your storage access will not conflict with other modules
|
-- Below is a drop in boiler plate which ensures your storage access will not conflict with other modules
|
||||||
local storage =
|
local storage = {}
|
||||||
Storage.register({
|
Storage.register(storage, function(tbl)
|
||||||
my_table = {},
|
storage = tbl
|
||||||
my_primitive = 1,
|
end)
|
||||||
}, function(tbl)
|
|
||||||
storage = tbl
|
|
||||||
end)
|
|
||||||
|
|
||||||
--- Registering new storage tables:
|
--- Registering new storage tables:
|
||||||
-- The boiler plate above is not recommend because it is not descriptive in its function
|
-- The boiler plate above is not recommend because it is not descriptive in its function
|
||||||
@@ -48,39 +45,37 @@ local my_metatable = Storage.register_metatable("MyMetaTable", {
|
|||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local Clustorio = require("modules/clusterio/api")
|
|
||||||
local ExpUtil = require("modules/exp_util/common")
|
local ExpUtil = require("modules/exp_util/common")
|
||||||
|
|
||||||
|
--- @class ExpUtil_Storage
|
||||||
local Storage = {
|
local Storage = {
|
||||||
--- @package
|
_registered = {}, --- @type table<string, { init: table, callback: fun(tbl: table) }> Map of all registered values and their initial values
|
||||||
registered = {}, --- @type { [string]: { init: table, callback: fun(tbl: table) } } Map of all registered values and their initial values
|
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Register a new table to be stored in storage, can only be called once per file, can not be called during runtime
|
--- Register a new table to be stored in storage, can only be called once per file, can not be called during runtime
|
||||||
--- @param tbl table The initial value for the table you are registering, this should be a local variable
|
--- @generic T:table
|
||||||
--- @param callback fun(tbl: table) The callback used to replace local references and metatables
|
--- @param tbl T The initial value for the table you are registering, this should be a local variable
|
||||||
--- @return table # The table passed as the first argument
|
--- @param callback fun(tbl: T) The callback used to replace local references and metatables
|
||||||
|
-- This function does not return the table because the callback can't access the local it would be assigned to
|
||||||
function Storage.register(tbl, callback)
|
function Storage.register(tbl, callback)
|
||||||
ExpUtil.assert_not_runtime()
|
ExpUtil.assert_not_runtime()
|
||||||
ExpUtil.assert_argument_type(tbl, "table", 1, "tbl")
|
ExpUtil.assert_argument_type(tbl, "table", 1, "tbl")
|
||||||
ExpUtil.assert_argument_type(callback, "function", 2, "callback")
|
ExpUtil.assert_argument_type(callback, "function", 2, "callback")
|
||||||
|
|
||||||
local name = ExpUtil.safe_file_path(2)
|
local name = ExpUtil.safe_file_path(2)
|
||||||
if Storage.registered[name] then
|
if Storage._registered[name] then
|
||||||
error("Storage.register can only be called once per file", 2)
|
error("Storage.register can only be called once per file", 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
Storage.registered[name] = {
|
Storage._registered[name] = {
|
||||||
init = tbl,
|
init = tbl,
|
||||||
callback = callback,
|
callback = callback,
|
||||||
}
|
}
|
||||||
|
|
||||||
return tbl
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Register a metatable which will be automatically restored during on_load
|
--- Register a metatable which will be automatically restored during on_load
|
||||||
--- @param name string The name of the metatable to register, must be unique within your module
|
--- @param name string The name of the metatable to register, must be unique within your module
|
||||||
--- @param tbl table The metatable to register
|
--- @param tbl metatable The metatable to register
|
||||||
--- @return table # The metatable passed as the second argument
|
--- @return table # The metatable passed as the second argument
|
||||||
function Storage.register_metatable(name, tbl)
|
function Storage.register_metatable(name, tbl)
|
||||||
local module_name = ExpUtil.get_module_name(2)
|
local module_name = ExpUtil.get_module_name(2)
|
||||||
@@ -91,10 +86,10 @@ end
|
|||||||
--- Restore aliases on load, we do not need to initialise data during this event
|
--- Restore aliases on load, we do not need to initialise data during this event
|
||||||
--- @package
|
--- @package
|
||||||
function Storage.on_load()
|
function Storage.on_load()
|
||||||
--- @type { [string]: table }
|
--- @type table<string, table>
|
||||||
local exp_storage = storage.exp_storage
|
local exp_storage = storage.exp_storage
|
||||||
if exp_storage == nil then return end
|
if exp_storage == nil then return end
|
||||||
for name, info in pairs(Storage.registered) do
|
for name, info in pairs(Storage._registered) do
|
||||||
if exp_storage[name] ~= nil then
|
if exp_storage[name] ~= nil then
|
||||||
info.callback(exp_storage[name])
|
info.callback(exp_storage[name])
|
||||||
end
|
end
|
||||||
@@ -104,14 +99,14 @@ end
|
|||||||
--- Event Handler, sets initial values if needed and calls all callbacks
|
--- Event Handler, sets initial values if needed and calls all callbacks
|
||||||
--- @package
|
--- @package
|
||||||
function Storage.on_init()
|
function Storage.on_init()
|
||||||
--- @type { [string]: table }
|
--- @type table<string, table>
|
||||||
local exp_storage = storage.exp_storage
|
local exp_storage = storage.exp_storage
|
||||||
if exp_storage == nil then
|
if exp_storage == nil then
|
||||||
exp_storage = {}
|
exp_storage = {}
|
||||||
storage.exp_storage = exp_storage
|
storage.exp_storage = exp_storage
|
||||||
end
|
end
|
||||||
|
|
||||||
for name, info in pairs(Storage.registered) do
|
for name, info in pairs(Storage._registered) do
|
||||||
if exp_storage[name] == nil then
|
if exp_storage[name] == nil then
|
||||||
exp_storage[name] = info.init
|
exp_storage[name] = info.init
|
||||||
end
|
end
|
||||||
@@ -119,9 +114,12 @@ function Storage.on_init()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @package
|
local events = {}
|
||||||
Storage.events = {
|
|
||||||
[Clustorio.events.on_server_startup] = Storage.on_init,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
local Clustorio = ExpUtil.optional_require("modules/clusterio/api")
|
||||||
|
if Clustorio then
|
||||||
|
events[Clustorio.events.on_server_startup] = Storage.on_init
|
||||||
|
end
|
||||||
|
|
||||||
|
Storage.events = events --- @package
|
||||||
return Storage
|
return Storage
|
||||||
|
|||||||
Reference in New Issue
Block a user