-- ====================================================================================== -- System: Simple Coroutine Scheduler for MTA -- Autor: marlondev -- Descrição: -- Provê funções para criar “threads” (corrotinas) que podem: -- * Esperar X milissegundos (Wait) -- * Esperar X ticks/frame (WaitTick) -- * Ser retomadas manualmente (ResumeThread) -- * Serem consultadas se estão aguardando (IsWaiting) -- * Executar um callback no próximo frame (RunNextFrame) -- ====================================================================================== local CLIENT = isElement(localPlayer); local setTimer, isTimer, killTimer = setTimer, isTimer, killTimer; local coroutine = coroutine; local assert = assert or (function() end) local outputDebugString = outputDebugString or (function() end); local Threads = setmetatable({}, { __mode = 'k' }) local Threads_Tick = setmetatable({}, { __mode = 'k' }) local _coroutine_resume = coroutine.resume function coroutine.resume(...) local state,result = _coroutine_resume(...) if not state then outputDebugString( tostring(result), 1 ) -- Output error message end return state,result end function CreateThread(func, ...) assert(type(func) == 'function', 'Expected a function for the thread.') local co = coroutine.create(func) assert(co, 'Failed to create coroutine.') local ErrorCallback = select(1, ...) if type(ErrorCallback) == 'function' then local args = {...} table.remove(args, 1) local status, err = coroutine.resume(co, unpack(args)) if not status then ErrorCallback(status, err) end return co, status, err end return co, coroutine.resume(co, ...) end local THREAD_TICK_RUNNING_RENDER = false; local function onRenderWaitTick() if next(Threads_Tick) == nil then removeEventHandler('onClientRender', root, onRenderWaitTick) THREAD_TICK_RUNNING_RENDER = false; return end local NOW = getTickCount(); local ThreadsToTick = Threads_Tick; local next = next; local pcall = pcall; for co, v in next, ThreadsToTick, nil do if v.Expires and v.Expires < NOW then if type (v.Callback) == 'function' then pcall(v.Callback, co); else ThreadsToTick[co] = nil; end end end end function Wait(ms, errorCallback ,...) local co = coroutine.running() assert(co, 'Wait can only be used in a coroutine!') assert(type(ms) == 'number', 'Expected a number for ms.') if Threads_Tick[co] then Threads_Tick[co] = nil end if isTimer(Threads[co]) then killTimer(Threads[co]) Threads[co] = nil end Threads[co] = setTimer(function(...) local status, err = coroutine.resume(co, ...); if Threads[co] then Threads[co] = nil end if type(errorCallback) == 'function' then errorCallback(co) end if not status then outputDebugString(tostring(err), 4, 255, 0, 0) end end, ms, 1, ...) if not isTimer(Threads[co]) then outputDebugString('['..resource.name..']: [Thread] ('..os.date('%Y-%m-%d %H:%M:%S')..') - Failed to create timer for coroutine.', 4, 255, 0, 0) else coroutine.yield(); end return status, err end function WaitTick(TickCount, errorCallback) local co = coroutine.running(); assert(CLIENT, 'WaitTick can only be used in a client-side coroutine!') assert(co, 'WaitTick can only be used in a coroutine!') assert(type(TickCount) == 'number', 'Expected a number for TickCount.') if TickCount <= 0 then TickCount = 1 end if Threads[co] then killTimer(Threads[co]) Threads[co] = nil end if type(Threads_Tick[co]) == 'table' then Threads_Tick[co] = nil end local callback = function(co) local info = Threads_Tick[co]; if not info then return end Threads_Tick[co] = nil local status, err = coroutine.resume(co); if not status then if type(info.ErrorCallback) == 'function' then info.ErrorCallback(co) end end end Threads_Tick[co] = { Expires = getTickCount() + TickCount, Callback = callback, ErrorCallback = errorCallback } if not THREAD_TICK_RUNNING_RENDER then addEventHandler('onClientRender', root, onRenderWaitTick, true, 'high+999') THREAD_TICK_RUNNING_RENDER = true; end coroutine.yield() end function ResumeThread(co, ...) assert(co, 'Expected a coroutine.') assert(Threads[co], 'Thread is not waiting.') if Threads_Tick[co] then Threads_Tick[co] = nil end if isTimer(Threads[co]) then killTimer(Threads[co]) Threads[co] = nil end local status, err = coroutine.resume(co, ...) if not status then outputDebugString('['..resource.name..']: [Thread] ('..os.date('%Y-%m-%d %H:%M:%S')..') - Error in thread: ' .. tostring(err), 4, 255, 0, 0) end return status, err end function IsWaiting(co) return (Threads[co] and isTimer(Threads[co])) or (Threads_Tick[co] ~= nil) end function getThread() local Thread = coroutine.running() return type(Thread) == 'thread' and Thread or nil end function isThreadValid(co) if not co then co = getThread() end return type(co) == 'thread' and true or false end function getThreadStatus(co) if not co then co = getThread() end if not co then return false, 'Thread não encontrado' end return coroutine.status(co), IsWaiting(co) end function RunNextFrame(callback) if type(callback) ~= 'function' then return false, 'Callback inválido' end if CLIENT then local wrapper = false local callbackk = callback; wrapper = function () callbackk() removeEventHandler("onClientRender", root, wrapper) end addEventHandler('onClientRender', root, wrapper) return true else setTimer(function() callback() end, 1, 1) return true; end end