This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
eastar's profile picture

Help me understand coroutines!

Started by eastar, 29 June 2015 - 11:27 PM
eastar #1
Posted 30 June 2015 - 01:27 AM
Hello!

I need to use coroutnes but I don't understand them.
What I want: in my program on the screen a message has to move around while at the same time timers need to run and modem messages have to be recived in the background. I was quite glad when I found this tutorial becouse right in the 2nd paragraph it mentions something that I will need, so I knew I was at the right place.

After reading the tutorial a couple of times I was still baffled becouse to me it looked like coroutines (coroutine.resume) are simple function calls and coroutine.yield is the same as return. The only difference that I noticed, after ensuring myself that the program in the tutorial indeed worked, was that if os.pullEvent() is called within a coroutine then the event won't be removed from the event queue so the other os.pullEvent() in the other coroutine can use it.

If I'm right about this (which has slightly lower chance than me becoming a US president) I'd like to carry on with what I understand about the program and the tutorial and what I don't.

In the tutorial it says when resuming coroutine(s) they start running in the background (kind of) in a pseudo-parallel way. After the coroutine(s) are called the ancestor (the main function) is halted and lets the coroutines do their thing. Once all(?) coroutines have yielded (unfortunately in the example program they return at the same time :l ) the ancestor gets to run again. If I'm right about this, my question is: How a 2nd,3rd, nth coroutine is started if the ancestor is halted right after the 1st coroutine is called? Or the halt doesn't go for coroutine-related commands?

Next question about the program is the evt = os.pullEvent() line. Why is it there? If my understanding is correct so far, the program would never even get there since after the coroutines yielded the if coroutine.status(getC) == "dead" would be true so the main function's while loop would be broken out of.

The last thing that I didn't understand at all is the unpack(evt). I understand what it does (if we don't know how much element an array has it returns the elements in proper format to be arguments). But why is it there? They are not used in the functions, or are they?

I'm sure I'm wrong about a lot of things. So please help me, correct me!

Thanks in advance!
HPWebcamAble #2
Posted 30 June 2015 - 01:58 AM
If you just want to be able to run several coroutines together, look into the parallel API, which just does this for you.



You likely don't actually need to use coroutines at all, just a single 'os.pullEvent' and multiple if statements,
though its hard to explain in detail without seeing how you implement this with coroutines
Edited on 29 June 2015 - 11:58 PM
Bomb Bloke #3
Posted 30 June 2015 - 02:04 AM
I need to use coroutnes but I don't understand them.
What I want: in my program on the screen a message has to move around while at the same time timers need to run and modem messages have to be recived in the background. I was quite glad when I found this tutorial becouse right in the 2nd paragraph it mentions something that I will need, so I knew I was at the right place.

As HPWebcamAble says, you only think you need them because you don't understand them. The code examples for os.startTimer() demonstrate how to do that sort of thing without them.

On those few occasions where you might need to use coroutines, odds are the pre-built coroutine managers the parallel API offers will do the job for you.

But, since we're here…

After reading the tutorial a couple of times I was still baffled becouse to me it looked like coroutines (coroutine.resume) are simple function calls and coroutine.yield is the same as return.

coroutine.yield() and return are indeed very similar, but the main difference is that when a function returns, it can no longer be resumed.

The only difference that I noticed, after ensuring myself that the program in the tutorial indeed worked, was that if os.pullEvent() is called within a coroutine then the event won't be removed from the event queue so the other os.pullEvent() in the other coroutine can use it.

That depends on the coroutine manager you're using, but yes, most are built with that sort of mindset behind them.

In the tutorial it says when resuming coroutine(s) they start running in the background (kind of) in a pseudo-parallel way.

If you resume a coroutine, then the code which resumed it pauses until that coroutine yields or returns (about the same as if you call a function normally - the code doing the calling pauses until the function returns).

Once all(?) coroutines have yielded (unfortunately in the example program they return at the same time :l ) the ancestor gets to run again. If I'm right about this, my question is: How a 2nd,3rd, nth coroutine is started if the ancestor is halted right after the 1st coroutine is called? Or the halt doesn't go for coroutine-related commands?

Rather, the "ancestor" code gets control back as soon as any coroutine it resumes yields/returns. Because it also pauses as soon as it resumes any coroutine, what's happening is that it'll resume the first, wait to get control back, then resume the second, wait to get control back, and so on - generally, you'd have your "ancestor" code (your coroutine manager) do this until all the coroutines have returned or errored (switching their status to "dead"). Or maybe you might only wait for some, or perhaps even none, of them to die. It's up to you.

Note that you wouldn't want to pass all events to all coroutines - if one specifically asks for a "char" event (by calling os.pullEvent("char") for eg), then you'd only resume that one when you got that type of event from the front of the event queue.

