1
0
mirror of https://github.com/ComfyFactory/ComfyFactorio.git synced 2025-01-24 03:47:58 +02:00

Merge pull request #15 from Gerkiz/event_changes

Changes
This commit is contained in:
MewMew 2019-03-09 23:36:52 +01:00 committed by GitHub
commit e02f93d84b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1500 additions and 151 deletions

162
utils/color_presets.lua Normal file
View File

@ -0,0 +1,162 @@
-- source: https://www.rapidtables.com/web/color/RGB_Color.html
return {
maroon = {r = 128, g = 0, b = 0},
dark_red = {r = 139, g = 0, b = 0},
brown = {r = 165, g = 42, b = 42},
firebrick = {r = 178, g = 34, b = 34},
crimson = {r = 220, g = 20, b = 60},
red = {r = 255, g = 0, b = 0},
tomato = {r = 255, g = 99, b = 71},
coral = {r = 255, g = 127, b = 80},
indian_red = {r = 205, g = 92, b = 92},
light_coral = {r = 240, g = 128, b = 128},
dark_salmon = {r = 233, g = 150, b = 122},
salmon = {r = 250, g = 128, b = 114},
light_salmon = {r = 255, g = 160, b = 122},
orange_red = {r = 255, g = 69, b = 0},
dark_orange = {r = 255, g = 140, b = 0},
orange = {r = 255, g = 165, b = 0},
gold = {r = 255, g = 215, b = 0},
dark_golden_rod = {r = 184, g = 134, b = 11},
golden_rod = {r = 218, g = 165, b = 32},
pale_golden_rod = {r = 238, g = 232, b = 170},
dark_khaki = {r = 189, g = 183, b = 107},
khaki = {r = 240, g = 230, b = 140},
olive = {r = 128, g = 128, b = 0},
yellow = {r = 255, g = 255, b = 0},
yellow_green = {r = 154, g = 205, b = 50},
dark_olive_green = {r = 85, g = 107, b = 47},
olive_drab = {r = 107, g = 142, b = 35},
lawn_green = {r = 124, g = 252, b = 0},
chart_reuse = {r = 127, g = 255, b = 0},
green_yellow = {r = 173, g = 255, b = 47},
dark_green = {r = 0, g = 100, b = 0},
green = {r = 0, g = 128, b = 0},
forest_green = {r = 34, g = 139, b = 34},
lime = {r = 0, g = 255, b = 0},
lime_green = {r = 50, g = 205, b = 50},
light_green = {r = 144, g = 238, b = 144},
pale_green = {r = 152, g = 251, b = 152},
dark_sea_green = {r = 143, g = 188, b = 143},
medium_spring_green = {r = 0, g = 250, b = 154},
spring_green = {r = 0, g = 255, b = 127},
sea_green = {r = 46, g = 139, b = 87},
medium_aqua_marine = {r = 102, g = 205, b = 170},
medium_sea_green = {r = 60, g = 179, b = 113},
light_sea_green = {r = 32, g = 178, b = 170},
dark_slate_gray = {r = 47, g = 79, b = 79},
teal = {r = 0, g = 128, b = 128},
dark_cyan = {r = 0, g = 139, b = 139},
aqua = {r = 0, g = 255, b = 255},
cyan = {r = 0, g = 255, b = 255},
light_cyan = {r = 224, g = 255, b = 255},
dark_turquoise = {r = 0, g = 206, b = 209},
turquoise = {r = 64, g = 224, b = 208},
medium_turquoise = {r = 72, g = 209, b = 204},
pale_turquoise = {r = 175, g = 238, b = 238},
aqua_marine = {r = 127, g = 255, b = 212},
powder_blue = {r = 176, g = 224, b = 230},
cadet_blue = {r = 95, g = 158, b = 160},
steel_blue = {r = 70, g = 130, b = 180},
corn_flower_blue = {r = 100, g = 149, b = 237},
deep_sky_blue = {r = 0, g = 191, b = 255},
dodger_blue = {r = 30, g = 144, b = 255},
light_blue = {r = 173, g = 216, b = 230},
sky_blue = {r = 135, g = 206, b = 235},
light_sky_blue = {r = 135, g = 206, b = 250},
midnight_blue = {r = 25, g = 25, b = 112},
navy = {r = 0, g = 0, b = 128},
dark_blue = {r = 0, g = 0, b = 139},
medium_blue = {r = 0, g = 0, b = 205},
blue = {r = 0, g = 0, b = 255},
royal_blue = {r = 65, g = 105, b = 225},
blue_violet = {r = 138, g = 43, b = 226},
indigo = {r = 75, g = 0, b = 130},
dark_slate_blue = {r = 72, g = 61, b = 139},
slate_blue = {r = 106, g = 90, b = 205},
medium_slate_blue = {r = 123, g = 104, b = 238},
medium_purple = {r = 147, g = 112, b = 219},
dark_magenta = {r = 139, g = 0, b = 139},
dark_violet = {r = 148, g = 0, b = 211},
dark_orchid = {r = 153, g = 50, b = 204},
medium_orchid = {r = 186, g = 85, b = 211},
purple = {r = 128, g = 0, b = 128},
thistle = {r = 216, g = 191, b = 216},
plum = {r = 221, g = 160, b = 221},
violet = {r = 238, g = 130, b = 238},
magenta = {r = 255, g = 0, b = 255},
fuchsia = {r = 255, g = 0, b = 255},
orchid = {r = 218, g = 112, b = 214},
medium_violet_red = {r = 199, g = 21, b = 133},
pale_violet_red = {r = 219, g = 112, b = 147},
deep_pink = {r = 255, g = 20, b = 147},
hot_pink = {r = 255, g = 105, b = 180},
light_pink = {r = 255, g = 182, b = 193},
pink = {r = 255, g = 192, b = 203},
antique_white = {r = 250, g = 235, b = 215},
beige = {r = 245, g = 245, b = 220},
bisque = {r = 255, g = 228, b = 196},
blanched_almond = {r = 255, g = 235, b = 205},
wheat = {r = 245, g = 222, b = 179},
corn_silk = {r = 255, g = 248, b = 220},
lemon_chiffon = {r = 255, g = 250, b = 205},
light_golden_rod_yellow = {r = 250, g = 250, b = 210},
light_yellow = {r = 255, g = 255, b = 224},
saddle_brown = {r = 139, g = 69, b = 19},
sienna = {r = 160, g = 82, b = 45},
chocolate = {r = 210, g = 105, b = 30},
peru = {r = 205, g = 133, b = 63},
sandy_brown = {r = 244, g = 164, b = 96},
burly_wood = {r = 222, g = 184, b = 135},
tan = {r = 210, g = 180, b = 140},
rosy_brown = {r = 188, g = 143, b = 143},
moccasin = {r = 255, g = 228, b = 181},
navajo_white = {r = 255, g = 222, b = 173},
peach_puff = {r = 255, g = 218, b = 185},
misty_rose = {r = 255, g = 228, b = 225},
lavender_blush = {r = 255, g = 240, b = 245},
linen = {r = 250, g = 240, b = 230},
old_lace = {r = 253, g = 245, b = 230},
papaya_whip = {r = 255, g = 239, b = 213},
sea_shell = {r = 255, g = 245, b = 238},
mint_cream = {r = 245, g = 255, b = 250},
slate_gray = {r = 112, g = 128, b = 144},
light_slate_gray = {r = 119, g = 136, b = 153},
light_steel_blue = {r = 176, g = 196, b = 222},
lavender = {r = 230, g = 230, b = 250},
floral_white = {r = 255, g = 250, b = 240},
alice_blue = {r = 240, g = 248, b = 255},
ghost_white = {r = 248, g = 248, b = 255},
honeydew = {r = 240, g = 255, b = 240},
ivory = {r = 255, g = 255, b = 240},
azure = {r = 240, g = 255, b = 255},
snow = {r = 255, g = 250, b = 250},
black = {r = 0, g = 0, b = 0},
silver = {r = 192, g = 192, b = 192},
dim_grey = {r = 105, g = 105, b = 105},
dim_gray = {r = 105, g = 105, b = 105},
grey = {r = 128, g = 128, b = 128},
gray = {r = 128, g = 128, b = 128},
dark_grey = {r = 169, g = 169, b = 169},
dark_gray = {r = 169, g = 169, b = 169},
light_grey = {r = 211, g = 211, b = 211},
light_gray = {r = 211, g = 211, b = 211},
gainsboro = {r = 220, g = 220, b = 220},
white_smoke = {r = 245, g = 245, b = 245},
white = {r = 255, g = 255, b = 255},
jailed = {r = 255, g = 255, b = 255},
probation = {r = 255, g = 255, b = 255},
guest = {r = 255, g = 255, b = 255},
auto_trusted = {r = 192, g = 192, b = 192},
regular = {r = 0.155, g = 0.540, b = 0.898},
admin = {r = 0.093, g = 0.768, b = 0.172},
[-10] = {r = 255, g = 255, b = 255}, -- probation
[0] = {r = 255, g = 255, b = 255}, -- guest
[10] = {r = 192, g = 192, b = 192}, -- auto_trusted
[20] = {r = 0.155, g = 0.540, b = 0.898}, -- regular
[30] = {r = 0.093, g = 0.768, b = 0.172}, -- admin
success = {r = 0, g = 255, b = 0},
warning = {r = 255, g = 255, b = 0},
fail = {r = 255, g = 0, b = 0},
info = {r = 255, g = 255, b = 255}
}

