-
Notifications
You must be signed in to change notification settings - Fork 12
/
coroutine.go
129 lines (110 loc) · 4.48 KB
/
coroutine.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package coroutine
import (
"errors"
)
// Coroutine instances expose APIs allowing the program to drive the execution
// of coroutines.
//
// The type parameter R represents the type of values that the program can
// receive from the coroutine (what it yields), and the type parameter S is
// what the program can send back to a coroutine yield point.
type Coroutine[R, S any] struct{ ctx *Context[R, S] }
// Recv returns the last value that the coroutine has yielded. The method must
// be called only after a call to Next has returned true, or the return value is
// undefined. Calling the method multiple times after a call to Next returns the
// same value each time.
func (c Coroutine[R, S]) Recv() R { return c.ctx.recv }
// Send sets the value that will be seen by the coroutine after it resumes from
// a yield point. Calling the method multiple times before a call to Next does
// not result in sending multiple values, only the last value sent will be seen
// by the coroutine.
func (c Coroutine[R, S]) Send(v S) { c.ctx.send = v }
// Result is the return value of the coroutine, if it was constructed with
// NewWithReturn. Result should only be called once Next returns false,
// indicating that the coroutine finished executing.
func (c Coroutine[R, S]) Result() R { return c.ctx.result }
// Stop interrupts the coroutine. On the next call to Next, the coroutine will
// not return from its yield point; instead, it unwinds its call stack, calling
// each defer statement in the inverse order that they were declared.
//
// Stop is idempotent, calling it multiple times or after completion of the
// coroutine has no effect.
//
// This method is just an interrupt mechanism, the program does not have to call
// it to release the coroutine resources after completion.
func (c Coroutine[R, S]) Stop() { c.ctx.stop = true }
// Done returns true if the coroutine completed, either because it was stopped
// or because its function returned.
func (c Coroutine[R, S]) Done() bool { return c.ctx.done }
// Context returns the coroutine's associated Context.
func (c Coroutine[R, S]) Context() *Context[R, S] { return c.ctx }
// Context is passed to a coroutine and flows through all
// functions that Yield (or could yield).
type Context[R, S any] struct {
// Value passed to Yield when a coroutine yields control back to its caller,
// and value returned to the coroutine when the caller resumes it.
//
// Keep as first fields so they don't use any space if they are the empty
// struct.
recv R
send S
// Value returned from the coroutine.
result R
// Booleans managing the state of the coroutine.
done bool
stop bool
resume bool //nolint
context[R]
}
// Run executes a coroutine to completion, calling f for each value that the
// coroutine yields, and sending back each value that f returns.
//
// If c was constructed with NewWithReturn, the return value of the coroutine is
// returned by Run. Otherwise, the zero-value is returned and can be ignored by
// the caller.
func Run[R, S any](c Coroutine[R, S], f func(R) S) R {
// The coroutine is run to completion, but f might panic in which case we
// don't want to leave it in an uncompleted state and interrupt it instead.
defer func() {
if !c.Done() {
c.Stop()
c.Next()
}
}()
for c.Next() {
r := c.Recv()
s := f(r)
c.Send(s)
}
return c.Result()
}
// Yield sends v to the generator and pauses the execution of the coroutine
// until the Next method is called on the associated generator.
//
// The function panics when called on a stack where no active coroutine exists,
// or if the type parameters do not match those of the coroutine.
func Yield[R, S any](v R) S {
return LoadContext[R, S]().Yield(v)
}
// LoadContext returns the context for the current coroutine.
//
// The function panics when called on a stack where no active coroutine exists,
// or if the type parameters do not match those of the coroutine.
func LoadContext[R, S any]() *Context[R, S] {
switch c := load().(type) {
case *Context[R, S]:
return c
case nil:
panic("coroutine.Yield: not called from a coroutine stack")
default:
panic("coroutine.Yield: coroutine type mismatch")
}
}
var (
// ErrNotDurable is an error that occurs when attempting to
// serialize a coroutine that is not durable.
ErrNotDurable = errors.New("only durable coroutines can be serialized")
// ErrInvalidState is an error that occurs when attempting to
// deserialize a coroutine that was serialized in another build.
ErrInvalidState = errors.New("durable coroutine was serialized in another build")
)