mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 19:45:22 +09:00
671 lines
37 KiB
Lua
671 lines
37 KiB
Lua
--- Factorio Softmod Manager
|
|
-- @module FSM
|
|
-- @alias Manager
|
|
-- @author Cooldude2606
|
|
-- @usage Manager = require("FactorioSoftmodManager")
|
|
local moduleIndex = require("/modules/index")
|
|
local Manager = {}
|
|
|
|
--- Setup for metatable of the Manager to force read only nature
|
|
-- @usage Manager() -- runs Manager.loadModdules()
|
|
-- @usage Manager[name] -- returns module by that name
|
|
-- @usage tostring(Manager) -- returns formated list of loaded modules
|
|
local ReadOnlyManager = setmetatable({},{
|
|
__metatable=false,
|
|
__index=function(tbl,key)
|
|
-- first looks in manager and then looks in mander.loadModules
|
|
return rawget(Manager,key) ~= nil and rawget(Manager,key) or rawget(Manager.loadModules,key)
|
|
end,
|
|
__call=function(tbl)
|
|
-- if there are no modules loaded then it loads them
|
|
if #tbl.loadModules == 0 then
|
|
tbl.loadModules()
|
|
end
|
|
end,
|
|
__newindex=function(tbl,key,value)
|
|
-- provents the changing of any key that is not currentState
|
|
if key == 'currentState' then
|
|
-- provides a verbose that is always emited describing the change in state
|
|
Manager.verbose(string.rep('__',10)..'| Start: '..value..' |'..string.rep('__',10),true)
|
|
Manager.verbose('The verbose state is now: '..tostring(Manager.setVerbose[value]),true)
|
|
rawset(Manager,key,value)
|
|
else error('Manager is read only please use included methods',2) end
|
|
end,
|
|
__tostring=function(tbl)
|
|
-- acts as a redirect
|
|
return tostring(Manager.loadModules)
|
|
end
|
|
})
|
|
|
|
local function setupModuleName(name)
|
|
-- creates a table that acts like a string but is read only
|
|
return setmetatable({},{
|
|
__index=function(tbl,key) return name end,
|
|
__newindex=function(tbl,key,value) error('Module Name Is Read Only') end,
|
|
__tostring=function(tbl) return name end,
|
|
__concat=function(val1,val2) return type(val1) == 'string' and val1..name or name..val2 end,
|
|
__metatable=false,
|
|
})
|
|
end
|
|
|
|
Manager.currentState = 'selfInit'
|
|
-- selfInit > moduleLoad > moduleInit > modulePost > moduleEnv
|
|
|
|
--- Default output for the verbose
|
|
-- @usage Manager.verbose('Hello, World!')
|
|
-- @tparam string rtn the value that will be returned though verbose output
|
|
Manager._verbose = function(rtn)
|
|
-- creates one file per game, ie clears file on reset
|
|
if game and Manager.setVerbose._output ~= true then Manager.setVerbose._output=true game.write_file('verbose.log',rtn)
|
|
elseif game then game.write_file('verbose.log','\n'..rtn,true) end
|
|
-- standard print and log, _log is a version of log which is ln1 of control.lua for shorter log lines
|
|
if print then print(rtn) end
|
|
if _log then _log(rtn) end
|
|
end
|
|
|
|
--- Used to call the output of the verbose when the current state allows it
|
|
-- @usage Manager.verbose('Hello, World!')
|
|
-- @tparam string rtn the value that will be returned though verbose output
|
|
-- @tparam string action is used to decide which verbose this is error || event etc
|
|
Manager.verbose = function(rtn,action)
|
|
local settings = Manager.setVerbose
|
|
local state = Manager.currentState
|
|
if Manager.error and state == Manager.error.__crash then return end
|
|
-- if ran in a module the the global module_name is present
|
|
local rtn = type(rtn) == table and serpent.line(rtn) or tostring(rtn)
|
|
if module_name then rtn='['..module_name..'] '..rtn
|
|
else rtn='[FSM] '..rtn end
|
|
-- module_verbose is a local override for a file, action is used in the manager to describe an extra type, state is the current state
|
|
-- if action is true then it will always trigger verbose
|
|
if module_verbose or (action and (action == true or settings[action])) or (not action and settings[state]) then
|
|
if type(settings.output) == 'function' then
|
|
-- calls the output function, not pcalled as if this fails some thing is very wrong
|
|
settings.output(rtn)
|
|
else
|
|
error('Verbose set for: '..state..' but output can not be called',2)
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Main logic for allowing verbose at different stages though out the script
|
|
-- @function Manager.setVerbose
|
|
-- @usage Manager.setVerbose{output=log}
|
|
-- @tparam newTbl settings the table that will be searched for settings to be updated
|
|
-- @usage Manager.setVerbose[setting] -- returns the value of that setting
|
|
-- @usage tostring(Manager.setVerbose) -- returns a formated list of the current settings
|
|
Manager.setVerbose = setmetatable(
|
|
--- Different verbose settings used for setVerbose
|
|
-- @table Manager.verboseSettings
|
|
-- @tfield boolean selfInit called while the manager is being set up
|
|
-- @tfield boolean moduleLoad when a module is required by the manager
|
|
-- @tfield boolean moduleInit when and within the initation of a module
|
|
-- @tfield boolean modulePost when and within the post of a module
|
|
-- @tfield boolean moduleEnv during module runtime, this is a global option set within each module(module_verbose=true ln:1) for fine control
|
|
-- @tfield boolean eventRegistered when a module registers its event handlers
|
|
-- @tfield boolean errorCaught when an error is caught during runtime
|
|
-- @tfield function output can be: print || log || or other function
|
|
-- @field _output a constant value that can used to store output data
|
|
{
|
|
selfInit=true,
|
|
moduleLoad=false,
|
|
moduleInit=false,
|
|
modulePost=false,
|
|
moduleEnv=false,
|
|
eventRegistered=false,
|
|
errorCaught=true,
|
|
output=Manager._verbose,
|
|
_output={}
|
|
},
|
|
{
|
|
__metatable=false,
|
|
__call=function(tbl,settings)
|
|
-- does not allow any new keys, but will override any existing ones
|
|
for key,value in pairs(settings) do
|
|
if rawget(tbl,key) ~= nil then
|
|
Manager.verbose('Verbose for: "'..key..'" has been set to: '..tostring(value))
|
|
rawset(tbl,key,value)
|
|
end
|
|
end
|
|
end,
|
|
__newindex=function(tbl,key,value)
|
|
-- stops creationg of new keys
|
|
error('New settings cannot be added during runtime',2)
|
|
end,
|
|
__index=function(tbl,key)
|
|
-- will always return a value, never nil
|
|
return rawget(tbl,key) or false
|
|
end,
|
|
__tostring=function(tbl)
|
|
-- a simple concat function for the settings
|
|
local rtn = ''
|
|
for key,value in pairs(tbl) do
|
|
if type(value) == 'boolean' then
|
|
rtn=rtn..key..': '..tostring(value)..', '
|
|
end
|
|
end
|
|
return rtn:sub(1,-3)
|
|
end
|
|
}
|
|
)
|
|
-- call to verbose to show start up, will always be present
|
|
Manager.verbose(string.rep('__',10)..'| Start: selfInit |'..string.rep('__',10),true)
|
|
Manager.verbose('The verbose state is: '..tostring(Manager.setVerbose.selfInit),true)
|
|
|
|
--- Used to avoid conflicts in the global table
|
|
-- @usage global[key] -- used like the normal global table
|
|
-- @usage global{'foo','bar'} -- sets the default value
|
|
-- @usage global(true) -- restores global to default
|
|
-- @usage global(mopdule_name) -- returns that module's global
|
|
-- @tparam[opt={}] ?table|string|true if table then the default for the global, if a string then the module to get the global of, if true then reset the global to default
|
|
-- @treturn table the new global table for that module
|
|
Manager.global=setmetatable({__defaults={},__global={
|
|
__call=function(tbl,default) return Manager.global(default) end,
|
|
__index=function(tbl,key) return rawget(Manager.global(),key) or tbl(key) end,
|
|
__newindex=function(tbl,key,value) rawset(Manager.global(),key,value) end,
|
|
__pairs=function(tbl)
|
|
local tbl = Manager.global()
|
|
local function next_pair(tbl,k)
|
|
k, v = next(tbl, k)
|
|
if type(v) ~= nil then return k,v end
|
|
end
|
|
return next_pair, tbl, nil
|
|
end
|
|
}},{
|
|
__call=function(tbl,default,tbl2)
|
|
local Global = _G.global
|
|
local tbl2 = type(tbl2) == 'table' and getmetatable(tbl2) or nil
|
|
local module_name = type(default) == 'string' and default or tbl2 and tbl2.name or module_name
|
|
local module_path = type(default) == 'string' and moduleIndex[default] or tbl2 and tbl2.path or module_path
|
|
if not module_path or not module_name then return _G.global end
|
|
if type(default) == 'table' then Manager.verbose('Default global has been set for: global'..module_path:gsub('/','.')) rawset(rawget(tbl,'__defaults'),tostring(module_name),default) end
|
|
local path = 'global'
|
|
local new_dir = false
|
|
for dir in module_path:gmatch('%a+') do
|
|
path = path..'.'..dir
|
|
if not rawget(Global,dir) then new_dir=true Manager.verbose('Added Global Dir: '..path) rawset(Global,dir,{}) end
|
|
Global = rawget(Global,dir)
|
|
end
|
|
if (new_dir or default == true) and rawget(rawget(tbl,'__defaults'),tostring(module_name)) then
|
|
Manager.verbose('Set Global Dir: '..path..' to its default')
|
|
-- cant set it to be equle otherwise it will lose its global propeity
|
|
local function deepcopy(tbl) if type(tbl) ~= 'table' then return tbl end local rtn = {} for key,value in pairs(tbl) do rtn[key] = deepcopy(value) end return rtn end
|
|
for key,value in pairs(Global) do rawset(Global,key,nil) end
|
|
for key,value in pairs(rawget(rawget(tbl,'__defaults'),tostring(module_name))) do rawset(Global,key,deepcopy(value)) end
|
|
end
|
|
return setmetatable(Global,{
|
|
__call=function(tbl,default) return Manager.global(default,tbl) end,
|
|
__index=function(tbl,key) return rawget(Manager.global(),key) or moduleIndex[key] and Manager.global(key) end,
|
|
path=module_path,name=module_name
|
|
})
|
|
end,
|
|
__index=function(tbl,key) return rawget(tbl(),key) or rawget(_G.global,key) or moduleIndex[key] and Manager.global(key) end,
|
|
__newindex=function(tbl,key,value) rawset(tbl(),key,value) end,
|
|
__pairs=function(tbl)
|
|
local tbl = Manager.global()
|
|
local function next_pair(tbl,k)
|
|
k, v = next(tbl, k)
|
|
if type(v) ~= nil then return k,v end
|
|
end
|
|
return next_pair, tbl, nil
|
|
end
|
|
})
|
|
setmetatable(global,Manager.global.__global)
|
|
|
|
--- Creates a sand box envorment and runs a callback in that sand box; provents global pollution
|
|
-- @function Manager.sandbox
|
|
-- @usage Manager.sandbox(callback) -- return sandbox, success, other returns from callback
|
|
-- @tparam function callback the function that will be ran in the sandbox
|
|
-- @param[opt] env any other params that the function will use
|
|
-- @usage Manager.sandbox() -- returns and empty sandbox
|
|
-- @usage Manager.sandbox[key] -- returns the sand box value in that key
|
|
Manager.sandbox = setmetatable({
|
|
-- can not use existing keys of _G
|
|
verbose=Manager.verbose,
|
|
loaded_modules={}, -- this is over riden later
|
|
module_verbose=false,
|
|
module_exports=false,
|
|
_no_error_in_sandbox=true
|
|
},{
|
|
__metatable=false,
|
|
__index=ReadOnlyManager,
|
|
__call=function(tbl,callback,env,...)
|
|
if type(callback) == 'function' then
|
|
-- creates a new sandbox env
|
|
local sandbox = tbl()
|
|
local env = type(env) == 'table' and env or type(env) ~= 'nil' and {env} or {}
|
|
-- new indexs are saved into sandbox and if _G does not have the index then look in sandbox
|
|
local old_mt = getmetatable(_G) or {}
|
|
setmetatable(env,{__index=sandbox})
|
|
setmetatable(_G,{__index=env,__newindex=sandbox})
|
|
-- runs the callback
|
|
local rtn = {pcall(callback,...)}
|
|
local success = table.remove(rtn,1)
|
|
-- this is to allow modules to be access with out the need of using Mangaer[name] also keeps global clean
|
|
setmetatable(_G,old_mt)
|
|
if success then return sandbox, success, rtn
|
|
else return sandbox, success, rtn[1] end
|
|
else return setmetatable({},{__index=tbl}) end
|
|
end
|
|
})
|
|
|
|
--- Allows access to modules via require and collections are returned as one object
|
|
-- @function Manager.require
|
|
-- @usage local Module = Manager.require(ModuleName)
|
|
-- @usage local Module = Manager.require[ModuleName]
|
|
-- @usage local SrcData = Manager.require(path)
|
|
-- @treturn table the module that was required, one object containg submodules for a
|
|
Manager.require = setmetatable({
|
|
__require=require
|
|
},{
|
|
__metatable=false,
|
|
__index=function(tbl,key) return tbl(key,nil,true) end,
|
|
__call=function(tbl,path,env,mute)
|
|
local raw_require = rawget(tbl,'__require')
|
|
local env = env or {}
|
|
-- runs in a sand box becuase sandbox everything
|
|
local sandbox, success, data = Manager.sandbox(raw_require,env,path)
|
|
-- if there was no error then it assumed the path existed and returns the data
|
|
if success then return unpack(data)
|
|
else
|
|
if type(path) ~= 'string' then error('Path supplied must be a string; got: '..type(path),2) return end
|
|
-- else it assums the path was a module name and checks index for the module
|
|
if moduleIndex[path] then return rawget(Manager.loadModules,path) end
|
|
if moduleIndex[path:gsub('?','')] then return rawget(Manager.loadModules,path) end
|
|
-- if its not listed then it tries to remove a version tag and tries again
|
|
local path_no_version = path:find('@') and path:sub(1,path:find('@')-1) or path
|
|
if moduleIndex[path_no_version] then return rawget(Manager.loadModules,path_no_version) end
|
|
-- still no then it will look for all modules that include this one in the name (like a collection)
|
|
local collection = {}
|
|
for module_name,path in pairs(moduleIndex) do
|
|
if module_name:find('@') and module_name:sub(1,module_name:find('@')-1) == path_no_version then return rawget(Manager.loadModules,module_name) end
|
|
if module_name:find(path_no_version) then
|
|
local start, _end = module_name:find(path_no_version)
|
|
collection[module_name:sub(_end+2)] = rawget(Manager.loadModules,module_name)
|
|
end
|
|
end
|
|
-- if there is any keys in the collection the collection is returned else the errors with the require error
|
|
for _ in pairs(collection) do return collection end
|
|
if mute then return false else error(data,2) end
|
|
end
|
|
end
|
|
})
|
|
require = Manager.require
|
|
|
|
--- Loads the modules that are present in the index list
|
|
-- @function Manager.loadModules
|
|
-- @usage Manager.loadModules() -- loads all moddules in the index list
|
|
-- @usage #Manager.loadModules -- returns the number of modules loaded
|
|
-- @usage tostring(Manager.loadModules) -- returns a formatted list of all modules loaded
|
|
-- @usage pairs(Manager.loadModules) -- loops over the loaded modules moduleName, module
|
|
Manager.loadModules = setmetatable({},
|
|
{
|
|
__metatable=false,
|
|
__index=Manager.require,
|
|
__call=function(tbl)
|
|
-- ReadOnlyManager used to trigger verbose change
|
|
ReadOnlyManager.currentState = 'moduleLoad'
|
|
-- goes though the index looking for modules
|
|
for module_name,path in pairs(moduleIndex) do
|
|
Manager.verbose('Loading module: "'..module_name..'"; path: '..path)
|
|
-- runs the module in a sandbox env
|
|
local sandbox, success, module = Manager.sandbox(Manager.require.__require,{module_name=setupModuleName(module_name),module_path=path},path..'/control')
|
|
-- extracts the module into a global index table for later use
|
|
if success then
|
|
-- verbose to notifie of any globals that were attempted to be created
|
|
local globals = ''
|
|
for key,value in pairs(sandbox) do globals = globals..key..', ' end
|
|
if globals ~= '' then Manager.verbose('Globals caught in "'..module_name..'": '..globals:sub(1,-3),'errorCaught') end
|
|
Manager.verbose('Successfully loaded: "'..module_name..'"; path: '..path)
|
|
-- sets that it has been loaded and adds to the loaded index
|
|
-- if you prefere module_exports can be used rather than returning the module
|
|
if type(tbl[module_name]) == 'nil' then
|
|
-- if it is a new module then creat the new index
|
|
if string.find(module_name,'GlobalLib') then
|
|
Manager.verbose('Extracting GlobalLib: '..module_name)
|
|
-- if it is named GlobalLib then it will be auto extracted into _G
|
|
if sandbox.module_exports and type(sandbox.module_exports) == 'table'
|
|
then for key,value in pairs(sandbox.module_exports) do _G[key] = value end
|
|
else for key,value in pairs(table.remove(module,1)) do _G[key] = value end end
|
|
else
|
|
if sandbox.module_exports and type(sandbox.module_exports) == 'table'
|
|
then tbl[module_name] = sandbox.module_exports
|
|
else tbl[module_name] = table.remove(module,1) end
|
|
end
|
|
elseif type(tbl[module_name]) == 'table' then
|
|
-- if this module adds onto an existing one then append the keys
|
|
if sandbox.module_exports and type(sandbox.module_exports) == 'table'
|
|
then for key,value in pairs(sandbox.module_exports) do tbl[module_name][key] = value end
|
|
else for key,value in pairs(table.remove(module,1)) do tbl[module_name][key] = value end end
|
|
else
|
|
-- if it is a function then it is still able to be called even if more keys are going to be added
|
|
-- if it is a string then it will act like one; if it is a number well thats too many metatable indexs
|
|
tbl[module_name] = setmetatable({__old=tbl[module_name]},{
|
|
__call=function(tbl,...) if type(tbl.__old) == 'function' then tbl.__old(...) else return tbl.__old end end,
|
|
__tostring=function(tbl) return tbl.__old end,
|
|
__concat=function(tbl,val) return tbl.__old..val end
|
|
})
|
|
-- same as above for adding the keys to the table
|
|
if sandbox.module_exports and type(sandbox.module_exports) == 'table'
|
|
then for key,value in pairs(sandbox.module_exports) do tbl[module_name][key] = value end
|
|
else for key,value in pairs(table.remove(module,1)) do tbl[module_name][key] = value end end
|
|
end
|
|
-- if there is a module by this name in _G ex table then it will be indexed to the new module
|
|
if rawget(_G,module_name) and type(tbl[module_name]) == 'table' then setmetatable(rawget(_G,module_name),{__index=tbl[module_name]}) end
|
|
else
|
|
Manager.verbose('Failed load: "'..module_name..'"; path: '..path..' ('..module..')','errorCaught')
|
|
for event_name,callbacks in pairs(Manager.event) do Manager.verbose('Removed Event Handler: "'..module_name..'/'..Manager.event.names[event_name],'eventRegistered') callbacks[module_name] = nil end
|
|
end
|
|
end
|
|
-- new state for the manager to allow control of verbose
|
|
ReadOnlyManager.currentState = 'moduleInit'
|
|
-- runs though all loaded modules looking for on_init function; all other modules have been loaded use this to load extra code based on opttial dependies
|
|
for module_name,data in pairs(tbl) do
|
|
-- looks for init so that init or on_init can be used
|
|
if type(data) == 'table' and data.init and data.on_init == nil then data.on_init = data.init data.init = nil end
|
|
if type(data) == 'table' and data.on_init and type(data.on_init) == 'function' then
|
|
Manager.verbose('Initiating module: "'..module_name..'"')
|
|
local sandbox, success, err = Manager.sandbox(data.on_init,{module_name=setupModuleName(module_name),module_path=moduleIndex[tostring(module_name)]},data)
|
|
if success then
|
|
Manager.verbose('Successfully Initiated: "'..module_name..'"')
|
|
else
|
|
Manager.verbose('Failed Initiation: "'..module_name..'" ('..err..')','errorCaught')
|
|
end
|
|
-- clears the init function so it cant be used in runtime
|
|
data.on_init = nil
|
|
end
|
|
end
|
|
-- new state for the manager to allow control of verbose
|
|
ReadOnlyManager.currentState = 'modulePost'
|
|
-- runs though all loaded modules looking for on_post function; all other modules have been loaded and inited, do not load extra code in this time only altar your own data
|
|
for module_name,data in pairs(tbl) do
|
|
-- looks for post so that post or on_post can be used
|
|
if type(data) == 'table' and data.post and data.on_post == nil then data.on_post = data.post data.post = nil end
|
|
if type(data) == 'table' and data.on_post and type(data.on_post) == 'function' then
|
|
Manager.verbose('Post for module: "'..module_name..'"')
|
|
local sandbox, success, err = Manager.sandbox(data.on_post,{module_name=setupModuleName(module_name),module_path=moduleIndex[tostring(module_name)]},data)
|
|
if success then
|
|
Manager.verbose('Successful post: "'..module_name..'"')
|
|
else
|
|
Manager.verbose('Failed post: "'..module_name..'" ('..err..')','errorCaught')
|
|
end
|
|
-- clears the post function so it cant be used in runtime
|
|
data.on_post = nil
|
|
end
|
|
end
|
|
-- this could also be called runtime
|
|
ReadOnlyManager.currentState = 'moduleEnv'
|
|
end,
|
|
__len=function(tbl)
|
|
-- counts the number of loaded modules
|
|
local rtn = 0
|
|
for key,value in pairs(tbl) do
|
|
rtn = rtn + 1
|
|
end
|
|
return rtn
|
|
end,
|
|
__tostring=function(tbl)
|
|
-- a concat of all the loaded modules
|
|
local rtn = 'Load Modules: '
|
|
for key,value in pairs(tbl) do
|
|
rtn=rtn..key..', '
|
|
end
|
|
return rtn:sub(1,-3)
|
|
end
|
|
}
|
|
)
|
|
Manager.sandbox.loaded_modules = Manager.loadModules
|
|
|
|
--- A more detailed replacement for the lua error function to allow for handlers to be added; repleaces default error so error can be used instead of Manager.error
|
|
-- @function Manager.error
|
|
-- @usage Manager.error(err) -- calls all error handlers that are set or if none then prints to game and if that fails crashs game
|
|
-- @usage Manager.error() -- returns an error constant that can be used to crash game
|
|
-- @usage Manager.error(Manager.error()) -- crashs the game
|
|
-- @usage Manager.error.addHandler(name,callback) -- adds a new handler if handler returns Manager.error() then game will crash
|
|
-- @tparam[2] ?string|fucntion err the string to be passed to handlers; if a function it will register a handler
|
|
-- @tparam[2] function callback if given the err param will be used to given the handler a name
|
|
-- @usage Manager.error[name] -- returns the handler of that name if present
|
|
-- @usage #Manager.error -- returns the number of error handlers that are present
|
|
-- @usage pairs(Manager.error) -- loops over only the error handlers handler_name,hander
|
|
Manager.error = setmetatable({
|
|
__crash=false,
|
|
__error_call=error,
|
|
__error_const={},
|
|
__error_handler=function(handler_name,callback)
|
|
-- when handler_name is a string it is expeced that callback is a function; other wise handler_name must be a function
|
|
if type(handler_name) == 'string' and type(callback) == 'function' then Manager.error[handler_name]=callback
|
|
elseif type(handler_name) == 'function' then table.insert(Manager.error,handler_name)
|
|
else Manager.error('Handler is not a function',2) end
|
|
end
|
|
},{
|
|
__metatalbe=false,
|
|
__call=function(tbl,err,...)
|
|
-- if no params then return the error constant
|
|
if err == nil then return rawget(tbl,'__error_const') end
|
|
-- if the error constant is given crash game
|
|
if err == rawget(tbl,'__error_const') then Manager.verbose('Force Stop','errorCaught') rawget(tbl,'__error_call')('Force Stop',2) end
|
|
-- other wise treat the call as if its been passed an err string
|
|
if _G._no_error_in_sandbox and ReadOnlyManager.currentState == 'moduleEnv' then else Manager.verbose('An error has occurred: '..err,'errorCaught') end
|
|
if #tbl > 0 then
|
|
-- there is at least one error handler loaded; loops over the error handlers
|
|
for handler_name,callback in pairs(tbl) do
|
|
local success, err = pcall(callback,err,...)
|
|
if not success then Manager.verbose('Error handler: "'..handler_name..'" failed to run ('..err..')','errorCaught') end
|
|
-- if the error constant is returned from the handler then crash the game
|
|
if err == rawget(tbl,'__error_const') then Manager.verbose('Force Stop by: '..handler_name,'errorCaught') rawset(tbl,'__crash',true) rawget(tbl,'__error_call')('Force Stop by: '..handler_name) end
|
|
end
|
|
elseif game then
|
|
-- there are no handlers loaded so it will print to the game if loaded
|
|
Manager.verbose('No error handlers loaded; Default game print used','errorCaught')
|
|
game.print(err)
|
|
else
|
|
-- all else fails it will crash the game with the error code
|
|
Manager.verbose('No error handlers loaded; Game not loaded; Forced crash: '..err,'errorCaught')
|
|
rawset(tbl,'__crash',true)
|
|
rawget(tbl,'__error_call')(err,2)
|
|
end
|
|
rawget(tbl,'__error_call')(err,2)
|
|
end,
|
|
__index=function(tbl,key)
|
|
-- this allows the __error_handler to be called from many different names
|
|
if key:lower() == 'addhandler' or key:lower() == 'sethandler' or key:lower() == 'handler' or key:lower() == 'register' then return rawget(tbl,'__error_handler')
|
|
else rawget(tbl,'__error_call')('Invalid index for error handler; please use build in methods.') end
|
|
end,
|
|
__newindex=function(tbl,key,value)
|
|
-- making a new index adds it as a handler
|
|
if type(value) == 'function' then
|
|
Manager.verbose('Added Error Handler: "'..key..'"','eventRegistered')
|
|
rawset(tbl,key,value)
|
|
end
|
|
end,
|
|
__len=function(tbl)
|
|
-- counts the number of handlers there are
|
|
local rtn=0
|
|
for handler_name,callback in pairs(tbl) do
|
|
rtn=rtn+1
|
|
end
|
|
return rtn
|
|
end,
|
|
__pairs=function(tbl)
|
|
-- will not return any of the three core values as part of pairs
|
|
local function next_pair(tbl,k)
|
|
local v
|
|
k, v = next(tbl, k)
|
|
if k == '__error_call' or k == '__error_const' or k == '__error_handler' or k == '__crash' then return next_pair(tbl,k) end
|
|
if type(v) == 'function' then return k,v end
|
|
end
|
|
return next_pair, tbl, nil
|
|
end
|
|
})
|
|
-- overrides the default error function
|
|
error=Manager.error
|
|
|
|
-- event does work a bit differnt from error, and if event breaks error is the fallback
|
|
|
|
--- Event handler that modules can use, each module can register one function per event
|
|
-- @function Manager.event
|
|
-- @usage Manager.event[event_name] = callback -- sets the callback for that event
|
|
-- @usage Manager.event[event_name] = nil -- clears the callback for that event
|
|
-- @usage Manager.event(event_name,callback) -- sets the callback for that event
|
|
-- @usage Manager.event[event_name] -- returns the callback for that event or the event id if not registered
|
|
-- @usage Manager.event(event_name) -- runs all the call backs for that event
|
|
-- @tparam ?int|string event_name that referes to an event
|
|
-- @tparam function callback the function that will be set for that event
|
|
-- @usage Manager.event() -- returns the stop value for the event proccessor, if returned during an event will stop all other callbacks
|
|
-- @usage #Manager.event -- returns the number of callbacks that are registered
|
|
-- @usage pairs(Manager.events) -- returns event_id,table of callbacks
|
|
Manager.event = setmetatable({
|
|
__stop={},
|
|
__events={},
|
|
__event=script.on_event,
|
|
__generate=script.generate_event_name,
|
|
__get_handler=script.get_event_handler,
|
|
__raise=script.raise_event,
|
|
__init=script.on_init,
|
|
__load=script.on_load,
|
|
__config=script.on_configuration_changed,
|
|
events=defines.events,
|
|
error_chache={}
|
|
},{
|
|
__metatable=false,
|
|
__call=function(tbl,event_name,new_callback,...)
|
|
if Manager.error.__crash then Manager.error.__error_call('No error handlers loaded; Game not loaded; Forced crash: '..tostring(Manager.error.__crash)) end
|
|
-- if no params then return the stop constant
|
|
if event_name == nil then return rawget(tbl,'__stop') end
|
|
-- if the event name is a table then loop over each value in that table
|
|
if type(event_name) == 'table' then
|
|
for key,_event_name in pairs(event_name) do tbl(_event_name,new_callback,...) end return
|
|
end
|
|
-- convert the event name to a number index
|
|
event_name = tonumber(event_name) or tbl.names[event_name]
|
|
-- if there is a callback present then set new callback rather than raise the event
|
|
if type(new_callback) == 'function' then
|
|
Manager.event[event_name] = new_callback return
|
|
end
|
|
-- other wise raise the event and call every callback; no use of script.raise_event due to override
|
|
if type(tbl[event_name]) == 'table' then
|
|
for module_name,callback in pairs(tbl[event_name]) do
|
|
-- loops over the call backs and which module it is from
|
|
if type(callback) ~= 'function' then error('Invalid Event Callback: "'..event_name..'/'..module_name..'"') end
|
|
local sandbox, success, err = Manager.sandbox(callback,{module_name=setupModuleName(module_name),module_path=moduleIndex[tostring(module_name)]},new_callback,...)
|
|
if not success then
|
|
local chache = tbl.error_chache
|
|
local error_message = 'Event Failed: "'..module_name..'/'..tbl.names[event_name]..'" ('..err..')'
|
|
if not chache[error_message] then Manager.verbose(error_message,'errorCaught') error(error_message) end
|
|
if tbl.names[event_name] == 'on_tick' then
|
|
if not chache[error_message] then chache[error_message] = {game.tick,1} end
|
|
if chache[error_message][1] >= game.tick-10 then chache[error_message] = {game.tick,chache[error_message][2]+1}
|
|
else chache[error_message] = nil end
|
|
if chache[error_message] and chache[error_message][2] > 100 then
|
|
Manager.verbose('There was an error happening every tick for 100 ticks, the event handler has been removed!','errorCaught')
|
|
tbl[event_name][module_name] = nil
|
|
end
|
|
end
|
|
end
|
|
-- if stop constant is returned then stop further processing
|
|
if err == rawget(tbl,'__stop') then Manager.verbose('Event Haulted By: "'..module_name..'"','errorCaught') break end
|
|
end
|
|
end
|
|
end,
|
|
__newindex=function(tbl,key,value)
|
|
-- handles the creation of new event handlers
|
|
if type(value) ~= 'function' and type(value) ~= nil then error('Attempted to set a non function value to an event',2) end
|
|
-- checks for a global module name that is present
|
|
local module_name = module_name or 'FSM'
|
|
-- converts the key to a number index for the event
|
|
Manager.verbose('Added Handler: "'..tbl.names[key]..'"','eventRegistered')
|
|
-- checks that the event has a valid table to store callbacks; if its not valid it will creat it and register a real event handler
|
|
if not rawget(rawget(tbl,'__events'),key) then
|
|
if key == -1 or key == -2 then -- this already has a handler
|
|
elseif key < 0 then rawget(tbl,tbl.names[key])(function(...) tbl(key,...) end)
|
|
else rawget(tbl,'__event')(key,function(...) tbl(key,...) end) end
|
|
rawset(rawget(tbl,'__events'),key,{}) end
|
|
-- adds callback to Manager.event.__events[event_id][module_name]
|
|
rawset(rawget(rawget(tbl,'__events'),key),tostring(module_name),value)
|
|
end,
|
|
__index=function(tbl,key)
|
|
-- few redirect key
|
|
local redirect={register=tbl,dispatch=tbl,remove=function(event_id) tbl[event_name]=nil end}
|
|
if rawget(redirect,key) then return rawget(redirect,key) end
|
|
-- proforms different look ups depentding weather the current module has an event handler registered
|
|
if module_name then
|
|
-- first looks for the event callback table and then under the module name; does same but converts the key to a number; no handler regisered so returns the converted event id
|
|
return rawget(rawget(tbl,'__events'),key) and rawget(rawget(rawget(tbl,'__events'),key),tostring(module_name))
|
|
or rawget(rawget(tbl,'__events'),rawget(tbl,'names')[key]) and rawget(rawget(rawget(tbl,'__events'),rawget(tbl,'names')[key]),tostring(module_name))
|
|
or rawget(tbl,'names')[key]
|
|
else
|
|
-- if there is no module present then it will return the full list of regisered handlers; or other wise the converted event id
|
|
return rawget(rawget(tbl,'__events'),key) or rawget(rawget(tbl,'__events'),rawget(tbl,'names')[key]) or rawget(tbl,'names')[key]
|
|
end
|
|
end,
|
|
__len=function(tbl)
|
|
-- counts every handler that is regised not just the the number of events with handlers
|
|
local rtn=0
|
|
for event,callbacks in pairs(tbl) do
|
|
for module,callback in pairs(callbacks) do
|
|
rtn=rtn+1
|
|
end
|
|
end
|
|
return rtn
|
|
end,
|
|
__pairs=function(tbl)
|
|
-- will loops over the event handlers and not Manager.event
|
|
local function next_pair(tbl,k)
|
|
k, v = next(rawget(tbl,'__events'), k)
|
|
if type(v) == 'table' then return k,v end
|
|
end
|
|
return next_pair, tbl, nil
|
|
end
|
|
})
|
|
|
|
--- Sub set to Manger.event and acts as a coverter between event_name and event_id
|
|
-- @table Manager.event.names
|
|
-- @usage Manager.event[event_name]
|
|
rawset(Manager.event,'names',setmetatable({},{
|
|
__index=function(tbl,key)
|
|
if type(key) == 'number' or tonumber(key) then
|
|
-- if it is a number then it will first look in the chache
|
|
if rawget(tbl,key) then return rawget(tbl,key) end
|
|
-- if it is a core event then it will simply return
|
|
if key == -1 then rawset(tbl,key,'__init')
|
|
elseif key == -2 then rawset(tbl,key,'__load')
|
|
elseif key == -3 then rawset(tbl,key,'__config')
|
|
else
|
|
-- if it is not a core event then it does a value look up on Manager.events aka defines.events
|
|
for event,id in pairs(rawget(Manager.event,'events')) do
|
|
if id == key then rawset(tbl,key,event) end
|
|
end
|
|
end
|
|
-- returns the value from the chache after being loaded in
|
|
return rawget(tbl,key)
|
|
-- if it is a string then no reverse look up is required
|
|
else
|
|
if key == 'on_init' or key == 'init' or key == '__init' then return -1
|
|
elseif key == 'on_load' or key == 'load' or key == '__load' then return -2
|
|
elseif key == 'on_configuration_changed' or key == 'configuration_changed' or key == '__config' then return -3
|
|
else return rawget(rawget(Manager.event,'events'),key) end
|
|
end
|
|
end
|
|
}))
|
|
|
|
script.on_init(function(...)
|
|
setmetatable(global,Manager.global.__global)
|
|
Manager.event(-1,...)
|
|
end)
|
|
|
|
script.on_load(function(...)
|
|
setmetatable(global,Manager.global.__global)
|
|
Manager.event(-2,...)
|
|
end)
|
|
--over rides for the base values; can be called though Event
|
|
Event=setmetatable({},{__call=Manager.event,__index=function(tbl,key) return Manager.event[key] or script[key] or error('Invalid Index To Table Event') end})
|
|
script.mod_name = setmetatable({},{__index=_G.module_name})
|
|
script.on_event=Manager.event
|
|
script.raise_event=Manager.event
|
|
script.on_init=function(callback) Manager.event(-1,callback) end
|
|
script.on_load=function(callback) Manager.event(-2,callback) end
|
|
script.on_configuration_changed=function(callback) Manager.event(-3,callback) end
|
|
script.get_event_handler=function(event_name) return type(Manager.event[event_name]) == 'function' and Manager.event[event_name] or nil end
|
|
script.generate_event_name=function(event_name) local event_id = Manager.event.__generate() local event_name = event_name or event_id Manager.event.events[event_name]=event_id return event_id end
|
|
-- to do set up nth tick
|
|
|
|
return ReadOnlyManager |