When a coroutine is waiting it can be resumed immediately and later causes an error when roblox attempts to resume the coroutine after waiting.
This error is easily reproducible in any script.
local co = coroutine.create(function() print("Hello, world!") wait(3) print("Goodbye.") end)
coroutine.resume(co) -- Starts the function, prints "Hello, world!", then waits.
coroutine.resume(co) -- Immediately resumes the coroutine and prints "Goodbye." after wait call.
-- Afterward roblox attempts to resume the coroutine and fails.
I’m aware that its how the scheduler works, however it is still a bug, the scheduler should not be throwing errors during normal operation and a yielding function that hasn’t returned should not allow the coroutine to resume.
You’re explicitly resuming a thread which is being handled by the internal scheduler. The scheduler does not expect you to do this so it seems reasonable that it should throw an error.
Imagine having written some code which accidentally resumes a thread twice. Were this error not there it could be a nightmare to debug and realize the mistake that you’ve made.
If you want this behavior, you can implement it yourself.
function wait(duration)
local thread = coroutine.running()
delay(duration, function (...)
if coroutine.status(thread) == "suspended" then
coroutine.resume(thread, ...)
end
end)
return coroutine.yield()
end
Correct, however, the wait has not been fulfilled it should not return until it is finished waiting. This is not an issue specific to wait(); any yielding function should fulfill or error before returning.
As an example, a yielding function that reads a value from disk when explicitly resumed should not immediately return and then later error because it failed to read before being resumed. Likewise wait should also fulfill its promised task. When a read function that was yielding doesn’t have its value to return yet it should either yield again or throw an error.
You can also implement this behavior yourself by:
Yielding until the wait time has been achieved…
function wait(duration)
local thread = coroutine.running()
local fulfilled = false
local yield
delay(duration, function (...)
fulfilled = true
if coroutine.status(thread) == "suspended" then
coroutine.resume(thread, ...)
end
end)
while not fulfilled do
yield = {coroutine.yield()}
end
return unpack(yield)
end
or throwing an error/warning immediately upon resume rather than later…
function wait(duration)
local thread = coroutine.running()
local fulfilled = false
local yield
delay(duration, function (...)
fulfilled = true
if coroutine.status(thread) == "suspended" then
coroutine.resume(thread, ...)
end
end)
yield = {coroutine.yield()}
if not fulfilled then
warn("coroutine explicitly resumed before waiting.")
end
return unpack(yield)
end
The issue is not one of whether this can be done, its that this should not be the default interaction. Its error-prone and unexpected. When it throws an error the error is unhelpful. It means that wait() does not fulfill its promise of resuming after an elapsed time and only errors later rather than doing so immediately.
That is why I call this a bug. Failing to fulfill a task and failing to throw a proper error or warning is an issue that needs to be fixed and can be fixed quite easily.
This post has been edited. The post previously included code that would not capture all return values of yield. This has since been corrected.