11
utils/data_stages.lua Normal file
View File

@ -0,0 +1,11 @@
-- Non-applicable stages are commented out.
_STAGE = {
--settings = 1,
--data = 2,
--migration = 3,
control = 4,
init = 5,
load = 6,
--config_change = 7,
runtime = 8
}

164
utils/debug.lua Normal file
View File

@ -0,0 +1,164 @@
-- localised functions
local format = string.format
local match = string.match
local gsub = string.gsub
local serialize = serpent.line
local debug_getupvalue = debug.getupvalue
-- this
local Debug = {}
global.debug_message_count = 0
---@return number next index
local function increment()
local next = global.debug_message_count + 1
global.debug_message_count = next
return next
end
--- Takes the table output from debug.getinfo and pretties it
local function cleanup_debug(debug_table)
local short_src = match(debug_table.source, '/[^/]*/[^/]*$')
-- require will not return a valid string so short_src may be nil here
if short_src then
short_src = gsub(short_src, '%.lua', '')
end
return format('[function: %s file: %s line number: %s]', debug_table.name, short_src, debug_table.currentline)
end
---Shows the given message if debug is enabled. Uses serpent to print non scalars.
-- @param message <table|string|number|boolean>
-- @param stack_traceback <number|nil> levels of stack trace to give, defaults to 1 level if nil
function Debug.print(message, trace_levels)
if not _DEBUG then
return
end
if not trace_levels then
trace_levels = 2
else
trace_levels = trace_levels + 1
end
local traceback_string = ''
if type(message) ~= 'string' and type(message) ~= 'number' and type(message) ~= 'boolean' then
message = serialize(message)
end
message = format('[%d] %s', increment(), tostring(message))
if trace_levels >= 2 then
for i = 2, trace_levels do
local debug_table = debug.getinfo(i)
if debug_table then
traceback_string = format('%s -> %s', traceback_string, cleanup_debug(debug_table))
else
break
end
end
message = format('%s - Traceback%s', message, traceback_string)
end
if _LIFECYCLE == _STAGE.runtime then
game.print(message)
end
log(message)
end
local function get(obj, prop)
return obj[prop]
end
local function get_lua_object_type_safe(obj)
local s, r = pcall(get, obj, 'help')
if not s then
return
end
return r():match('Lua%a+')
end
--- Returns the value of the key inside the object
-- or 'InvalidLuaObject' if the LuaObject is invalid.
-- or 'InvalidLuaObjectKey' if the LuaObject does not have an entry at that key
-- @param object <table> LuaObject or metatable
-- @param key <string>
-- @return <any>
function Debug.get_meta_value(object, key)
if Debug.object_type(object) == 'InvalidLuaObject' then
return 'InvalidLuaObject'
end
local suc, value = pcall(get, object, key)
if not suc then
return 'InvalidLuaObjectKey'
end
return value
end
--- Returns the Lua data type or the factorio LuaObject type
-- or 'NoHelpLuaObject' if the LuaObject does not have a help function
-- or 'InvalidLuaObject' if the LuaObject is invalid.
-- @param object <any>
-- @return string
function Debug.object_type(object)
local obj_type = type(object)
if obj_type ~= 'table' or type(object.__self) ~= 'userdata' then
return obj_type
end
local suc, valid = pcall(get, object, 'valid')
if not suc then
-- no 'valid' property
return get_lua_object_type_safe(object) or 'NoHelpLuaObject'
end
if not valid then
return 'InvalidLuaObject'
else
return get_lua_object_type_safe(object) or 'NoHelpLuaObject'
end
end
---Shows the given message if debug is on.
---@param position Position
---@param message string
function Debug.print_position(position, message)
Debug.print(format('%s %s', serialize(position), message))
end
---Executes the given callback if cheating is enabled.
---@param callback function
function Debug.cheat(callback)
if _CHEATS then
callback()
end
end
--- Returns true if the function is a closure, false otherwise.
-- A closure is a function that contains 'upvalues' or in other words
-- has a reference to a local variable defined outside the function's scope.
-- @param func<function>
-- @return boolean
function Debug.is_closure(func)
local i = 1
while true do
local n = debug_getupvalue(func, i)
if n == nil then
return false
elseif n ~= '_ENV' then
return true
end
i = i + 1
end
end
return Debug

