26
Update readme
jonroach 12 Sep 2025 14:35
2 contributors
352 lines12.2 KB
1Stackful coroutines in C.
2
3* `Async` coroutines which can pause, waiting for a future.
4* `Generator` coroutines used as generators for loops.
5* `Coroutine` the coroutine engine used by `Async` and `Generator`.
6
7Your code doesn't need to do anything special to be a coroutine, and only standard, or commonly available libraries are needed.
8
9## Prerequisites
10
11The goal was to make a system which can be used 'out of the box'. These libraries rely on as much as possible on C's cross-platform comfort zone. C's standard libraries are used as far as possible, but, as `threads.h` is not usually supported, `pthread.h` has been used instead.
12
13You will need to build & link the code as part of your system - `coroutine/async.c`, `coroutine/generator.c` and `coroutine/coroutine.c` - ensure the headers, `include/*`, are available on your include path.
14
15## Quick Start
16
17### Async
18
19To run an Async program:
20
21 #include "async.h"
22 main(){
23 Async_StartSystem();
24 void *res = NULL;
25 bool canceled = Async_Run(asyncmain, &param, &res);
26 Async_StopSystem();
27 }
28
29Async runs tasks, switching between them when the current task waits on an `Async_Future`. `asyncmain()` is run as a task. The start function for any task looks like this:
30
31 bool mytask(void *param, void **res){
32
33 // do your thing here
34
35 return canceled;
36 }
37
38When `Task` returns from its start function, it returns whether it was canceled. Canceled `Task`s are assumed to have not finished what they were doing.
39
40Within your async task, create `Async_Task`s and `Async_Task_Await()` them when you want to wait for their result:
41
42 Async_Task task1;
43 Async_Task_ctor(&task1, adifferenttask, &task1param);
44
45 void *result;
46 bool canceled = Async_Task_Await(&task1, &result);
47
48 Async_Task_dtor(&task1);
49
50 // use the result
51
52When a task needs to wait for something, and wants to allow other tasks to run, it should use a `Future`:
53
54 Async_Future future;
55 Async_Future_ctor(&future);
56
57 // pass the future to the background-thing-which-might-take-a-while
58
59 bool canceled = Async_Future_Await(&future);
60 // get the result, if you want it, from the future
61
62 Async_Future_dtor(&future);
63
64When the background-thing-which-might-take-a-while has a result:
65
66 Async_Future_SetResult(future, false, result);
67
68### Generators
69
70The coroutine system needs to be started, either through `Async_StartSystem()`, or directly with `Coroutine_StartSystem()` if you don't want to do async things.
71
72You will need a generator function:
73
74 void *yield_my_things(void *param){
75 bool domore = true;
76
77 // loop/call functions to find more values to yield, and when you have one:
78 domore = Generator_Yield(thing);
79 // .. if domore is false, exit your generator - it is being destructed
80
81 // not actually used by generators, but this is a useful convention for bubbling
82 // the flag out to calling functions.
83 return (void *)domore;
84 }
85
86And to use it:
87
88 Generator gen;
89 Generator_ctor(&gen, yield_my_things, "..");
90 void *thing;
91 while(Generator_Next(&gen, &thing)){
92 // use thing - a value yielded by your generator
93 }
94 Generator_dtor(&gen);
95
96### Coroutines
97
98While you can use coroutines directly, it's designed as a system to support more useful patterns, like `Async` and `Generators`.
99
100The Coroutines system must be started:
101
102 Coroutine_StartSystem();
103 // use coroutines here
104 Coroutine_StopSystem();
105
106Your coroutine will need to have a start function:
107
108 void *start(void *param){
109 ...
110 }
111
112When there is no coroutine running, start your 'main' coroutine:
113
114 void *result = Coroutine_Run(comain, param);
115
116Create other coroutines like this:
117
118 Coroutine *cor = Coroutine_New(start);
119
120When you want a Coroutine to run, or to return from a yield:
121
122 Coroutine_Continue(cor, value, run_early);
123
124`value` will be start function's parameter, or the value returned from the yield.
125
126Within the Coroutine, to yield a value:
127
128 void *Coroutine_Yield(value, on_yield, void *me);
129
130The on_yield function is called after the coroutine has been 'wait'ed, but before the next coroutine is resumed.
131
132## How it Works
133
134The coroutine system uses the stack, divided into smaller stacks, for the coroutines. This means you may need to consider whether the coroutine stack size, set by `COROUTINE_STARTUP_STACK_SIZE`, is right for your coroutines, and whether your stack size is enough for the number of coroutines you might run concurrently.
135
136As each of your thread has its own stack - the coroutine system can be run (or not) independantly on each of your threads. For some special cases, you may want to adjust each of your thread's stack sizes depending on how it is used.
137
138## Style
139
140The style is influenced by C++. For example, where possible, a `Something *Something_New(a, b, c)` and `Something_Delete(Something *)`, where a `Something` is `malloc`ed, will have corresponding `Somthing_ctor(Somthing *, a, b, c)` and `Something_dtor(Something *)` to initialise and finalise a `Something` on the stack, or within another object. Using `.._ctor()` and `.._dtor()` will be faster as they avoid the `malloc()` and `free()`.
141
142 Something *oneofthem = Something_New();
143 // use oneofthem
144 Something_Delete(oneofthem);
145
146Can be also be done like this, and this will run faster:
147
148 Something oneofthem;
149 Something_ctor(&oneofthem);
150 // use oneofthem
151 Something_dtor(&oneofthem);
152
153The exception is `Coroutine_New()` and `Coroutine_Delete()`. The returned `Coroutine` is somewhere on your thread's stack - its memory is managed by the coroutine system, and is allocated and freed quickly.
154
155## Usage
156
157When you are using coroutines or generators:
158
159 void *myfunc(void *){
160 // your function here
161 }
162
163 Coroutine_StartSystem();
164 Coroutine_Run(myfunc, (void *)myparam);
165 Coroutine_StopSystem();
166
167If you also use async, then:
168
169 bool myfunc(void *myparam, void **res){
170 // your async function here
171 }
172
173 Async_StartSystem();
174 void *res = NULL;
175 bool canceled = Async_Run(myfunc, myparam, &res);
176 Async_StopSystem();
177
178While the system is started, you can make many calls to `Coroutine_Run()` or `Async_Run()`. A running system is thread local - each thread you want to use coroutines on will need to be `Coroutine_StartSystem()`ed or `Async_StartSystem()`ed.
179
180
181
182# API
183
184## Generator
185
186The pattern for a `Generator` is:
187
188#### A loop which uses the `Generator`
189
190 Generator gen;
191 Generator_ctor(&gen, mygen, &param);
192
193 void *value;
194 while(Generator_Next(&gen, &value)){
195 // use value here
196 }
197 // value is now the return value from the Generator
198
199 Generator_dtor(&gen);
200
201Or:
202
203 Generator *gen = Generator_New(mygen, &param);
204
205 void *value;
206 while(Generator_Next(gen, &value)){
207 // use value here
208 }
209
210 Generator_Delete(gen);
211
212`Generator`s yield a series of `void *`s - what the `void *`s mean is up to you. `Generator_Next()` returns a `bool` to indicate whether the `Generator` has finished.
213
214#### A generator function
215
216 void *mygen(void *param){
217 bool domore = true;
218 // The parameter is a pointer to a string of chars
219 for (char *str = param; *str; ++str) {
220 // The value yielded is a pointer to a character in the string
221 domore = Generator_Yield(str);
222 if (!domore){
223 break;
224 }
225 }
226
227 return (void *)domore;
228 }
229
230The `bool` returned from `Generator_Yield()` indicates whether the generator function should yield more values. When it is `false` the `Generator` is being finalised - your generator function should close files, and release any other resources it has claimed, before exiting.
231
232### void Generator_ctor(Generator *gen, void *(*start)(void *), void *param)
233
234Initialise a `Generator`
235
236 Generator gen;
237 Generator_ctor(&gen, mystart, &params);
238
239 // Generator is used
240
241 // ... later:
242 Generator_dtor(&gen);
243
244### Generator *Generator_New(void *(*)(void *), void *)
245
246`new` a `Generator` - malloc it and initialise it.
247
248 Generator *gen = Generator_New(mystart, &params);
249
250 // Generator is used
251
252 // ... later:
253 Generator_Delete(gen);
254
255### void Generator_dtor(Generator *gen)
256
257Finalise a `Generator`. Once a `Generator` is no longer needed, it must be finalised:
258
259 // earlier...
260 Generator gen;
261 Generator_ctor(&gen, mystart, &params);
262
263 // Generator is used
264
265 // the Generator is no longer needed
266 Generator_dtor(&gen);
267
268
269### void Generator_Delete(Generator *)
270
271Finalise then `free()` a `Generator`. Once a `new`ed `Generator` is no longer needed, it must be deleted:
272
273 // earlier...
274 Generator *gen = Generator_New(mystart, &params);
275
276 // Generator is used
277
278 // the Generator is no longer needed
279 Generator_Delete(gen);
280
281
282### bool Generator_Next(Generator *, void **value)
283
284Get the next value yielded by the `Generator`.
285
286 void *value;
287 while(Generator_Next(gen, &value)){
288 // use value here
289 }
290
291When `true` is returned, `value` is the value yielded by the `Generator`. When `false` is returned, `value` is the value returned when the `Generator` returned - the `Generator` has finished.
292
293### bool Generator_Yield(void *)
294
295Yield a value from a `Generator` function.
296
297 bool domore = Generator_Yield(value);
298
299`value` is then provided by `Generator_Next()` as the next value from the generator. The `bool` returned by `Generator_Yield()` indicates whether more values should be provided by your generator function. When `true` provide more values if possible. When `false` close files, free memory, free up any other resources and `return`. `false` is returned when the `Generator` is being finalised.
300
301## Coroutine
302
303### void Coroutine_StartSystem();
304
305Start the coroutine system on this thread. When you've finished with `Coroutine` call `Coroutine_Stop()`. `Coroutine` can be started & stopped many times on one thread. The total stack allowed for all coroutines running on a thread is the size of the call stack on that thread.
306
307### void Coroutine_StopSystem();
308
309Stop the coroutine system on this thread.
310
311### Coroutine_Start
312
313 void *(*)(void *param)
314
315The entry function for a coroutine. The `param` is the value passed to `Coroutine_Continue`, and the `void *` return value can be accessed through the `Coroutine` object.
316
317### Coroutine *Coroutine_New(Coroutine_Start start)
318
319Create a new `Coroutine`. The `Coroutine` system must be started to create a `Coroutine`. The stack size available to the coroutine will be `COROUTINE_STACK_SIZE` defined in `coroutine.h`. When you have finished with your `Coroutine`, use `Coroutine_Delete()` to delete it.
320
321### void Coroutine_Run_Coroutine(Coroutine *cor, void *value)
322
323Run the `Coroutine` and return when it returns. This is how to start coroutines running in the coroutine system. It is an error for the run coroutine to return before all other coroutines have completed.
324
325### void *Coroutine_Run(Coroutine_Start start, void *value)
326
327Convenience wrapper for `Coroutine_Run_Coroutine` which creates the `Coroutine` and retrieves its result.
328
329### void Coroutine_Delete(Coroutine *cor)
330
331Use `Coroutine_Delete()` to delete a coroutine when it is no longer needed.
332
333### void Coroutine_Continue(Coroutine *cor, void *value, bool early)
334
335Continue the given `Coroutine`. `value` is passed to the coroutine, as `param` to the `start` function, or as the return value from `Coroutine_Yield`. `early` determines whether the continued coroutine is added to the head of tail of the list of runable coroutines.
336
337### void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *this)
338
339Yield `value` from the current coroutine; this coroutine is moved to the list of coroutines waiting to be continued. The next runable coroutine is run - either by its start routine being called with `value` as its `param`, or by `value`being returned from its `Coroutine_Yield()`.
340
341### void *Coroutine_GetValue(Coroutine *cor)
342
343Return the `Coroutine`'s value - the value last yielded, or returned by its `start` routine.
344
345### Coroutine *Coroutine_GetActive()
346
347Return whihc coroutine is currently running, ie the caller's `Coroutine`.
348
349### bool Coroutine_IsRunning(Coroutine *cor)
350
351Return whether the given coroutine is still running - it may be running, ready to run, or waiting to be continued, but won't have returned from its `start` function.
352