Files
factorio-scenario-ExpCluster/locale/ExpCore/server.lua
2017-12-08 20:18:24 +00:00

346 lines
13 KiB
Lua

--[[
Explosive Gaming
This file can be used with permission but this and the credit below must remain in the file.
Contact a member of management on our discord to seek permission to use our code.
Any changes that you may make to the code are yours but that does not make the script yours.
Discord: https://discord.gg/r6dC2uK
]]
--Please Only Edit Below This Line-----------------------------------------------------------
-- server allows control over threads and other features the devs missed out
local Server = {}
local thread = {}
--- Returns a un-used uuid (better system needed)
-- @usage obj.uuid = Server.new_uuid()
-- @treturn string the new uuid
function Server.new_uuid()
uuid = tostring(Server._uuid()())
uuid = string.to_hex('uuid'..uuid)
return uuid
end
-- use this to change the location of the server uuids
function Server._uuid(reset)
global.exp_core = not reset and global.exp_core or {}
global.exp_core.uuids = not reset and global.exp_core.uuids or game.create_random_generator()
return global.exp_core.uuids
end
--- Returns either the number of threads or a able of threads
-- @usage Server.threads() -- return {...}
-- Server.threads(true) -- return int
-- @tparam[opt=nil] bolean count true to return the number of threads
-- @return either a list of threads or a number
function Server.threads(count)
return count and #Server._threads().all or Server._threads().all
end
-- use this to change the location of the server threads
-- all stores the threads indexed uuid, the other three only store the uuid's to index in the all table
function Server._threads(reset)
global.exp_core = not reset and global.exp_core or {}
global.exp_core.threads = not reset and global.exp_core.threads or {queue={},tick={},timeout={},events={},all={}}
return global.exp_core.threads
end
-- see thread:create (this was done so thread can remain local)
function Server.new_thread(obj)
return thread:create(obj)
end
--- Adds a thread into the resolve queue, can be used to lower lag
-- @usage Server.queue_thread(thread) -- return true/false
-- @tparam table the thread to add to the queue must have a resolve function (must be open)
-- @treturn bolean was the thread added
function Server.queue_thread(thread)
if not thread and not thread.valid and not thread:valid() then return false end
if not thread._resolve then return false end
table.insert(Server._threads().queue,thread.uuid)
return true
end
--- Closes all active threads, can use force if it causes errors
-- @usage Server.close_all_threads()
-- Server.close_all_threads(true) -- use if no force makes errors
-- @tparam bolean with_force use force when closing
function Server.close_all_threads(with_force)
if not with_force then
for uuid,thread in pairs(Server.threads()) do
thread:close()
end
end
Server._threads(true)
end
--- Runs all the theads which have opened with an on_tick event
-- @ussage Server.run_tick_threads()
function Server.run_tick_threads()
table.each(Server._threads().tick,function(uuid)
local thread = Server._threads().all[uuid]
if thread and thread:valid() and thread._tick then
local success, err = pcall(thread._tick,thread)
if not success then thread:error(err) end
end
end)
end
--- Checks the timeout on all active timeout threads
-- @ussage Server.check_timeouts()
function Server.check_timeouts()
table.each(Server._threads().timeout,function(uuid)
local thread = Server._threads().all[uuid]
if thread and thread:valid() then
thread:check_timeout()
end
end)
end
--- Adds a thread to a game event (used in thread:on_event)
-- @usage Server.add_thread_handler(defines.event,thread,function)
-- @tparam number event the event to run the thread on
-- @tparam table thread the thread that will have the callback
-- @tparam function callback the function that the thread will run
-- @treturn bolean if the handler was added
function Server.add_thread_handler(event,thread,callback)
if not is_type(event,'number') or not is_type(thread,'table') or not is_type(callback,'function') then return false end
local threads = Server._threads()
if not thread._events then thread._events = {} end
thread._events[event] = callback
if not threads.events[event] then
threads.events[event] = {}
Event.register(event,function(event)
local event_id = event.name
local threads = Server._threads().events[event_id]
if not threads then return end
table.each(threads,function(uuid)
local thread = Server._threads().all[uuid]
if thread and thread:valid() then
if is_type(thread._events[event_id],'function') then
local success, err = pcall(thread._events[event_id],thread,event)
if not success then thread:error(err) end
end
end
end)
end)
end
table.insert(threads.events[event],thread.uuid)
return true
end
--- Given a string or function it will run that function and return any values
-- @usage Server.interface('local x = 1+1 print(x) return x') -- return 2
-- Server.interface('local x = 1+1 print(x)',thread) -- no return
-- @param callback either a function or string which will be ran via pcall
-- @param[opt] thread give a thread for the interface to run on (does not need to be open, but cant use on_resolve)
-- @param[opt] ... any args you want to pass to the function
function Server.interface(callback,thread,...)
if thread then
thread:on_resolve(function(callback,...)
if is_type(callback,'function') then
pcall(callback,...)
else
pcall(loadstring(callback),...)
end
end)
thread:open()
Server.queue_thread(thread)
else
if is_type(callback,'function') then
local success, err = pcall(callback,...)
return success, err
else
local success, err = pcall(loadstring(callback),...)
return success, err
end
return false
end
end
commands.add_command('server-interface', 'Runs the given input from the script', {'code',true}, function(event,args)
local callback = args.code
if not string.find(callback,'%s') and not string.find(callback,'return') then callback = 'return '..callback end
local success, err = Server.interface(callback)
player_return(err)
end)
-- thread allows you to run fuinction async to the main game
thread.__index = thread
thread.uuid = Server.new_uuid
--- Returns a new thread object
-- @usage new_thread = thread:create()
-- @tparam[opt={}] table obj all are opt {timeout=int,name=str,data=any} advanced users can prefix with _function to avoid the on_function functions
-- @treturn table the new thread object
function thread:create(obj)
local obj = obj or {}
setmetatable(obj,self)
obj.uuid = Server.new_uuid()
return obj
end
--- Test if the thread has all requied parts
-- @usage if thread:valid() then end
-- @tparam bolean skip_location_check true to skip the location check
-- @treturn bolean is the thread valid
function thread:valid(skip_location_check)
if is_type(self.uuid,'string') and
skip_location_check or is_type(self.opened,'number') and
skip_location_check or is_type(Server._threads().all[self.uuid],'table') and
is_type(self.timeout) or is_type(self.timeout,'number') and
is_type(self.name) or is_type(self.name,'string') and
is_type(self._close) or is_type(self._close,'function') and
is_type(self._timeout) or is_type(self._timeout,'function') and
is_type(self._tick) or is_type(self._tick,'function') and
is_type(self._resolve) or is_type(self._resolve,'function') and
is_type(self._success) or is_type(self._success,'function') and
is_type(self._error) or is_type(self._error,'function') then
return true
end
return false
end
--- Opens the thread by storing it in a place the server object can find it
-- @usage thread:open() -- return true
-- @treturn bolean if the thread was opened
function thread:open()
if not self:valid(true) then return false end
local threads = Server._threads()
local uuid = self.uuid
self.opened = game.tick
threads.all[uuid] = self
if is_type(self.timeout,'number') then table.insert(threads.timeout,uuid) end
if is_type(self._tick,'function') then table.insert(threads.tick,uuid) end
return true
end
--- Inverse of thread:open() - it removes the thread and calles on_close
-- @usage thread:close() -- return true
-- @treturn bolean if the thread had a on_close function
function thread:close()
local threads = Server._threads()
local uuid = self.uuid
local _return = false
if is_type(self._close,'function') then pcall(self._close,self) _return = true end
local value,key = table.find(threads.queue,function(v,k,uuid) return v == uuid end,uuid)
if key then table.remove(threads.queue,key) end
local value,key = table.find(threads.timeout,function(v,k,uuid) return v == uuid end,uuid)
if key then table.remove(threads.timeout,key) end
local value,key = table.find(threads.tick,function(v,k,uuid) return v == uuid end,uuid)
if key then table.remove(threads.tick,key) end
if self._events then
table.each(self._events,function(callback,event)
if threads.events[event] then
local value,key = table.find(threads.events[event],function(v,k,uuid) return v == uuid end,uuid)
if key then table.remove(threads.events[event],key) end
end
end)
end
table.remove(threads.all,uuid)
return _return
end
--- Trigger the on_resolve function and closes the thread - error and success called based on result of pcall (useful for async)
-- @usage thread:resolve(x,y,z) -- return true
-- @param[opt] ... any arguments you want to pass to the resolve function
-- @treturn bolean true if the thread called on_success or on_error
function thread:resolve(...)
local _return = false
if is_type(self._resolve,'function') then
local success, err = pcall(self._resolve,...)
if success then
if is_type(self._success,'function') then
pcall(self._success,self,err)
_return = true
end
else
_return = self:error(err)
end
end
self:close()
return _return
end
--- Checks the timeout on a thread - if timedout then it calles on_timeout and closes
-- @usage thread:check_timeout() -- return true
-- @treturn bolean if the thread timedout
function thread:check_timeout()
local _return = false
if not self:valid() then return false end
if is_type(self.timeout,'number') and game.tick >= (self.opened+self.timeout) then
if is_type(self._timeout,'function') then
pcall(self._timeout,self)
end
_return = true
self:close()
end
return _return
end
--- Rasies an error on this thread
-- @usage thread:error(err) -- return true
-- @param err the err to be rasied
-- @treturn bolean did the thread handdle the error
function thread:error(err)
local _return = false
if is_type(self._error,'function') then
pcall(thread._error,self,err)
_return = true
else
error(err)
end
return _return
end
--- Set function to run then an event is called on a thread, none of them are 'needed' but you are advised to have atleast one
-- @usage thread:on_event('close',function) -- return true
-- events = ['close','timeout','tick','resolve','success','error']
-- if event is a number then it is asumed to be a game event
-- @tparam string event the name of the event that it is called on
-- @tparam function callback the function which is called on the event
-- @treturn was the function added
function thread:on_event(event,callback)
local events = {'close','timeout','tick','resolve','success','error'}
local value = table.find(events,function(v,k,find) return v == string.lower(find) end,event)
if value and is_type(callback,'function') then
self['_'..value] = callback
return true
elseif is_type(event,'number') and is_type(callback,'function') then
Server.add_thread_handler(event,self,callback)
return true
end
return false
end
Event.register(defines.events.on_tick,function(event)
local threads = Server._threads()
if #threads.tick > 0 then Server.run_tick_threads() end
if #threads.timeout > 0 then Server.check_timeouts() end
if #threads.queue > 0 then
local thread = threads.all[threads.queue[1]]
if thread and thread:valid() then thread:resolve() end
end
end)
return Server
--[[
Thread Example:
local thread = Server.new_thread{name='tree-decon',data={}}
-- user thread:on_event('tick') rather than thread:on_event(defines.events.on_tick) as it makes less lag
thread:on_event('tick',function(self)
local trees = self.data
local tree = table.remove(trees,1)
if tree.valid then tree.destroy() end
end)
thread:on_event('error',function(self,err)
-- cant see how this can cause an error
-- but this is where error handling goes
-- this is also called during on_resolve
-- but on_tick does not have on_success
end)
thread:on_event(defines.events.on_marked_for_deconstruction,function(self,event)
if event.entity.type == 'tree' then
table.insert(self.data,event.entity)
end
end)
thread:open()
]]