Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Use:
- On success:
- local success, return1, return2, ... = tpcall(func, arg1, arg2, ...)
- On error:
- local success, error, traceback = tpcall(func, arg1, arg2, ...)
- --]]
- --[[ runner.lua: belongs inside tpcall.lua
- return setmetatable({}, {
- __call = function(_, func, ...)
- return func(...)
- end
- })
- --]]
- local PREGENERATE_RUNNERS_COUNT = 10
- local runnerBase = script.runner
- local runnersFree = {}
- local runnersById = {}
- local runnersByCoroutine = {}
- local count = 0
- local function getFreeRunner(running)
- if runnersByCoroutine[running] then
- return runnersByCoroutine[running]
- else
- local freeRunner = next(runnersFree)
- if freeRunner then
- return freeRunner
- else
- count = count + 1
- local uniqueId = "tpcall:"..count
- runnerBase.Name = uniqueId
- local newRunner = require(runnerBase:Clone())
- newRunner.id = uniqueId
- runnersById[uniqueId] = newRunner
- return newRunner
- end
- end
- end
- for i = 1, PREGENERATE_RUNNERS_COUNT do
- count = count + 1
- local uniqueId = "tpcall:"..count
- runnerBase.Name = uniqueId
- local newRunner = require(runnerBase:Clone())
- newRunner.id = uniqueId
- runnersById[uniqueId] = newRunner
- runnersFree[newRunner] = true
- end
- local lastErrorId, lastError, lastTrace
- game:GetService("ScriptContext").Error:Connect(function(err, trace, scr)
- local tpcallId = trace:match("(tpcall:%d+)")
- if tpcallId then
- -- for some reason, the error message includes the script and
- -- line number. The script can have any name, and the error is
- -- arbitrary, so we can't use pattern matching on the error.
- -- We *can* use it on the trace though, which includes the script
- -- name and error. Using this, we can find length counts and get
- -- a substring of the actual error!
- -- This approach isn't perfect. It relies on the ", line %d+ -" format.
- -- If for some reason your script has a name like that then it will
- -- produce improper output
- local scriptName, errorLine = trace:match("^(.-), line (%d+) %- [^\n]*\n")
- if scriptName then
- if err:sub(1, #scriptName) == scriptName then
- lastError = err:sub(#scriptName + #errorLine + 4)
- else
- lastError = err
- end
- else
- lastError = err:match("^"..tpcallId..":%d+: (.*)$") or err
- end
- lastErrorId = tpcallId
- lastTrace = trace:match("^(.*)\n[^\n]+\n[^\n]+\n$") or ""
- runnersById[tpcallId].event:Fire()
- end
- end)
- return function(func, ...)
- local runner = getFreeRunner(coroutine.running())
- local initialCoroutine, initialEvent = runner.coroutine, runner.event
- -- We have been given the runner *for our coroutine*.
- -- This means it's "stacked" on top of another coroutine and event.
- -- (and we are *guaranteed* to finish first, since we are
- -- stacked on top of the coroutine that called this tpcall)
- -- In fact, initialCoroutine and coroutine.running() should be
- -- the same! We only switch to a "new" coroutine when we move
- -- into the BindableEvent, but it's guaranteed that the
- -- BindableEvent coroutine finishes before we continue. We use
- -- BindableEvents to yield until that new coroutine is finished.
- -- The one exception: when this call is at the top of the tpcall
- -- "stack". In that scenario, initialCoroutine and initialEvent
- -- are nil. This is how tpcall knows when a runner is free again:
- -- it's free when the initialCoroutine and initialEvent are nil.
- -- If there is an error, lastErrorId = runnerId
- -- If there is no error, results ~= nil
- local results
- local args = {...}
- local event = Instance.new("BindableEvent")
- runner.event = event
- local running
- local conn
- conn = event.Event:Connect(function()
- conn:disconnect()
- running = coroutine.running()
- runner.coroutine = running
- runnersByCoroutine[running] = runner
- results = {runner(func, unpack(args))}
- event:Fire()
- end)
- runnersFree[runner] = nil
- event:Fire()
- local runnerId = runner.id
- if not results and lastErrorId ~= runnerId then
- event.Event:Wait()
- end
- runnersByCoroutine[running] = nil
- runner.coroutine, runner.event = initialCoroutine, initialEvent
- if not initialCoroutine then
- runnersFree[runner] = true
- end
- if lastErrorId == runnerId then
- lastErrorId = nil
- return false, lastError, lastTrace
- else
- return true, unpack(results)
- end
- end
Add Comment
Please, Sign In to add comment