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

Coroutines. How do I use them?

Started by Cranium, 20 April 2013 - 09:52 AM
Cranium #1
Posted 20 April 2013 - 11:52 AM
Yes. I have looked on the documentation for Lua.org, I have tried the lua users wiki, and I have found nothing helpful on Google.
I want to know how to use coroutines. I don't really have a specific task to accomplish, but the fact that I do not know them bugs me, because it might just help me write better programs.

The only information I know is that coroutine.create actually creates the reference, coroutine.resume (re)starts the coroutine, and coroutine.status returns the status of the coroutine. Beyond that, I am unsure of how to implement them.

Can anyone give me a down and dirty tutorial on how to use them in a much easier to read format?
Kingdaro #2
Posted 20 April 2013 - 12:25 PM
I'm not a super mega coroutine sensei, but I'll try to explain them in use with CC to the best of my ability.

So you have some loops with two different waiting times. If you wanted to run them simultaneously, you make some coroutines out of them.

function hello()
  while true do
    print 'hello'
    sleep(math.random())
  end
end

function world()
  while true do
    print 'world'
    sleep(math.random())
  end
end

local routines = {
  coroutine.create(hello);
  coroutine.create(world);
}

With the coroutines defined, the rundown of a basic coroutine loop, is that you have a list of routines to yield and resume. In one cycle of the loop, you resume all routines in the list and let them do their jobs.

When you reresume a coroutine, you have to give it events from os.pullEvent() (or pullEventRaw), so that functions like sleep() that rely on a timer event actually work.
local events = {}
while true do
  for i=1, #routines do
    coroutine.resume(routines[i], unpack(events))
  end
  events = {os.pullEvent()}
end
ds84182 #3
Posted 20 April 2013 - 12:37 PM
You have this coroutine named co1, and a coroutine named co2.
co1 has the function:

function ()
while true do
sleep(0)
print("lol")
end
end
co2 has the function:

function ()
while true do
sleep(1)
print("lawl")
end
end

If you use this code:

local co1event = nil
local co2event = nil
local event = {}
while true do
event = {os.pullEvent()}
if coroutine.status(co1) ~= "dead" and (co1event == event[1] or co1event == nil) then
_, co1event = coroutine.resume(co1,unpack(event))
end
if coroutine.status(co2) ~= "dead" and (co2event == event[1] or co2event == nil) then
_, co2event = coroutine.resume(co2,unpack(event))
end
end
Both functions will run "simultaneously".
But a breakdown of the code is:
co1/2event are the filters for the next event. It is the same value as filter in os.pullEvent(filter)
coroutine.status will figure out if the coroutine is dead or not. If the coroutine is dead, it cannot be resumed.
coroutine.resume will make the coroutine resume. We pass the event to it so that os.pullEvent can actually return something!

Hope this helped!

Edit: Fixed some code.
Cranium #4
Posted 20 April 2013 - 12:45 PM
Alright, so I am just derping around, and seeing what I can do. I have this code:

function hello(s)
 while true do
  print 'hello'
  sleep(s)
 end
end
function world(s)
 while true do
  print 'world'
  sleep(s)
 end
end
local routines = {
 coroutine.create(hello);
 coroutine.create(world);
}
local events = {1}
while true do
 for i=1, #routines do
  coroutine.resume(routines[i], events[1])
  print(coroutine.status(routines[i]))
 end
 events = {os.pullEvent()}
end
and I am trying to pass the number I get from events[1] into the sleep. I obviously did something wrong.
Kingdaro #5
Posted 20 April 2013 - 12:51 PM
Yeah, when you pass in the events, you only pass the first parameter instead of unpacking it. This should fix it:

coroutine.resume(routines[i], unpack(events))

Passing parameters to coroutines on initialization is a very interesting concept though.
Cranium #6
Posted 20 April 2013 - 01:06 PM
So doing some debugging, and using the unpack() you suggested, the sleep and number never changes…

function co1(s)
 while true do
  print("Coroutine #1, sleeping "..tostring(s).." seconds")
  sleep(s)
 end
end
function co2(s)
 while true do
  print("Coroutine #2, sleeping "..tostring(s).." seconds")
  sleep(s)
 end
end
local routines = {
 coroutine.create(co1);
 coroutine.create(co2);
}
local events = {1}
while true do
 for i=1, #routines do
  coroutine.resume(routines[i], unpack(events))
  print(coroutine.status(routines[i]))
 end
 events = {os.pullEvent()}
end
Lyqyd #7
Posted 20 April 2013 - 01:31 PM
Of course not; you pass in 1 as the initial parameter and never change it.

Think of coroutines as resumable functions. When you resume them, they pick up where they left off. If you pass in parameters, they will be caught as the result of the yield function. If you pass parameters to the yield call, they will be caught as the result of the resume call that resumed the coroutine.
Cranium #8
Posted 20 April 2013 - 01:52 PM
Of course not; you pass in 1 as the initial parameter and never change it.

Think of coroutines as resumable functions. When you resume them, they pick up where they left off. If you pass in parameters, they will be caught as the result of the yield function. If you pass parameters to the yield call, they will be caught as the result of the resume call that resumed the coroutine.
er…actually, I do change what the table events contains. I did change it so instead of events[1], it's events[2], but it never changes.
Derped…. But how do I pass the arguments I get from events into the two functions?
Edited on 20 April 2013 - 11:54 AM
Lyqyd #9
Posted 20 April 2013 - 02:21 PM
You use unpack(), like Kingdaro had posted earlier. So, you get the events from Java in your coroutine controller:


