The require function doesn’t support yields like this, but if you want to yield immediately upon requiring a module, you could put the logic into a function and call it right after requiring, like so:
It's a "ROBLOX thread" (from delay/spawn/event:connect()/a script's main thread/etc)
NO
yes
*Maybe, but treat the coroutine as being a “ROBLOX thread” from then on
The core problem is that ROBLOX internally uses (the C side equivalent of) coroutine.create/yield/resume to implement its “threads”. Therefore when you use coroutine functions on ROBLOX threads, you interfere with what the engine does to its coroutines. And this works sometimes, but I wouldn’t rely on it. You might break assumptions the engine makes about the states its threads are in (like in this case), and even if you get it working, engine changes could make it break on you.
wait works because it’s coded to play nice with ROBLOX threads, including in a ModuleScript. But you manually yielding the ModuleScript’s thread probably causes the engine to lose track of that thread, meaning scripts waiting on the ModuleScript’s initialisation aren’t woken.
If you must yield in this context, you need to use a ROBLOX yielding function (like event:wait() on a BindableEvent).
I think that change might actually be the problem here, kind of. Seems like calling coroutine.yield() now causes the task scheduler to relinquish control of the thread, and that leaves the ModuleScript in limbo.
(obviously, it still wouldn’t have done what OP wanted before the change).
So maybe this can be fixed. But things like this are why I recommend avoiding it.
local sig=Instance.new'BindableEvent'
delay(1,function()
sig:Fire()
end)
print'yielding'
sig.Event:Wait()
print'resumed'
return nil
So strangely this works… I thought that after this change [Live] Changes to coroutine.yield there became no difference between Roblox threads and coroutine threads
On the 10th of September I enabled a change which allowed you to call coroutine.yield in any sort of context, essentially making a user-owned thread and Roblox thread synonymous. coroutine.yield is not at fault.
The problem is related to continuations, an internal mechanism used to pass information between a thread and the Roblox scheduler. In this specific case, the continuation is used to resume all threads requiring the module. coroutine.resume does not support continuations as it exists outside of the Roblox scheduler. Therefore, the script which called require will never resume.
You can see this in action with the following example (note how coroutine.yield is not called).
local t=coroutine.running()
delay(1,function()
coroutine.resume(t)
end)
print'yielding'
wait(1e5)
print'resumed'
return nil
If the thread is resumed by the Roblox scheduler, continuations will run.
I have every intention of fixing this at some point in the future, however it’s not as trivial as it may seem. By rerouting coroutine.resume to use the same resume mechanism as other threads, there is a significant performance impact which I would like to avoid.
At present I am not working at Roblox due to my studies therefore I am unable to give you a timescale in which this will be fixed. Another member of staff could pick this up before I return. I can definitely ping somebody about this, however I would not expect a fix in the near future.
A short-term fix which makes coroutine.resume slower is unlikely to happen due to the significant impact it would have on performance.
For now, you might be better off just using a BindableEvent instead.
Not with regards to any metric worth caring about (instantiating a bindable event, connecting it, and firing it takes microseconds more than a coroutine).
I believe you mean the Roblox implementation of resume. @Kampfkarren is saying that performance is unlikely to be a concern for you, however that does not mean the Roblox engine should not be performant.
Then require would fail 100% of the time.
The slowdown you get from using a BindableEvent is comparable to that of the temporary fix for coroutine.resume. It seems reasonable, that until a performant fix is shipped, to use a BindableEvent as an alternative.
I will be using bindableevents because thats the only way for me to continue scripting in my framework(and working on Roblox at all for that matter)
But this means everywhere i use coroutine resume i have to use bindableevents
I very much appreciate the Roblox engine being performant but I just don’t understand why buggy performant code was chosen over stable code…and why nothing will be done about this either (until months when you can return to fix it :/)
At some point I am going to put together an alternative to the coroutine library which handles all of this for you.
Before the change to coroutine.yield this bug was less prevalent as there was no real reason to resume a thread with continuations. We definitely appreciate that stable code is a priority, however if we’re going to change it then we should just do it right the first time.
Thanks, I just have a couple of questions about it. Will it be a part of the Roblox engine or an auxiliary lua module? Also is this the only coroutine related bug it would solve or are there others I should be aware of? I’ve been having incomplete stack traces in my games too, would this be fixed? (I don’t have a repro atm but I suspect it’s to do with coroutine resume because I never did replicate it when trying to with coroutine wrap)
I guess the issue isn’t important enough/doesn’t affect enough people to necessitate a temporary fix
The library will be a Lua module, as a temporary alternative to the coroutine library.
Unfortunately I do not have a copy of the source code to hand to answer those questions. I believe there are some other related bugs this may fix, I am not certain if stack traces is one of them.
Sorry to revive this topic, but the new task library solves this problem. You should use task.spawn ( Takes a thread or function and resumes it immediately through the engine’s scheduler.) instead of coroutine.resume and it will work.
New version of your code (only changed the ModuleScript):
local t=coroutine.running()
delay(1,function()
task.spawn(t)
end)
print'yielding'
coroutine.yield()
print'resumed'
return nil