34
utils/dump_env.lua Normal file
View File

@ -0,0 +1,34 @@
-- A small debugging tool that writes the contents of _ENV to a file when the game loads.
-- Useful for ensuring you get the same information when loading
-- the reference and desync levels in desync reports.
-- dependencies
local table = require 'utils.table'
local Event = require 'utils.event'
-- localized functions
local inspect = table.inspect
-- local constants
local filename = 'env_dump.lua'
-- Removes metatables and the package table
local filter = function(item, path)
if path[#path] ~= inspect.METATABLE and item ~= 'package' then
return item
end
end
local function player_joined(event)
game.tick_paused = true
local dump_string = inspect(_ENV, {process = filter})
if dump_string then
local s = string.format('tick on join: %s\n%s', event.tick, dump_string)
game.write_file(filename, s)
game.print('_ENV dumped into ' .. filename)
else
game.print('_ENV not dumped, dump_string was nil')
end
game.print('Game is paused. Use /c game.tick_paused = false to resume play')
end
Event.add(defines.events.on_player_joined_game, player_joined)

View File

@ -1,122 +1,241 @@
--- This Module allows for registering multiple handlers to the same event, overcoming the limitation of script.register.
--
-- ** Event.add(event_name, handler) **
--
-- Handlers added with Event.add must be added at the control stage or in Event.on_init or Event.on_load.
-- Remember that for each player, on_init or on_load is run, never both. So if you can't add the handler in the
-- control stage add the handler in both on_init and on_load.
-- Handlers added with Event.add cannot be removed.
-- For handlers that need to be removed or added at runtime use Event.add_removable.
-- @usage
-- local Event = require 'utils.event'
-- Event.add(
-- defines.events.on_built_entity,
-- function(event)
-- game.print(serpent.block(event)) -- prints the content of the event table to console.
-- end
-- )
--
-- ** Event.add_removable(event_name, token) **
--
-- For conditional event handlers. Event.add_removable can be safely called at runtime without desync risk.
-- Only use this if you need to add the handler at runtime or need to remove the handler, otherwise use Event.add
--
-- Event.add_removable can be safely used at the control stage or in Event.on_init. If used in on_init you don't
-- need to also add in on_load (unlike Event.add).
-- Event.add_removable cannot be called in on_load, doing so will crash the game on loading.
-- Token is used because it's a desync risk to store closures inside the global table.
--
-- @usage
-- local Token = require 'utils.token'
-- local Event = require 'utils.event'
--
-- Token.register must not be called inside an event handler.
-- local handler =
-- Token.register(
-- function(event)
-- game.print(serpent.block(event)) -- prints the content of the event table to console.
-- end
-- )
--
-- The below code would typically be inside another event or a custom command.
-- Event.add_removable(defines.events.on_built_entity, handler)
--
-- When you no longer need the handler.
-- Event.remove_removable(defines.events.on_built_entity, handler)
--
-- It's not an error to register the same token multiple times to the same event, however when
-- removing only the first occurrence is removed.
--
-- ** Event.add_removable_function(event_name, func) **
--
-- Only use this function if you can't use Event.add_removable. i.e you are registering the handler at the console.
-- The same restrictions that apply to Event.add_removable also apply to Event.add_removable_function.
-- func cannot be a closure in this case, as there is no safe way to store closures in the global table.
-- A closure is a function that uses a local variable not defined in the function.
--
-- @usage
-- local Event = require 'utils.event'
--
-- If you want to remove the handler you will need to keep a reference to it.
-- global.handler = function(event)
-- game.print(serpent.block(event)) -- prints the content of the event table to console.
-- end
--
-- The below code would typically be used at the command console.
-- Event.add_removable_function(defines.events.on_built_entity, global.handler)
--
-- When you no longer need the handler.
-- Event.remove_removable_function(defines.events.on_built_entity, global.handler)
--
-- ** Other Events **
--
-- Use Event.on_init(handler) for script.on_init(handler)
-- Use Event.on_load(handler) for script.on_load(handler)
--
-- Use Event.on_nth_tick(tick, handler) for script.on_nth_tick(tick, handler)
-- Favour this event over Event.add(defines.events.on_tick, handler)
-- There are also Event.add_removable_nth_tick(tick, token) and Event.add_removable_nth_tick_function(tick, func)
-- That work the same as above.
--
-- ** Custom Scenario Events **
--
-- local Event = require 'utils.event'
--
-- local event_id = script.generate_event_name()
--
-- Event.add(
-- event_id,
-- function(event)
-- game.print(serpent.block(event)) -- prints the content of the event table to console.
-- end
-- )
--
-- The table contains extra information that you want to pass to the handler.
-- script.raise_event(event_id, {extra = 'data'})
local EventCore = require 'utils.event_core'
local Global = require 'utils.global'
local Token = require 'utils.token'
local Debug = require 'utils.debug'
local table_remove = table.remove
local core_add = EventCore.add
local core_on_init = EventCore.on_init
local core_on_load = EventCore.on_load
local core_on_nth_tick = EventCore.on_nth_tick
local stage_load = _STAGE.load
local script_on_event = script.on_event
local script_on_nth_tick = script.on_nth_tick
local generate_event_name = script.generate_event_name
local Event = {}
local init_event_name = -1
local load_event_name = -2
local handlers_added = false -- set to true after the removeable event handlers have been added.
local control_stage = true
local event_handlers = EventCore.get_event_handlers()
local on_nth_tick_event_handlers = EventCore.get_on_nth_tick_event_handlers()
-- map of event_name to handlers[]
local event_handlers = {}
-- map of nth_tick to handlers[]
local on_nth_tick_event_handlers = {}
local token_handlers = {}
local token_nth_tick_handlers = {}
local function_handlers = {}
local function_nth_tick_handlers = {}
local function call_handlers(handlers, event)
if _DEBUG then
for _, handler in ipairs(handlers) do
handler(event)
end
else
for _, handler in ipairs(handlers) do
local success, error = pcall(handler, event)
if not success then
log(error)
end
end
Global.register(
{
token_handlers = token_handlers,
token_nth_tick_handlers = token_nth_tick_handlers,
function_handlers = function_handlers,
function_nth_tick_handlers = function_nth_tick_handlers
},
function(tbl)
token_handlers = tbl.token_handlers
token_nth_tick_handlers = tbl.token_nth_tick_handlers
function_handlers = tbl.function_handlers
function_nth_tick_handlers = tbl.function_nth_tick_handlers
end
end
)
local function on_event(event)
local handlers = event_handlers[event.name]
call_handlers(handlers, event)
end
local function on_init()
local handlers = event_handlers[init_event_name]
call_handlers(handlers)
end
local function on_load()
local handlers = event_handlers[load_event_name]
call_handlers(handlers)
end
local function on_nth_tick_event(event)
local handlers = on_nth_tick_event_handlers[event.nth_tick]
call_handlers(handlers, event)
end
function Event.add(event_name, handler)
local handlers = event_handlers[event_name]
if not handlers then
event_handlers[event_name] = {handler}
script.on_event(event_name, on_event)
else
table.insert(handlers, handler)
end
end
function Event.on_init(handler)
local handlers = event_handlers[init_event_name]
if not handlers then
event_handlers[init_event_name] = {handler}
script.on_init(on_init)
else
table.insert(handlers, handler)
end
end
function Event.on_load(handler)
local handlers = event_handlers[load_event_name]
if not handlers then
event_handlers[load_event_name] = {handler}
script.on_load(on_load)
else
table.insert(handlers, handler)
end
end
function Event.on_nth_tick(tick, handler)
local handlers = on_nth_tick_event_handlers[tick]
if not handlers then
on_nth_tick_event_handlers[tick] = {handler}
script.on_nth_tick(tick, on_nth_tick_event)
else
table.insert(handlers, handler)
end
end
local Token = require 'utils.global_token'
global.event_tokens = {}
function Event.add_removable(event_name, token)
local event_tokens = global.event_tokens
local tokens = event_tokens[event_name]
if not tokens then
event_tokens[event_name] = {token}
else
table.insert(tokens, token)
local function remove(tbl, handler)
if tbl == nil then
return
end
if not control_stage then
local handler = Token.get(token)
Event.add(event_name, handler)
end
end
local function remove(t, e)
for i, v in ipairs(t) do
if v == e then
table.remove(t, i)
-- the handler we are looking for is more likly to be at the back of the array.
for i = #tbl, 1, -1 do
if tbl[i] == handler then
table_remove(tbl, i)
break
end
end
end
function Event.remove_removable(event_name, token)
local event_tokens = global.event_tokens
--- Register a handler for the event_name event.
-- This function must be called in the control stage or in Event.on_init or Event.on_load.
-- See documentation at top of file for details on using events.
-- @param event_name<number>
-- @param handler<function>
function Event.add(event_name, handler)
if _LIFECYCLE == 8 then
error('Calling Event.add after on_init() or on_load() has run is a desync risk.', 2)
end
local tokens = event_tokens[event_name]
core_add(event_name, handler)
end
--- Register a handler for the script.on_init event.
-- This function must be called in the control stage or in Event.on_init or Event.on_load
-- See documentation at top of file for details on using events.
-- @param handler<function>
function Event.on_init(handler)
if _LIFECYCLE == 8 then
error('Calling Event.on_init after on_init() or on_load() has run is a desync risk.', 2)
end
core_on_init(handler)
end
--- Register a handler for the script.on_load event.
-- This function must be called in the control stage or in Event.on_init or Event.on_load
-- See documentation at top of file for details on using events.
-- @param handler<function>
function Event.on_load(handler)
if _LIFECYCLE == 8 then
error('Calling Event.on_load after on_init() or on_load() has run is a desync risk.', 2)
end
core_on_load(handler)
end
--- Register a handler for the nth_tick event.
-- This function must be called in the control stage or in Event.on_init or Event.on_load.
-- See documentation at top of file for details on using events.
-- @param tick<number> The handler will be called every nth tick
-- @param handler<function>
function Event.on_nth_tick(tick, handler)
if _LIFECYCLE == 8 then
error('Calling Event.on_nth_tick after on_init() or on_load() has run is a desync risk.', 2)
end
core_on_nth_tick(tick, handler)
end
--- Register a token handler that can be safely added and removed at runtime.
-- Do NOT call this method during on_load.
-- See documentation at top of file for details on using events.
-- @param event_name<number>
-- @param token<number>
function Event.add_removable(event_name, token)
if type(token) ~= 'number' then
error('token must be a number', 2)
end
if _LIFECYCLE == stage_load then
error('cannot call during on_load', 2)
end
local tokens = token_handlers[event_name]
if not tokens then
token_handlers[event_name] = {token}
else
tokens[#tokens + 1] = token
end
if handlers_added then
local handler = Token.get(token)
core_add(event_name, handler)
end
end
--- Removes a token handler for the given event_name.
-- Do NOT call this method during on_load.
-- See documentation at top of file for details on using events.
-- @param event_name<number>
-- @param token<number>
function Event.remove_removable(event_name, token)
if _LIFECYCLE == stage_load then
error('cannot call during on_load', 2)
end
local tokens = token_handlers[event_name]
if not tokens then
return
@ -129,24 +248,219 @@ function Event.remove_removable(event_name, token)
remove(handlers, handler)
if #handlers == 0 then
script.on_event(event_name, nil)
script_on_event(event_name, nil)
end
end
local function add_token_handlers()
control_stage = false
--- Register a handler that can be safely added and removed at runtime.
-- The handler must not be a closure, as that is a desync risk.
-- Do NOT call this method during on_load.
-- See documentation at top of file for details on using events.
-- @param event_name<number>
-- @param func<function>
function Event.add_removable_function(event_name, func)
if _LIFECYCLE == stage_load then
error('cannot call during on_load', 2)
end
if type(func) ~= 'function' then
error('func must be a function', 2)
end
local event_tokens = global.event_tokens
if Debug.is_closure(func) then
error('func cannot be a closure as that is a desync risk. Consider using Event.add_removable(event_name, token) instead.', 2)
end
for event_name, tokens in pairs(event_tokens) do
for _, token in ipairs(tokens) do
local handler = Token.get(token)
Event.add(event_name, handler)
local funcs = function_handlers[event_name]
if not funcs then
function_handlers[event_name] = {func}
else
funcs[#funcs + 1] = func
end
if handlers_added then
core_add(event_name, func)
end
end
--- Removes a handler for the given event_name.
-- Do NOT call this method during on_load.
-- See documentation at top of file for details on using events.
-- @param event_name<number>
-- @param func<function>
function Event.remove_removable_function(event_name, func)
if _LIFECYCLE == stage_load then
error('cannot call during on_load', 2)
end
local funcs = function_handlers[event_name]
if not funcs then
return
end
local handlers = event_handlers[event_name]
remove(funcs, func)
remove(handlers, func)
if #handlers == 0 then
script_on_event(event_name, nil)
end
end
--- Register a token handler for the nth tick that can be safely added and removed at runtime.
-- Do NOT call this method during on_load.
-- See documentation at top of file for details on using events.
-- @param tick<number>
-- @param token<number>
function Event.add_removable_nth_tick(tick, token)
if _LIFECYCLE == stage_load then
error('cannot call during on_load', 2)
end
if type(token) ~= 'number' then
error('token must be a number', 2)
end
local tokens = token_nth_tick_handlers[tick]
if not tokens then
token_nth_tick_handlers[tick] = {token}
else
tokens[#tokens + 1] = token
end
if handlers_added then
local handler = Token.get(token)
core_on_nth_tick(tick, handler)
end
end
--- Removes a token handler for the nth tick.
-- Do NOT call this method during on_load.
-- See documentation at top of file for details on using events.
-- @param tick<number>
-- @param token<number>
function Event.remove_removable_nth_tick(tick, token)
if _LIFECYCLE == stage_load then
error('cannot call during on_load', 2)
end
local tokens = token_nth_tick_handlers[tick]
if not tokens then
return
end
local handler = Token.get(token)
local handlers = on_nth_tick_event_handlers[tick]
remove(tokens, token)
remove(handlers, handler)
if #handlers == 0 then
script_on_nth_tick(tick, nil)
end
end
--- Register a handler for the nth tick that can be safely added and removed at runtime.
-- The handler must not be a closure, as that is a desync risk.
-- Do NOT call this method during on_load.
-- See documentation at top of file for details on using events.
-- @param tick<number>
-- @param func<function>
function Event.add_removable_nth_tick_function(tick, func)
if _LIFECYCLE == stage_load then
error('cannot call during on_load', 2)
end
if type(func) ~= 'function' then
error('func must be a function', 2)
end
if Debug.is_closure(func) then
error('func cannot be a closure as that is a desync risk. Consider using Event.add_removable_nth_tick(tick, token) instead.', 2)
end
local funcs = function_nth_tick_handlers[tick]
if not funcs then
function_nth_tick_handlers[tick] = {func}
else
funcs[#funcs + 1] = func
end
if handlers_added then
core_on_nth_tick(tick, func)
end
end
--- Removes a handler for the nth tick.
-- Do NOT call this method during on_load.
-- See documentation at top of file for details on using events.
-- @param tick<number>
-- @param func<function>
function Event.remove_removable_nth_tick_function(tick, func)
if _LIFECYCLE == stage_load then
error('cannot call during on_load', 2)
end
local funcs = function_nth_tick_handlers[tick]
if not funcs then
return
end
local handlers = on_nth_tick_event_handlers[tick]
remove(funcs, func)
remove(handlers, func)
if #handlers == 0 then
script_on_nth_tick(tick, nil)
end
end
--- Generate a new, unique event ID.
-- @param <string> name of the event/variable that is exposed
function Event.generate_event_name(name)
local event_id = generate_event_name()
defines.events[name] = event_id -- luacheck: ignore 122
return event_id
end
-- If we're not in debug, we just turn the above function into an alias of script.generate_event_name
if not _DEBUG then
Event.generate_event_name = generate_event_name
end
local function add_handlers()
for event_name, tokens in pairs(token_handlers) do
for i = 1, #tokens do
local handler = Token.get(tokens[i])
core_add(event_name, handler)
end
end
for event_name, funcs in pairs(function_handlers) do
for i = 1, #funcs do
local handler = funcs[i]
core_add(event_name, handler)
end
end
for tick, tokens in pairs(token_nth_tick_handlers) do
for i = 1, #tokens do
local handler = Token.get(tokens[i])
core_on_nth_tick(tick, handler)
end
end
for tick, funcs in pairs(function_nth_tick_handlers) do
for i = 1, #funcs do
local handler = funcs[i]
core_on_nth_tick(tick, handler)
end
end
handlers_added = true
end
Event.on_init(add_token_handlers)
Event.on_load(add_token_handlers)
core_on_init(add_handlers)
core_on_load(add_handlers)
return Event

View File

@ -11,13 +11,23 @@ local event_handlers = {}
-- map of nth_tick to handlers[]
local on_nth_tick_event_handlers = {}
local function call_handlers(handlers, event)
if _DEBUG then
for _, handler in ipairs(handlers) do
local pcall = pcall
local log = log
local script_on_event = script.on_event
local script_on_nth_tick = script.on_nth_tick
local call_handlers
if _DEBUG then
function call_handlers(handlers, event)
for i = 1, #handlers do
local handler = handlers[i]
handler(event)
end
else
for _, handler in ipairs(handlers) do
end
else
function call_handlers(handlers, event)
for i = 1, #handlers do
local handler = handlers[i]
local success, error = pcall(handler, event)
if not success then
log(error)
@ -63,11 +73,11 @@ function Public.add(event_name, handler)
local handlers = event_handlers[event_name]
if not handlers then
event_handlers[event_name] = {handler}
script.on_event(event_name, on_event)
script_on_event(event_name, on_event)
else
table.insert(handlers, handler)
if #handlers == 1 then
script.on_event(event_name, on_event)
script_on_event(event_name, on_event)
end
end
end
@ -105,11 +115,11 @@ function Public.on_nth_tick(tick, handler)
local handlers = on_nth_tick_event_handlers[tick]
if not handlers then
on_nth_tick_event_handlers[tick] = {handler}
script.on_nth_tick(tick, on_nth_tick_event)
script_on_nth_tick(tick, on_nth_tick_event)
else
table.insert(handlers, handler)
if #handlers == 1 then
script.on_nth_tick(tick, on_nth_tick_event)
script_on_nth_tick(tick, on_nth_tick_event)
end
end
end

View File

@ -71,6 +71,20 @@ function Game.player_print(str)
end
end
function Game.get_player(mixed)
if type(mixed) == "table" then
if mixed.__self then
return mixed and mixed.valid and mixed
elseif mixed.player_index then
local player = game.players[mixed.player_index]
return player and player.valid and player
end
elseif mixed then
local player = game.players[mixed]
return player and player.valid and player
end
end
--[[
@param Position String to display at
@param text String to display

View File

@ -1,46 +1,79 @@
local Event = require 'utils.event'
local Token = require 'utils.global_token'
local Event = require 'utils.event_core'
local Token = require 'utils.token'
local Global = {}
local load_data = {}
local init_data = {}
function Global.register(tbl, callback)
if _LIFECYCLE ~= _STAGE.control then
error('can only be called during the control stage', 2)
end
local token = Token.register_global(tbl)
table.insert(load_data, {tbl = tbl, callback = callback, token = token})
Event.on_load(
function()
callback(Token.get_global(token))
end
)
end
function Global.register_init(tbl, init_handler, callback)
if _LIFECYCLE ~= _STAGE.control then
error('can only be called during the control stage', 2)
end
local token = Token.register_global(tbl)
table.insert(load_data, {tbl = tbl, callback = callback, token = token})
table.insert(init_data, {token = token, init_handler = init_handler, callback = callback})
Event.on_init(
function()
init_handler(tbl)
callback(tbl)
end
)
Event.on_load(
function()
callback(Token.get_global(token))
end
)
end
Event.on_load(
function()
for _, d in ipairs(load_data) do
local tbl = Token.get_global(d.token)
d.callback(tbl)
end
if _DEBUG then
local concat = table.concat
load_data = nil
init_data = nil
local names = {}
Global.names = names
function Global.register(tbl, callback)
local filepath = debug.getinfo(2, 'S').source:match('^.+/currently%-playing/(.+)$'):sub(1, -5)
local token = Token.register_global(tbl)
names[token] = concat {token, ' - ', filepath}
Event.on_load(
function()
callback(Token.get_global(token))
end
)
end
)
Event.on_init(
function()
for _, d in ipairs(init_data) do
local tbl = Token.get_global(d.token)
d.init_handler(tbl)
d.callback(tbl)
end
function Global.register_init(tbl, init_handler, callback)
local filepath = debug.getinfo(2, 'S').source:match('^.+/currently%-playing/(.+)$'):sub(1, -5)
local token = Token.register_global(tbl)
load_data = nil
init_data = nil
names[token] = concat {token, ' - ', filepath}
Event.on_init(
function()
init_handler(tbl)
callback(tbl)
end
)
Event.on_load(
function()
callback(Token.get_global(token))
end
)
end
)
end
return Global

342
utils/inspect.lua Normal file
View File

@ -0,0 +1,342 @@
local inspect ={
_VERSION = 'inspect.lua 3.1.0',
_URL = 'http://github.com/kikito/inspect.lua',
_DESCRIPTION = 'human-readable representations of tables',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2013 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local tostring = tostring
inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})
-- Apostrophizes the string if it has quotes, but not aphostrophes
-- Otherwise, it returns a regular quoted string
local function smartQuote(str)
if str:match('"') and not str:match("'") then
return "'" .. str .. "'"
end
return '"' .. str:gsub('"', '\\"') .. '"'
end
-- \a => '\\a', \0 => '\\0', 31 => '\31'
local shortControlCharEscapes = {
["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v"
}
local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031
for i=0, 31 do
local ch = string.char(i)
if not shortControlCharEscapes[ch] then
shortControlCharEscapes[ch] = "\\"..i
longControlCharEscapes[ch] = string.format("\\%03d", i)
end
end
local function escape(str)
return (str:gsub("\\", "\\\\")
:gsub("(%c)%f[0-9]", longControlCharEscapes)
:gsub("%c", shortControlCharEscapes))
end
local function isIdentifier(str)
return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
end
local function isSequenceKey(k, sequenceLength)
return type(k) == 'number'
and 1 <= k
and k <= sequenceLength
and math.floor(k) == k
end
local defaultTypeOrders = {
['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
['function'] = 5, ['userdata'] = 6, ['thread'] = 7
}
local function sortKeys(a, b)
local ta, tb = type(a), type(b)
-- strings and numbers are sorted numerically/alphabetically
if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
-- Two default types are compared according to the defaultTypeOrders table
if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
elseif dta then return true -- default types before custom ones
elseif dtb then return false -- custom types after default ones
end
-- custom types are sorted out alphabetically
return ta < tb
end
-- For implementation reasons, the behavior of rawlen & # is "undefined" when
-- tables aren't pure sequences. So we implement our own # operator.
local function getSequenceLength(t)
local len = 1
local v = rawget(t,len)
while v ~= nil do
len = len + 1
v = rawget(t,len)
end
return len - 1
end
local function getNonSequentialKeys(t)
local keys = {}
local sequenceLength = getSequenceLength(t)
for k,_ in pairs(t) do
if not isSequenceKey(k, sequenceLength) then table.insert(keys, k) end
end
table.sort(keys, sortKeys)
return keys, sequenceLength
end
local function getToStringResultSafely(t, mt)
local __tostring = type(mt) == 'table' and rawget(mt, '__tostring')
local str, ok
if type(__tostring) == 'function' then
ok, str = pcall(__tostring, t)
str = ok and str or 'error: ' .. tostring(str)
end
if type(str) == 'string' and #str > 0 then return str end
end
local function countTableAppearances(t, tableAppearances)
tableAppearances = tableAppearances or {}
if type(t) == 'table' then
if not tableAppearances[t] then
tableAppearances[t] = 1
for k,v in pairs(t) do
countTableAppearances(k, tableAppearances)
countTableAppearances(v, tableAppearances)
end
countTableAppearances(getmetatable(t), tableAppearances)
else
tableAppearances[t] = tableAppearances[t] + 1
end
end
return tableAppearances
end
local copySequence = function(s)
local copy, len = {}, #s
for i=1, len do copy[i] = s[i] end
return copy, len
end
local function makePath(path, ...)
local keys = {...}
local newPath, len = copySequence(path)
for i=1, #keys do
newPath[len + i] = keys[i]
end
return newPath
end
local function processRecursive(process, item, path, visited)
if item == nil then return nil end
if visited[item] then return visited[item] end
local processed = process(item, path)
if type(processed) == 'table' then
local processedCopy = {}
visited[item] = processedCopy
local processedKey
for k,v in pairs(processed) do
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
if processedKey ~= nil then
processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
end
end
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
setmetatable(processedCopy, mt)
processed = processedCopy
end
return processed
end
-------------------------------------------------------------------
local Inspector = {}
local Inspector_mt = {__index = Inspector}
function Inspector:puts(...)
local args = {...}
local buffer = self.buffer
local len = #buffer
for i=1, #args do
len = len + 1
buffer[len] = args[i]
end
end
function Inspector:down(f)
self.level = self.level + 1
f()
self.level = self.level - 1
end
function Inspector:tabify()
self:puts(self.newline, string.rep(self.indent, self.level))
end
function Inspector:alreadyVisited(v)
return self.ids[v] ~= nil
end
function Inspector:getId(v)
local id = self.ids[v]
if not id then
local tv = type(v)
id = (self.maxIds[tv] or 0) + 1
self.maxIds[tv] = id
self.ids[v] = id
end
return tostring(id)
end
function Inspector:putKey(k)
if isIdentifier(k) then return self:puts(k) end
self:puts("[")
self:putValue(k)
self:puts("]")
end
function Inspector:putTable(t)
if t == inspect.KEY or t == inspect.METATABLE then
self:puts(tostring(t))
elseif self:alreadyVisited(t) then
self:puts('<table ', self:getId(t), '>')
elseif self.level >= self.depth then
self:puts('{...}')
else
if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t)
local mt = getmetatable(t)
local toStringResult = getToStringResultSafely(t, mt)
self:puts('{')
self:down(function()
if toStringResult then
self:puts(' -- ', escape(toStringResult))
if sequenceLength >= 1 then self:tabify() end
end
local count = 0
for i=1, sequenceLength do
if count > 0 then self:puts(',') end
self:puts(' ')
self:putValue(t[i])
count = count + 1
end
for _,k in ipairs(nonSequentialKeys) do
if count > 0 then self:puts(',') end
self:tabify()
self:putKey(k)
self:puts(' = ')
self:putValue(t[k])
count = count + 1
end
if mt then
if count > 0 then self:puts(',') end
self:tabify()
self:puts('<metatable> = ')
self:putValue(mt)
end
end)
if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing }
self:tabify()
elseif sequenceLength > 0 then -- array tables have one extra space before closing }
self:puts(' ')
end
self:puts('}')
end
end
function Inspector:putValue(v)
local tv = type(v)
if tv == 'string' then
self:puts(smartQuote(escape(v)))
elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
tv == 'cdata' or tv == 'ctype' then
self:puts(tostring(v))
elseif tv == 'table' then
self:putTable(v)
else
self:puts('<',tv,' ',self:getId(v),'>')
end
end
-------------------------------------------------------------------
function inspect.inspect(root, options)
options = options or {}
local depth = options.depth or math.huge
local newline = options.newline or '\n'
local indent = options.indent or ' '
local process = options.process
if process then
root = processRecursive(process, root, {}, {})
end
local inspector = setmetatable({
depth = depth,
level = 0,
buffer = {},
ids = {},
maxIds = {},
newline = newline,
indent = indent,
tableAppearances = countTableAppearances(root)
}, Inspector_mt)
inspector:putValue(root)
return table.concat(inspector.buffer)
end
setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
return inspect

View File

@ -61,7 +61,7 @@ local data_set_handlers = {}
-- function()
-- Server.try_get_all_data('regulars', callback)
-- end)
Public.events = {on_server_started = script.generate_event_name()}
Public.events = {on_server_started = Event.generate_event_name('on_server_started')}
--- Sends a message to the linked discord channel. The message is sanitized of markdown server side.
-- @param message<string> message to send.

265
utils/table.lua Normal file
View File

@ -0,0 +1,265 @@
--luacheck:ignore global table
local random = math.random
local floor = math.floor
local remove = table.remove
local tonumber = tonumber
local pairs = pairs
local table_size = table_size
--- Searches a table to remove a specific element without an index
-- @param t <table> to search
-- @param <any> table element to search for
function table.remove_element(t, element)
for k, v in pairs(t) do
if v == element then
remove(t, k)
break
end
end
end
--- Removes an item from an array in O(1) time.
-- The catch is that fast_remove doesn't guarantee to maintain the order of items in the array.
-- @param tbl <table> arrayed table
-- @param index <number> Must be >= 0. The case where index > #tbl is handled.
function table.fast_remove(tbl, index)
local count = #tbl
if index > count then
return
elseif index < count then
tbl[index] = tbl[count]
end
tbl[count] = nil
end
--- Adds the contents of table t2 to table t1
-- @param t1 <table> to insert into
-- @param t2 <table> to insert from
function table.add_all(t1, t2)
for k, v in pairs(t2) do
if tonumber(k) then
t1[#t1 + 1] = v
else
t1[k] = v
end
end
end
--- Checks if a table contains an element
-- @param t <table>
-- @param e <any> table element
-- @returns <any> the index of the element or nil
function table.index_of(t, e)
for k, v in pairs(t) do
if v == e then
return k
end
end
return nil
end
--- Checks if the arrayed portion of a table contains an element
-- @param t <table>
-- @param e <any> table element
-- @returns <number|nil> the index of the element or nil
function table.index_of_in_array(t, e)
for i = 1, #t do
if t[i] == e then
return i
end
end
return nil
end
local index_of = table.index_of
--- Checks if a table contains an element
-- @param t <table>
-- @param e <any> table element
-- @returns <boolean> indicating success
function table.contains(t, e)
return index_of(t, e) and true or false
end
local index_of_in_array = table.index_of_in_array
--- Checks if the arrayed portion of a table contains an element
-- @param t <table>
-- @param e <any> table element
-- @returns <boolean> indicating success
function table.array_contains(t, e)
return index_of_in_array(t, e) and true or false
end
--- Adds an element into a specific index position while shuffling the rest down
-- @param t <table> to add into
-- @param index <number> the position in the table to add to
-- @param element <any> to add to the table
function table.set(t, index, element)
local i = 1
for k in pairs(t) do
if i == index then
t[k] = element
return nil
end
i = i + 1
end
error('Index out of bounds', 2)
end
--- Chooses a random entry from a table
-- because this uses math.random, it cannot be used outside of events
-- @param t <table>
-- @param key <boolean> to indicate whether to return the key or value
-- @return <any> a random element of table t
function table.get_random_dictionary_entry(t, key)
local target_index = random(1, table_size(t))
local count = 1
for k, v in pairs(t) do
if target_index == count then
if key then
return k
else
return v
end
end
count = count + 1
end
end
--- Chooses a random entry from a weighted table
-- because this uses math.random, it cannot be used outside of events
-- @param weight_table <table> of tables with items and their weights
-- @param item_index <number> of the index of items, defaults to 1
-- @param weight_index <number> of the index of the weights, defaults to 2
-- @return <any> table element
-- @see features.chat_triggers::hodor
function table.get_random_weighted(weighted_table, item_index, weight_index)
local total_weight = 0
item_index = item_index or 1
weight_index = weight_index or 2
for _, w in pairs(weighted_table) do
total_weight = total_weight + w[weight_index]
end
local index = random() * total_weight
local weight_sum = 0
for _, w in pairs(weighted_table) do
weight_sum = weight_sum + w[weight_index]
if weight_sum >= index then
return w[item_index]
end
end
end
--- 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
-- from: http://www.sdknews.com/cross-platform/corona/tutorial-how-to-shuffle-table-items
-- @param t <table> to shuffle
function table.shuffle_table(t, rng)
local rand = rng or math.random
local iterations = #t
if iterations == 0 then
error('Not a sequential table')
return
end
local j
for i = iterations, 2, -1 do
j = rand(i)
t[i], t[j] = t[j], t[i]
end
end
--- Clears all existing entries in a table
-- @param t <table> to clear
-- @param array <boolean> to indicate whether the table is an array or not
function table.clear_table(t, array)
if array then
for i = 1, #t do
t[i] = nil
end
else
for i in pairs(t) do
t[i] = nil
end
end
end
--[[
Returns the index where t[index] == target.
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.
t must be a list in ascending order for the return value to be valid.
Usage example:
local t = {1,3,5,7,9}
local x = 5
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.
local lower = 1
local upper = #t
if upper == 0 then
return -2 -- ~1
end
repeat
local mid = floor((lower + upper) * 0.5)
local value = t[mid]
if value == target then
return mid
elseif value < target then
lower = mid + 1
else
upper = mid - 1
end
until lower > upper
return -1 - lower -- ~lower
end
-- add table-related functions that exist in base factorio/util to the 'table' table
require 'util'
--- 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.
-- @param table <table> the table to serialize
-- @param options <table> 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 'utils.inspect'
--- Takes a table and returns the number of entries in the table. (Slower than #table, faster than iterating via pairs)
table.size = table_size
--- 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.
-- @param object <table> the object to copy
-- @return <table> the copied object
table.deep_copy = table.deepcopy
--- 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}
-- @param tables <table> takes a table of tables to merge
-- @return <table> a merged table
table.merge = util.merge
--- Determines if two tables are structurally equal.
-- Notice: tables that are LuaObjects or contain LuaObjects won't be compared correctly, use == operator for LuaObjects
-- @param tbl1 <table>
-- @param tbl2 <table>
-- @return <boolean>
table.equals = table.compare
return table