Coroutine (API)

From ComputerCraft Wiki
Revision as of 00:17, 6 August 2013 by Lyqyd (Talk | contribs) (Removal of NeedsWork following edits made to bottom section of page.)

Jump to: navigation, search
Bug

From ComputerCraft 1.3 this API suffers from the problem of potentially spawning Java threads that live forever therefore, use of coroutines should be minimized. This is fixed in ComputerCraft 1.32+, though.

This is for the Coroutine API. For the Coroutine Object, visit Coroutine (type).

Coroutine is a default Lua 5.1 API defined here. Please list any non-working functions on this page.

Grid disk.png   Coroutine (API)

ReturnMethod NameDescription
coroutine coro coroutine.create(function f) Creates a new coroutine.
boolean success, … coroutine.resume(coroutine coro, [var1], [var2], ...) Starts or resumes an existing coroutine.
coroutine coro coroutine.running() Returns the currently executing coroutine.
string status coroutine.status(coroutine coroutine) Returns the status of the given coroutine.
function coroutine wrapper coroutine.wrap(function function) Creates a new coroutine and wraps it in a function.
[var3], [var4], … coroutine.yield([var1], [var2], …) Pauses the currently executing coroutine and passes control to its caller.

Coroutine States and Ancestry

Every coroutine is in one of four states:

  • A suspended coroutine is one that has not yet been started or that has yielded. A suspended coroutine can become running if it is passed to coroutine.resume.
  • A running coroutine is one that is currently executing code. Only one coroutine can be running at a time on a particular computer. A running coroutine can become suspended if it coroutine.yield, normal if it calls coroutine.resume on another coroutine, or dead by if it returns from its body function.
  • A normal coroutine is one that, while running, resumed another coroutine (the child coroutine). A normal coroutine can become running if its child calls coroutine.yield or returns from its function body.
  • A dead coroutine is one that, while running, returned from its function body. A dead coroutine can never change state.

While suspended and dead coroutines simply sit loose with no connections to other coroutines, normal and running coroutines have an ancestry relationship between them. Exactly one coroutine is running at any given time, and the running coroutine has no children. Every running or normal coroutine except the top-level BIOS coroutine has a parent, and this relationship forms a chain with one end (the furthest ancestor) being the top-level BIOS coroutine and the other end (the furthest descendant) being the running coroutine. Remember that only the running coroutine can actually execute code, so any functions invoked must be invoked by that coroutine. Then, calling coroutine.resume makes the chain longer by taking a previously suspended coroutine, hanging it underneath the current coroutine as a new child, and making the current coroutine normal and the new child running. Meanwhile, calling coroutine.yield makes the chain shorter by turning the currently executing coroutine into a suspended coroutine, detaching it from the chain, and letting its former parent become running.

It is important not to confuse this coroutine chain with the function call stack. Each individual coroutine contains a complete function call stack of its own, rooted at the coroutine’s body function, whether the coroutine is running, normal, or suspended; the chain described in the previous paragraph refers to how entire coroutines are connected together.

Examples

As the coroutine API is very tightly coupled and many of its function must be used to get any interesting results, examples are shown here instead of on the individual function pages.

Using the Basic Coroutine Functions

This example demonstrates using the basic coroutine functions to pass control and data between coroutines. This example prints the numbers 1 through 17 in order, showing how control flow moves through the main program coroutine and the two created coroutines, while using assertions to show how data flows through the program:

-- These variables will hold the coroutine objects.
local c1 = nil
local c2 = nil

-- This function will be the body of the first coroutine.
local function f1(x)
  -- The main code, below, starts c1 first, passing 1.
  -- Thus c1 should be running, c2 should be suspended (not started yet), and x should receive the value from main, 1.
  -- STEP 2: Check these assumptions.
  print(2)
  assert(coroutine.status(c1) == "running")
  assert(coroutine.status(c2) == "suspended")
  assert(x == 1)
  -- STEP 3: Run c2 until it yields, start it off with the value 2.
  print(3)
  success, value = coroutine.resume(c2, 2)
  assert(success)
  -- In step 6, c2 yields and passes back the value 3.
  -- Then c2, having yielded, should be suspended, and c1 should be running again.
  -- STEP 7: Check all that.
  print(7)
  assert(coroutine.status(c1) == "running")
  assert(coroutine.status(c2) == "suspended")
  assert(value == 3)
  -- STEP 8: Run c2 again, passing it the value 4.
  -- Since c2 has already run before, this time the 4 will come out of the yield() instead of appear as a function parameter.
  print(8)
  success, value = coroutine.resume(c2, 4)
  -- In step 10, c2 yields and passes back the value 5.
  -- Then c2, having yielded, should be suspended, and c1 should be running again.
  -- STEP 11: Check all that.
  print(11)
  assert(coroutine.status(c1) == "running")
  assert(coroutine.status(c2) == "suspended")
  assert(value == 5)
  -- STEP 12: Yield, turning c1 back into a suspended coroutine and continuing execution of the main program, passing back value 6.
  print(12)
  value = coroutine.yield(6)
