Beginners Guide to Coroutines
Beginners Guide to Coroutines
Coroutines are some of the most interesting and useful parts of Lua. However they are still some of the most misunderstood concepts of Lua scripting. When you make a new coroutine it is like creating a new script in your place. On Roblox one of the biggest differences in creating a new script and a new coroutine is that a new script’s context does not have to be created. In some cases this can save execution time.
These features of Lua will allow multiple threads to run alongside each other without having to create separate script instances or have to wait for the current running code block to finish.
How to Create a Coroutine
Creating a coroutine is fairly simple, all you need to start is a function and
coroutine.create(). The one parameter of
coroutine.create() will be the function you wish to run, so like this.
local newThread = coroutine.create(function() print("hola") end)
Now you’ve created your first coroutine and started a new thread! Yet nothing has happened, well this is because you haven’t run it. To run your coroutine you will need to use
coroutine.resume() and the parameter will be the thread you created.
local newThread = coroutine.create(function() print("hola") end) coroutine.resume(newThread)
Let’s say you wanted to call parameters in your coroutine’s function, well then what you will need to do that with is
coroutine.resume() like so:
local newThread = coroutine.create(function(a, b, c) print(a*b + c) end) coroutine.resume(newThread, 3, 5, 6)
coroutine.wrap() can be used as a replacement for
coroutine.create(). You use
coroutine.wrap() on the function like
coroutine.create() but you will use the variable you assign it to like a function. Think of
coroutine.wrap() as a function with a coroutine shoved inside it.
local newThread = coroutine.wrap(function() print("Hola") end) newThread()
If you want to add in parameters, just do it like you would any other function.
local newThread = coroutine.wrap(function(a, b, c) print(a * b + c) end) newThread(8,2,1)
What are Coroutines Really Useful For?
You have learned a couple of the coroutine functions, but what can you use them for? Well one of the most useful things is making loops and functions run at the same time.
local h = Instance.new("Hint", workspace); local m = Instance.new("Message", workspace); local changeHint = coroutine.wrap(function() for i = 60, 0, -1 do wait(0.5) h.Text = i end end) local changeMessage = coroutine.wrap(function() for i = 60, 0, -1 do wait(1) m.Text = i end end) changeHint() changeMessage() code
As you can now see, the message and the hint both change their text at the same time, but at different speeds.
coroutine.resume() will return error information, like
coroutine.resume() will return a boolean, saying if it succeeded, and if it didn’t, then a string which is the error message.
local success, errorMessage = coroutine.resume(coroutine.create(function() ppprint("HI") end)) if not success then -- check if there is an error print("There was an error:", errorMessage) end
There was an error: Script:2: attempt to call global ppprint (a nil value)
More, More Coroutines!
coroutine.yield() puts your coroutine in suspended mode, where it just stops and waits until the coroutine is called again.
coroutine.yield() cannot include a metamethod, C function or an iterator (if you don’t know what those are then you’re most likely not using them). Anything extra put inside a
coroutine.yield() will go straight to
new_thread=coroutine.wrap(function(param) print(param) local resumedWith = coroutine.yield() print("Resumed with: " .. resumedWith) end) new_thread("Hola mis amigos!") new_thread("This was retrieved with yield()")
Hola mis amigos! Resumed with: This was retrieved with yield()
coroutine.status() will tell you if your coroutine is either dead, suspended, running or normal. It will return this as a string to you. What do those mean?
- Running means that the coroutine is currently working and using its code.
- Dead means that the coroutine has stopped running and is done for now.
- Suspended means
coroutine.yield()ran and it’s waiting to start up again.
Now how do we use it? We simply do:
function core() print("hola") end new_thread=coroutine.create(core) print(coroutine.status(new_thread)) coroutine.resume(new_thread) print(coroutine.status(new_thread))
suspended hola dead
coroutine.running() will return the current thread that is running. Example:
new_thread=coroutine.create(function() print("hola") print(coroutine.running()) end) print(coroutine.running()) coroutine.resume(new_thread)
thread: XXXXXXXX hola thread: YYYYYYYY
YYYYYYYY will not only be different for you, but will also be different from each other. This is because the currently running coroutine changed between calls to
Interaction With the Thread Scheduler
coroutine.yield(...), in a function in the thread scheduler (or the top level) behaves like
wait()with no arguments.
wait()in a function used to build a coroutine queues the coroutine in the thread scheduler. However, the coroutine can be resumed immediately, leading to unexpected results:
local coro = coroutine.wrap(function(arg) print("C start", arg) print("C yield", coroutine.yield("Y1")) print("C wait", wait(2)) print("C yield", coroutine.yield("Y2")) print("C end") end) print("M start") -- R1 ends up in arg print("M resume", coro("R1")) -- R2 is returned from coroutine.yield print("M resume", coro("R2")) -- R3 is returned from wait! print("M resume", coro("R3")) print("M end")
M start C start R1 M resume Y1 C yield R2 M resume C wait R3 M resume Y2 M end C yield 2.0028280779259 3.0923760618118 C end