Coroutine (API)

From ComputerCraft Wiki
Revision as of 19:20, 23 April 2013 by Hawk777 (Talk | contribs) (Interaction with Other APIs: Explain how one might potentially do event dispatch)

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) Returns the status of the given coroutine.
function coroutine wrapper coroutine.wrap(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

Raw coroutines are not always safe to use with other API functions. The reason is that os.pullEventRaw (and therefore os.pullEvent, which calls it) are themselves built using coroutines: os.pullEventRaw actually invokes coroutine.yield, passing the event type filter up to what it expects is the parallel API, which would resume the coroutine once a matching event arrives. This is how the parallel API allows other code to run while one function is blocking waiting for an event. However, it means if you call os.pullEventRaw or any other function that uses it while managing coroutines yourself, you will see a coroutine unexpectedly yield, with the parent coroutine.resume being passed as a return value the filter string passed to os.pullEventRaw.

In this situation, it is essential that a developer carefully audit any APIs (s)he may wish to use to verify whether they could possibly block—many APIs, even those one might not expect, fall into this category. Some examples are:

Although it is not always practical to do so, in some situations, the parallel API may be preferable to using coroutines directly, as it handles event dispatching properly. However, it does not allow chaining coroutine invocations, nor does it allow adding coroutines to the parallel set after parallel execution has started. If coroutines are to be used directly, an application may either ensure it avoids any blocking API calls, or may arrange to handle event dispatch the same way that the parallel API does so that the blocking functions work properly. For example, one might arrange the convention that the top-level coroutine in the application will call os.pullEvent and deliver any returned events to sub-coroutines, the same way that the parallel API does, thus allowing those sub-coroutines to themselves call os.pullEvent, while insisting on a convention that more deeply nested coroutines never call os.pullEvent at all.