end

-- This function will be the body of the second coroutine.
local function f2(x)
  -- The first time c2 is run, it is run from f1, which passes 2.
  -- Thus c1 should be normal (as we are nested inside it), c2 should be running, and x should receive the value from f1, 2.
  -- STEP 4: Check these assumptions.
  print(4)
  assert(coroutine.status(c1) == "normal")
  assert(coroutine.status(c2) == "running")
  assert(x == 2)
  -- Because c1 is not suspended, we should not be able to resume it.
  -- STEP 5: Check this is correct.
  print(5)
  success, value = coroutine.resume(c1)
  assert(not success)
  assert(value == "cannot resume normal coroutine")
  -- STEP 6: Yield, turning c2 back into a suspended coroutine and continuing execution of c1, passing back value 3.
  print(6)
  value = coroutine.yield(3)
  -- In step 8, c1 resumed c2 again and passed the value 4.
  -- c2 should now be running again, and c1 normal again.
  -- STEP 9: Check all that.
  print(9)
  assert(coroutine.status(c1) == "normal")
  assert(coroutine.status(c2) == "running")
  assert(value == 4)
  -- STEP 10: Yield, turning c2 back into a suspended coroutine and continuing execution of c1, passing back value 5.
  print(10)
  value = coroutine.yield(5)
  -- In step 14, the main program resumed c2 directly and passed the value 7, bypassing c1.
  -- So c2 should be running and c1 should be suspended.
  -- STEP 15: Check all that.
  print(15)
  assert(coroutine.status(c1) == "suspended")
  assert(coroutine.status(c2) == "running")
  assert(value == 7)
  -- STEP 16: Now die, returning value 8 to the main program.
  print(16)
  return 8
end

-- Construct the two coroutines.
c1 = coroutine.create(f1)
c2 = coroutine.create(f2)

-- Newly constructed coroutines are always suspended.
assert(coroutine.status(c1) == "suspended")
assert(coroutine.status(c2) == "suspended")

-- STEP 1: Run c1 until it yields, starting it off with the value 1.
print(1)
success, value = coroutine.resume(c1, 1)
assert(success)

-- In step 12, c1 should have yielded and returned the value 6.
-- So now c1 and c2 should both be suspended.
-- STEP 13: Check all that.
print(13)
assert(coroutine.status(c1) == "suspended")
assert(coroutine.status(c2) == "suspended")
assert(value == 6)

-- STEP 14: Run c2 directly, NOT nested inside c1 this time, passing it the value 7.
print(14)
success, value = coroutine.resume(c2, 7)
assert(success)

-- c2 should have exited normally, returning the value 8.
-- Therefore c1 should still be suspended, but c2 should be dead.
-- STEP 17: Check all that.
print(17)
assert(coroutine.status(c1) == "suspended")
assert(coroutine.status(c2) == "dead")
assert(value == 8)

Using Coroutine Wrapper Functions

This second example is virtually identical, but shows using coroutine.wrap and wrapper functions instead of the basic coroutine functions (checks of coroutine.status have been removed as the raw coroutine objects are not available):

-- These variables will hold the coroutine wrapper functions.
local cw1 = nil
local cw2 = nil