event, p1, p2, p3 = os.pullEvent()

To make that easier, we usually do this:


event = {os.pullEvent()}

unpack() returns each of the numeric indices of the table, in order. That gets us back to the multiple returns. So, you pass it like this:


coroutine.resume(co, unpack(event))
Cranium #10
Posted 20 April 2013 - 04:04 PM
well, i get how unpack() works…. I just don't know how to use those values in the functions i created.
Symmetryc #11
Posted 20 April 2013 - 04:16 PM
I think I see your problem, you're using ordinary functions, not coroutine.create() functions, try this:

Coroutine = coroutine.create(function(number)
  write(number)
end)
number = math.random(1, 100)
coroutine.resume(Coroutine, number)

Edit: Oops, forgot that coroutines can't use sleep(), changed to write(), now my code should work.
Kingdaro #12
Posted 20 April 2013 - 04:27 PM
There is literally no difference between the two:


Coroutine = coroutine.create(function()
  print 'fubar'
end)

function fubar()
  print 'fubar'
end

Coroutine = coroutine.create(fubar)

Except the second method is much more organized, and allows access to the defined function later on if necessary.

Edit: Oops, forgot that coroutines can't use sleep(),
That's because you're not giving the coroutine events when resuming it.
Symmetryc #13
Posted 20 April 2013 - 04:30 PM
There is literally no difference between the two:


Coroutine = coroutine.create(function()
  print 'fubar'
end)

function fubar()
  print 'fubar'
end

Coroutine = coroutine.create(fubar)

Except the second method is much more organized, and allows access to the defined function later on if necessary.

Edit: Oops, forgot that coroutines can't use sleep(),
That's because you're not giving the coroutine events when resuming it.
My bad, I was using the sleep command with the second method and it didn't work so I started using the first and it still didn't work, so then I realized that you couldn't use sleep(), but I forgot to check the second method after to see if it works once I stop using sleep() :P/>.

Edit: How would I go about doing that? I always thought that you couldn't do it because whenever I tried it didn't work.
Cranium #14
Posted 21 April 2013 - 05:31 AM
Well, All I am trying to do, is be able to pass the variables from the coroutine.resume() into the functions. So say I want to use coroutine.resume(co1, 5), I would be able to use the number 5 as a variable to change the sleep within co1. apparently, I am not writing something right, because they never change.
isavegas #15
Posted 21 April 2013 - 05:45 AM
I feel so stupid now. o.o Learning quite a bit from this though. :D/>
Kingdaro #16
Posted 21 April 2013 - 06:20 AM
Well, All I am trying to do, is be able to pass the variables from the coroutine.resume() into the functions. So say I want to use coroutine.resume(co1, 5), I would be able to use the number 5 as a variable to change the sleep within co1. apparently, I am not writing something right, because they never change.

That's not really entirely possible without manually yielding the coroutines and checking events yourself. The coroutine.yield function actually returns the arguments given in coroutine.resume. We can use this to our advantage:

function hello(time)
   while true do
      print('hello ')
      print('Sleeping '..time..' seconds')

      local timer = os.startTimer(time)
      local newTime, ev, p1
      repeat
         newTime, ev, p1 = coroutine.yield()
      until ev == 'timer' and p1 == timer

      time = newTime
   end
end

function world(time)
   while true do
      print('world ')
      print('Sleeping '..time..' seconds')

      local timer = os.startTimer(time)
      local newTime, ev, p1
      repeat
         newTime, ev, p1 = coroutine.yield()
      until ev == 'timer' and p1 == timer

      time = newTime
   end
end

local routines = {
   coroutine.create(hello);
   coroutine.create(world);
}

local events = {0.1}
while true do
   for i=1, #routines do
      local ok, err = coroutine.resume(routines[i], unpack(events))
   end
   if events[2] == 'timer' or not events[2] then
      events[1] = events[1] + 0.1
   end
   events = {events[1], os.pullEvent()}
end

The "repeat until ev == 'timer' …" stuff is basically a replication of what the sleep function does. It starts a timer, then yields until the next event corresponds with its own timer. These lines:

   if events[2] == 'timer' or not events[2] then
      events[1] = events[1] + 0.1
   end

Make sure the event is actually a timer event before adding 0.1.
Symmetryc #17
Posted 21 April 2013 - 06:35 AM
Cranium, I think your program was working all along, it's just that you were using sleep(), when you should've used the work around that Kingdaro just posted.
Lyqyd #18
Posted 21 April 2013 - 07:58 AM
Cranium, I think your program was working all along, it's just that you were using sleep(), when you should've used the work around that Kingdaro just posted.

I appreciate that you are trying to help, but it would be preferable to not post if you don't know what you're talking about. Thanks. :)/>
Symmetryc #19
Posted 21 April 2013 - 08:05 AM
Cranium, I think your program was working all along, it's just that you were using sleep(), when you should've used the work around that Kingdaro just posted.

I appreciate that you are trying to help, but it would be preferable to not post if you don't know what you're talking about. Thanks. :)/>
OK, sorry :)/>.