Revise low level fiber semantics to play nicer with schedulers.

Now that I'm starting to write a real async scheduler on top of Wren's
basic fiber API, I have a better feel for what it needs. It turns out
run() is not it.

- Remove run() methods.
- Add transfer() which leaves the caller of the invoked fiber alone.
- Add suspend() to return control to the host application.
- Add Timer.schedule() to start a new independently scheduled fiber.
- Change Timer.sleep() so that it only transfers control to explicitly
  scheduled fibers, not any one.
This commit is contained in:
Bob Nystrom
2015-08-30 22:15:37 -07:00
parent 91af02ac81
commit 556af50f83
49 changed files with 469 additions and 350 deletions
+10 -2
View File
@@ -19,6 +19,14 @@ fiber is run. Does not immediately start running the fiber.
The currently executing fiber.
### Fiber.**suspend**()
Pauses the current fiber, and stops the interpreter. Control returns to the
host application.
To resume execution, the host application will need to invoke the interpreter
again. If there is still a reference to the suspended fiber, it can be resumed.
### Fiber.**yield**()
Pauses the current fiber and transfers control to the parent fiber. "Parent"
@@ -119,10 +127,10 @@ Invokes the fiber or resumes the fiber if it is in a paused state and sets
Whether the fiber's main function has completed and the fiber can no longer be
run. This returns `false` if the fiber is currently running or has yielded.
### **run**()
### **transfer**()
**TODO**
### **run**(value)
### **transfer**(value)
**TODO**
+8 -13
View File
@@ -158,17 +158,12 @@ Fibers have one more trick up their sleeves. When you execute a fiber using
lets you build up a chain of fiber calls that will eventually unwind back to
the main fiber when all of the called ones yield or finish.
This works fine for most uses, but sometimes you want something a little more
freeform. For example, you may be creating a [state
machine](http://en.wikipedia.org/wiki/Finite-state_machine) where each state is
a fiber. When you switch from one state to the next, you *don't* want to build
an implicit stack of fibers to return to. There is no "returning" in this case.
You just want to *transfer* to the next fiber and forget about the previous one
entirely. (This is analogous to [tail call
elimination](http://en.wikipedia.org/wiki/Tail_call) for regular function
calls.)
This is almost always what you want. But if you're doing something really low
level, like writing your own scheduler to manage a pool of fibers, you may not
want to treat them explicitly like a stack.
To enable this, fibers also have a `run()` method. This begins executing that
fiber, and "forgets" the previous one. If the running fiber yields or ends, it
will transfer control back to the last *called* one. (If there are no called
fibers, it will end execution.)
For rare cases like that, fibers also have a `transfer()` method. This switches
execution immediately to the transferred fiber. The previous one is suspended,
leaving it in whatever state it was in. You can resume the previous fiber by
transferring back to it, or even calling it. If you don't, execution stops when
the last transferred fiber returns.