-- This function will be the body of the first coroutine.
local function f1(x)
  -- The main code, below, starts c1 first, passing 1.
  -- Thus c1 should be running, c2 should be suspended (not started yet), and x should receive the value from main, 1.
  -- STEP 2: Check these assumptions.
  print(2)
  assert(x == 1)
  -- STEP 3: Run c2 until it yields, start it off with the value 2.
  print(3)
  value = cw2(2)
  -- In step 6, c2 yields and passes back the value 3.
  -- Then c2, having yielded, should be suspended, and c1 should be running again.
  -- STEP 7: Check all that.
  print(7)
  assert(value == 3)
  -- STEP 8: Run c2 again, passing it the value 4.
  -- Since c2 has already run before, this time the 4 will come out of the yield() instead of appear as a function parameter.
  print(8)
  value = cw2(4)
  -- In step 10, c2 yields and passes back the value 5.
  -- Then c2, having yielded, should be suspended, and c1 should be running again.
  -- STEP 11: Check all that.
  print(11)
  assert(value == 5)
  -- STEP 12: Yield, turning c1 back into a suspended coroutine and continuing execution of the main program, passing back value 6.
  print(12)
  value = coroutine.yield(6)
end

-- This function will be the body of the second coroutine.
local function f2(x)
  -- The first time c2 is run, it is run from f1, which passes 2.
  -- Thus c1 should be normal (as we are nested inside it), c2 should be running, and x should receive the value from f1, 2.
  -- STEP 4: Check these assumptions.
  print(4)
  assert(x == 2)
  -- Because c1 is not suspended, we should not be able to resume it.
  -- STEP 5: Check this is correct (we must use pcall because an error will be raised).
  print(5)
  success, value = pcall(cw1)
  assert(not success)
  -- STEP 6: Yield, turning c2 back into a suspended coroutine and continuing execution of c1, passing back value 3.
  print(6)
  value = coroutine.yield(3)
  -- In step 8, c1 resumed c2 again and passed the value 4.
  -- c2 should now be running again, and c1 normal again.
  -- STEP 9: Check all that.
  print(9)
  assert(value == 4)
  -- STEP 10: Yield, turning c2 back into a suspended coroutine and continuing execution of c1, passing back value 5.
  print(10)
  value = coroutine.yield(5)
  -- In step 14, the main program resumed c2 directly and passed the value 7, bypassing c1.
  -- So c2 should be running and c1 should be suspended.
  -- STEP 15: Check all that.
  print(15)
  assert(value == 7)
  -- STEP 16: Now die, returning value 8 to the main program.
  print(16)
  return 8
end

-- Construct the two coroutine wrappers.
cw1 = coroutine.wrap(f1)
cw2 = coroutine.wrap(f2)

-- STEP 1: Run cw1 until it yields, starting it off with the value 1.
print(1)
value = cw1(1)

-- In step 12, c1 should have yielded and returned the value 6.
-- So now c1 and c2 should both be suspended.
-- STEP 13: Check all that.
print(13)
assert(value == 6)

-- STEP 14: Run c2 directly, NOT nested inside c1 this time, passing it the value 7.
print(14)
value = cw2(7)

-- c2 should have exited normally, returning the value 8.
-- Therefore c1 should still be suspended, but c2 should be dead.
-- STEP 17: Check all that.
print(17)
assert(value == 8)

Interaction with Other APIs

The os.pullEventRaw (and therefore os.pullEvent) functions are built on top of coroutines: os.pullEventRaw actually just calls coroutine.yield, passing the event type filter up to the parent coroutine. The parent coroutine is expected to wait until an event of the required type arrives, then use coroutine.resume to pass the event to the suspended coroutine (where it will be returned from os.pullEventRaw). If you use the parallel API, this is exactly what parallel.waitForAny and parallel.waitForAll do: they pull events from the system event queue (or even from a higher invocation of parallel.waitForAny or parallel.waitForAll!) and deliver them to all nested coroutines. Therefore, the easiest way to use coroutines is the parallel API, if it works for your situation. However, in the event that you are not able to use the parallel API (e.g. if you need to add and remove coroutines from the executing set dynamically), you can build your own dispatcher that delivers events in the same way as the parallel API—looking at the source code to the parallel API may be useful here.

If you choose to use coroutines in a different way, then os.pullEventRaw and os.pullEvent will not work. This in turn will break any other API function which is written on top of os.pullEvent. As a guideline, nearly any API function that can possibly take time to complete will fall into this category; some examples are:

Most of these can be worked around by using non-blocking equivalents that return immediately and then queue an event on completion (such as using http.request) or that deliver an event unconditionally (such as char for keyboard input), then handling events in a way that is suitable for the application. However, if possible, it will often be easier to implement event dispatching instead, remembering that os.queueEvent can be used for inter-coroutine communication within an application. Another alternative, if you must use a custom protocol between your coroutines and dispatcher, might be to replace os.pullEventRaw with a new function that uses a mechanism appropriate to the application.