For example, parallel.waitForAny() runs all the functions you pass it as coroutines, and keeps going until any of them finish. When the first function it starts yields, it attempts to resume the second, and so on, simply looping through them. parallel.waitForAll() runs them until they all finish. Again, these two pre-made coroutine managers (which also deal with the event filters for you) handle most use-cases.

Next question about the program is the evt = os.pullEvent() line. Why is it there? If my understanding is correct so far, the program would never even get there since after the coroutines yielded the if coroutine.status(getC) == "dead" would be true so the main function's while loop would be broken out of.

A coroutine that yields isn't "dead", it's "suspended". It's "dead" if it returns or errors.

os.pullEvent() calls os.pullEventRaw(), which calls coroutine.yield(). When a coroutine yields, the "ancestor" script carries on from where it left off. When the coroutine is resumed, it carries on from where it yielded.

The last thing that I didn't understand at all is the unpack(evt). I understand what it does (if we don't know how much element an array has it returns the elements in proper format to be arguments). But why is it there? They are not used in the functions, or are they?

It's often convenient to store a bunch of arguments in a table, and unpack() provides a simple method with which to extract them.

Consider the source for os.pullEvent():

function os.pullEvent( sFilter )
    local eventData = { os.pullEventRaw( sFilter ) }
    if eventData[1] == "terminate" then
        error( "Terminated", 0 )
    end
    return table.unpack( eventData )
end

It starts off by calling os.pullEventRaw(), which returns an event. What sort of event? Who knows! It could be a "char" event, in which case there'd be two parameters - "char" and whatever the character was. Or it could be a "mouse_click" event, in which case there'd be four parameters - "mouse_click", the button pressed, and the x/y co-ords of the press. They all get bundled into a table.

The function then proceeds to check if the event was a "terminate" event, and errors out of the script if so. Otherwise, it takes however many parameters the event contains, and returns them to the code that called os.pullEvent() in the first place - using unpack().

Hopefully this clarifies things somewhat, but again, the main thing to take away from all this is "you don't need coroutines for your stated objective".
Edited on 30 June 2015 - 12:11 AM
Geforce Fan #4
Posted 30 June 2015 - 02:04 AM
If you just want to be able to run several coroutines together, look into the parallel API, which just does this for you.



You likely don't actually need to use coroutines at all, just a single 'os.pullEvent' and multiple if statements,
though its hard to explain in detail without seeing how you implement this with coroutines
But to expand his lua knowledge, it'd be a good idea to use coroutines.
eastar #5
Posted 30 June 2015 - 03:14 PM
Thanks to all of you! :)/>
I'll try that parallel API. Hopefully the waitForAny is the one I need.

Thank you again, really! :)/>
Lyqyd #6
Posted 30 June 2015 - 05:46 PM
If you just want to be able to run several coroutines together, look into the parallel API, which just does this for you.



You likely don't actually need to use coroutines at all, just a single 'os.pullEvent' and multiple if statements,
though its hard to explain in detail without seeing how you implement this with coroutines
But to expand his lua knowledge, it'd be a good idea to use coroutines.

In Ask a Pro, we strive to always suggest the best tool for the job. We don't send users off to learn things that are unnecessary to solve their current problem. Please bear this in mind when posting "answers" in the future.
cyanisaac #7
Posted 01 July 2015 - 12:22 AM
If you just want to be able to run several coroutines together, look into the parallel API, which just does this for you.



You likely don't actually need to use coroutines at all, just a single 'os.pullEvent' and multiple if statements,
though its hard to explain in detail without seeing how you implement this with coroutines
But to expand his lua knowledge, it'd be a good idea to use coroutines.

In Ask a Pro, we strive to always suggest the best tool for the job. We don't send users off to learn things that are unnecessary to solve their current problem. Please bear this in mind when posting "answers" in the future.

Actually, that's the answer I'd likely want. If there is a simpler and arguably better way to tackle a problem given a situation, you'd want to do that. Parallel just manages coroutines for you, which is sometimes all you need. IMO Parallel might be the best tool for the job, as you said it, and is probably a good answer.
Lyqyd #8
Posted 01 July 2015 - 01:07 AM
I'm not really sure what point you're trying to make, since it seems you agreed with me? I was simply making the point that we always try to suggest the best tool for the job, not suggest something purely for the sake of making somebody learn something new, especially if the new thing isn't the best way to do things.
Bomb Bloke #9
Posted 01 July 2015 - 03:50 AM
Handling coroutines directly requires an entirely different level of knowledge/effort compared to having the parallel API do it for you. Which of the two methods Geforce Fan meant is ambiguous.