Turi Create  4.0
coro.hpp
Go to the documentation of this file.
1 #ifndef TURI_CORO_HPP
2 #define TURI_CORO_HPP
3 /**
4  * \internal
5  * \file
6  * Very limited coroutine implementation.
7  *
8  * Kind of inspired by https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
9  *
10  * Essentially, DECL_CORO_STATE holds an integer which is a line number of the
11  * function., RESET_CORO resets that value to 0. CORO_YIELD sets the current
12  * line number so that the next time the function is called, a switch is used
13  * to jump to the next line.
14  *
15  * Generally this means that this is not a truly general coroutine mechanic
16  * since it does not remember any stack state between function invocations.
17  * Any stack state has to be maintained outside the function. To some extent
18  * this is automatically checked by the compiler due to the use of switch
19  * statements. Generally extra braces between CORO_YIELDs might be necessary
20  * to get the compiler to accept the code.
21  *
22  * Furthermore, parallel invocations, or multiple simultaneous coroutine
23  * invocations of the function is not allowed since there is a single global
24  * variable which maintains the line number. However, wrapping the
25  * DECL_CORO_STATE and the function in a struct/class will allow for it.
26  *
27  * Example:
28  * \code
29  * DECL_CORO_STATE(integers)
30  * int ctr;
31  *
32  * int integers() {
33  * // anything before CORO_BEGIN is run *everytime*
34  * CORO_BEGIN(integers)
35  * ctr = 0;
36  * while(1) {
37  * CORO_YIELD(ctr);
38  * ++ctr;
39  * };
40  * CORO_END
41  * }
42  *
43  * int main() {
44  * while(1) {
45  * std::cout << CALL_CORO(integers) << "\n";
46  * getchar();
47  * }
48  * }
49  * \endcode
50  *
51  * The behavior of this system is very subtle and takes some care to get right.
52  * A perculiarity is that argument values may be different between successive
53  * calls. Ex:
54  * \code
55  * int ctr = 0;
56  * int integers(int start, int end) {
57  * CORO_BEGIN(integers)
58  * for (ctr = start; ctr < end; ++ctr) {
59  * CORO_YIELD(ctr);
60  * }
61  * CORO_END
62  * }
63  *
64  * do {
65  * std::cout << integers(1,10) < "\n";
66  * } while(CORO_RUNNING(integers));
67  * \endcode
68  *
69  * User must be careful to call the method with the same input arguments
70  * everytime or interesting behavior might happen. Similarly, this allows input
71  * arguments to be used to transfer information to the coroutine (for instance,
72  * in a producer/consumer model, an argument can be used to transfer a buffer
73  * from the consumer to the producer).
74  *
75  * Example:
76  * \code
77  * int ctr = 0;
78  * void integers(int start, int end, int* out) {
79  * CORO_BEGIN(integers)
80  * for (ctr = start; ctr < end; ++ctr) {
81  * (*out) = ctr;
82  * CORO_YIELD(ctr);
83  * // after this, the out pointer may change.
84  * }
85  * CORO_END
86  * }
87  * \endcode
88  *
89  * But some care must be taken because after a CORO_YIELD, any of the arguments
90  * may have new values. Essentially, CORO_YIELD itself is an entry point to
91  * the function and has to be treated that way.
92  *
93  * To help in closure maintenance, a struct/class version of the macros are
94  * provided.
95  * \code
96  * struct integers {
97  * DECL_CORO_STATE(call)
98  * int start, int end;
99  * int ctr;
100  * integers(int start, int end):start(start),end(end), ctr(0) {}
101  *
102  * int read() {
103  * CORO_BEGIN(integers)
104  * for (ctr = start; ctr < end; ++ctr) {
105  * CORO_YIELD(ctr);
106  * }
107  * CORO_END
108  * }
109  * };
110  *
111  *
112  * integers counter(1, 10);
113  * do {
114  * std::cout << counter.read() < "\n";
115  * } while(CLASS_CORO_RUNNING(counter, read));
116  * \endcode
117  */
118 
119 #define DECL_CORO_STATE(f) int _coro_state ## f = 0;
120 
121 #define RESET_CORO(f) _coro_state ## f = 0;
122 #define RESET_CLASS_CORO(obj, f) obj._coro_state ## f = 0;
123 #define CALL_CORO(f, ...) f(__VA_ARGS__)
124 #define CALL_CORO_RESET(f, ...) {_coro_state ## f = 0; f(__VA_ARGS__);}
125 #define CORO_BEGIN(f) int& _coro_state = _coro_state ## f; switch(_coro_state) { case 0:
126 #define CORO_YIELD(x) _coro_state=__LINE__; return x; \
127  case __LINE__:
128 #define CORO_END _coro_state = 0; break; }
129 #define CORO_DONE(f) (_coro_state ##f == 0)
130 #define CORO_RUNNING(f) (_coro_state ##f != 0)
131 #define CLASS_CALL_CORO(obj, f, ...) (obj).f(__VA_ARGS__)
132 #define CLASS_CORO_DONE(obj, f) ((obj)._coro_state ##f == 0)
133 #define CLASS_CORO_RUNNING(obj, f) ((obj)._coro_state ##f != 0)
134 
135 #endif