8 months ago |
1 |
Stackful coroutines in C. | |||
8 months ago |
17 |
2 |
|||
8 months ago |
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`. | ||||
8 months ago |
17 |
6 |
|||
8 months ago |
7 |
Your code doesn't need to do anything special to be a coroutine, and only standard, or commonly available libraries are needed. | |||
8 months ago |
17 |
8 |
|||
8 months ago |
9 |
## Prerequisites | |||
8 months ago |
17 |
10 |
|||
8 months ago |
11 |
The 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. | |||
8 months ago |
17 |
12 |
|||
8 months ago |
13 |
You 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. | |||
8 months ago |
17 |
14 |
|||
8 months ago |
15 |
## Quick Start | |||
8 months ago |
17 |
16 |
|||
8 months ago |
17 |
### Async | |||
8 months ago |
17 |
18 |
|||
8 months ago |
19 |
To run an Async program: | |||
8 months ago |
17 |
20 |
|||
8 months ago |
21 |
#include "async.h" | |||
22 |
main(){ | ||||
23 |
Async_StartSystem(); | ||||
24 |
void *res = NULL; | ||||
25 |
bool canceled = Async_Run(asyncmain, ¶m, &res); | ||||
26 |
Async_StopSystem(); | ||||
27 |
} | ||||
28 |
|||||
29 |
Async 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 |
|||||
38 |
When `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 |
|||||
40 |
Within 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 |
|||||
52 |
When 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 |
|||||
8 months ago |
59 |
void *res; | |||
60 |
bool canceled = Async_Future_Await(&future, &res); | ||||
8 months ago |
61 |
||||
62 |
Async_Future_dtor(&future); | ||||
63 |
|||||
64 |
When the background-thing-which-might-take-a-while has a result: | ||||
65 |
|||||
66 |
Async_Future_SetResult(future, false, result); | ||||
67 |
|||||
68 |
### Generators | ||||
69 |
|||||
70 |
The 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 |
|||||
72 |
You 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 |
|||||
86 |
And 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 |
|||||
98 |
While you can use coroutines directly, it's designed as a system to support more useful patterns, like `Async` and `Generators`. | ||||
99 |
|||||
100 |
The Coroutines system must be started: | ||||
101 |
|||||
102 |
Coroutine_StartSystem(); | ||||
103 |
// use coroutines here | ||||
104 |
Coroutine_StopSystem(); | ||||
105 |
|||||
106 |
Your coroutine will need to have a start function: | ||||
107 |
|||||
108 |
void *start(void *param){ | ||||
109 |
... | ||||
110 |
} | ||||
111 |
|||||
112 |
When there is no coroutine running, start your 'main' coroutine: | ||||
113 |
|||||
114 |
void *result = Coroutine_Run(comain, param); | ||||
115 |
|||||
116 |
Create other coroutines like this: | ||||
117 |
|||||
118 |
Coroutine *cor = Coroutine_New(start); | ||||
119 |
|||||
120 |
When 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 |
|||||
126 |
Within the Coroutine, to yield a value: | ||||
127 |
|||||
128 |
void *Coroutine_Yield(value, on_yield, void *me); | ||||
129 |
|||||
130 |
The 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 |
|||||
8 months ago |
134 |
The 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 application, and whether your stack size is enough for the number of coroutines you might run. | |||
8 months ago |
135 |
||||
8 months ago |
136 |
Each of your threads has its own stack - the coroutine system can be run (or not) independantly on each thread. For some special cases, you may want to adjust each of your thread's stack sizes depending on how it is used. | |||
8 months ago |
137 |
||||
138 |
## Style | ||||
139 |
|||||
8 months ago |
17 |
140 |
The 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 |
|||||
146 |
Can 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 |
|||||
8 months ago |
153 |
The 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 |
|||||
8 months ago |
17 |
155 |
## Usage | ||
156 |
|||||
157 |
When 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 |
|||||
167 |
If 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 |
|||||
178 |
While 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 |
|||||
8 months ago |
180 |
## Stack Overruns | |||
8 months ago |
17 |
181 |
|||
8 months ago |
182 |
The C stack is divided down into smaller stacks. There's one to give some work room between `..StartSystem()` and `..Run()`, and one for each coroutine. These have guard markers which are checked to see if the stack has overrun. If there is a stack overrun, the system cannot continue - a message is output and the programe exited. There's a number of ways to avoid this issue: | |||
8 months ago |
17 |
183 |
|||
8 months ago |
184 |
* Use less stack. This is, sometimes, the right advice, especially if the startup stack overrins. The expectation is that very little is done between `.._StartSystem()` and `..Run()`. If your situation needs more doing, you can... | |||
185 |
|||||
186 |
* increase the stack size. Adjust `COROUTINE_STACK_SIZE` (for startup) or `COROUTINE_STARTUP_STACK_SIZE` (for coroutines) as appropriate. If your use case is even more demanding, such as if you want 1000s of coroutines (so you need small stack chunks), /and/ some of them can recurse an unknown amount (so you need a deep stack for that coroutine), then you can... | ||||
187 |
|||||
188 |
* monitor stack headroom, and add another stack chunk if you need to: | ||||
189 |
|||||
190 |
In this last case you'll need to add some code at key points: | ||||
191 |
|||||
192 |
void *myfunction(void *param){ | ||||
193 |
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_STACK){ | ||||
194 |
return Coroutine_Chain(myfunction, param); | ||||
195 |
} | ||||
196 |
// do everything normally | ||||
197 |
} | ||||
198 |
|||||
199 |
More realistically: | ||||
200 |
|||||
201 |
struct myfunctionparams { | ||||
202 |
int a; | ||||
203 |
char *b; | ||||
204 |
struct dog *d; | ||||
205 |
} | ||||
206 |
|||||
207 |
void *mychain(void *param){ | ||||
208 |
struct myfunctionparams *myparams = (struct myfunctionparams *)params; | ||||
209 |
return (void *)myfunction(myparams->a, myparams->b, *myparams->d); | ||||
210 |
} | ||||
211 |
|||||
212 |
int myfunction(int a, char *b, struct dog d){ | ||||
213 |
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_STACK){ | ||||
214 |
struct myfunctionparams params = { | ||||
215 |
a, | ||||
216 |
b, | ||||
217 |
&d | ||||
218 |
}; | ||||
219 |
return (int)Coroutine_Chain(mychain, ¶ms); | ||||
220 |
} | ||||
221 |
} | ||||
222 |
|||||
8 months ago |
223 |
And if you want to panic if the C stack overruns: | |||
224 |
|||||
225 |
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_COROUTINE_STACK){ | ||||
226 |
if (Coroutine_HasCoroutinesInFreePool() || | ||||
227 |
(char *)Coroutine_GetCStackTop() - c_stack_end >= MIN_ALLOWED_C_STACK) { | ||||
228 |
struct myfunctionparams params = { | ||||
229 |
a, | ||||
230 |
b, | ||||
231 |
&d | ||||
232 |
}; | ||||
233 |
return (int)Coroutine_Chain(mychain, ¶ms); | ||||
234 |
} | ||||
235 |
// panic now | ||||
236 |
} | ||||
237 |
|||||
8 months ago |
238 |
# API | |||
8 months ago |
17 |
239 |
|||
8 months ago |
240 |
## Async | |||
241 |
|||||
242 |
The pattern for using async is: | ||||
243 |
|||||
244 |
void *myasyncmaintask(void *param){ | ||||
245 |
// do your main async task things here, like starting more tasks | ||||
246 |
} | ||||
247 |
|||||
248 |
Async_StartSystem(); | ||||
249 |
void *res = NULL; | ||||
250 |
bool canceled = Async_Run(myasyncmaintask, NULL, &res); | ||||
251 |
Async_StopSystem(); | ||||
252 |
|||||
253 |
To create and wait for an async task: | ||||
254 |
|||||
255 |
Async_Task task1; | ||||
256 |
Async_Task_ctor(&task1, asynctask1, &task1param); | ||||
257 |
void *res = NULL; | ||||
258 |
bool canceled = Async_Task_Await(&task1, void **res) | ||||
259 |
Async_Task_dtor(&task1); | ||||
260 |
|||||
261 |
or, if you prefer new & delete: | ||||
262 |
|||||
263 |
Async_Task *task1 = Async_Task_New(asynctask1, &task1param); | ||||
264 |
void *res = NULL; | ||||
265 |
bool canceled = Async_Task_Await(task1, void **res) | ||||
266 |
Async_Task_Delete(task1); | ||||
267 |
|||||
268 |
Inside your task, when there is something to wait for and you want other tasks to run while your task is waiting, you will need a future: | ||||
269 |
|||||
270 |
Async_Future future; | ||||
271 |
Async_Future_ctor(&future); | ||||
272 |
|||||
273 |
// keep &future to hand for when the background thing completes | ||||
274 |
bool canceled = Async_Future_Await(&future, NULL); | ||||
275 |
|||||
276 |
Async_Future_dtor(&future); | ||||
277 |
|||||
278 |
`Async_Future_New()` and `Async_Future_Delete()` are also available if you prefer that style. | ||||
279 |
|||||
280 |
Inside the callback when the background thing is complete: | ||||
281 |
|||||
282 |
// result is a void * | ||||
283 |
Async_Future_SetResult(future, result, false); | ||||
284 |
|||||
285 |
or, if something went wrong: | ||||
286 |
|||||
287 |
// exception is a void * | ||||
288 |
Async_Future_SetResult(future, exception, true); | ||||
289 |
|||||
290 |
Back in the task, you can respond to the future: | ||||
291 |
|||||
292 |
... Async_Future_Await has returned | ||||
293 |
if (canceled){ | ||||
294 |
// exit quickly - you've been canceled | ||||
295 |
// you could, for example, use the future's result as an exception, or error code here | ||||
296 |
} | ||||
297 |
// carry on - the future's result may be an actual result, that's up to you | ||||
298 |
|||||
299 |
|||||
8 months ago |
300 |
##### void Async_Future_ctor(Async_Future *fut) | |||
8 months ago |
301 |
||||
8 months ago |
302 |
fut | |||
8 months ago |
303 |
: The `Async_Future` being constructed | |||
8 months ago |
304 |
||||
8 months ago |
305 |
Initialise a future. When you no longer need it, use `Async_Future_dtor()`. | |||
306 |
|||||
8 months ago |
307 |
##### Async_Future *Async_Future_New() | |||
8 months ago |
308 |
||||
8 months ago |
309 |
(returns) | |||
8 months ago |
310 |
: The new future | |||
8 months ago |
311 |
||||
8 months ago |
312 |
Allocates and initialises a future, When you no longer need it, use `Async_Future_Delete()`. | |||
8 months ago |
313 |
||||
8 months ago |
314 |
##### void Async_Future_dtor(Async_Future *fut) | |||
8 months ago |
315 |
||||
316 |
fut | ||||
317 |
: The `Async_Future` being destructed | ||||
318 |
|||||
319 |
Destruct a future previously constructed with `Async_Future_ctor()`. | ||||
320 |
|||||
8 months ago |
321 |
##### void Async_Future_Delete(Async_Future *fut) | |||
8 months ago |
322 |
||||
8 months ago |
323 |
fut | |||
324 |
: The `Async_Future` to be destructed and freed | ||||
325 |
|||||
8 months ago |
326 |
Delete (finalise and free) a future previously new'ed with `Async_Future_New()` | |||
327 |
|||||
8 months ago |
328 |
##### void Async_Future_SetResult(Async_Future *fut, bool canceled, void *value) | |||
8 months ago |
329 |
||||
8 months ago |
330 |
fut | |||
331 |
: The `Async_Future` whose result is being set | ||||
8 months ago |
332 |
||||
8 months ago |
333 |
canceled | |||
334 |
: The future's `canceled` setting | ||||
8 months ago |
335 |
||||
8 months ago |
336 |
value | |||
337 |
: The future's `value` of the result | ||||
8 months ago |
338 |
||||
8 months ago |
339 |
Set the result of a future. This has an effect only the first time its done, ie a completed future can't be canceled and a canceled future can't be completed. When an `Async_Future` has a result, its watchers are called back. | |||
8 months ago |
340 |
||||
8 months ago |
341 |
The `value` of a future might be a result if the future completes (when `canceled == false`), or could be some sort of exception value if `canceled == true`. The interpretation of a future's `value` is up to the user - as far as the async system is concerned, it's only a `void *`. | |||
8 months ago |
342 |
||||
8 months ago |
343 |
##### bool Async_Future_GetResult(Async_Future *fut, void **res) | |||
8 months ago |
344 |
||||
8 months ago |
345 |
(returns) | |||
346 |
: The `canceled` value of the `Async_Future`. | ||||
8 months ago |
347 |
||||
8 months ago |
348 |
res | |||
349 |
: Where to store the value of the `Async_Future`. This may be `NULL`. | ||||
8 months ago |
350 |
||||
8 months ago |
351 |
Get the result of a future. | |||
8 months ago |
352 |
||||
8 months ago |
353 |
##### typedef void (*Future_Watcher)(void *me, Async_Future *fut) | |||
8 months ago |
354 |
||||
8 months ago |
355 |
A `Future_Watcher` is a callback called when a future has a result. The `me` parameter is the one passed to `Async_Future_AddWatcher()`. `fut` is the future which has just got its result. | |||
8 months ago |
356 |
||||
8 months ago |
357 |
##### void Async_Future_AddWatcher(Async_Future *fut, Future_Watcher watcher, void *me) | |||
8 months ago |
358 |
||||
8 months ago |
359 |
fut | |||
360 |
: the `Async_Future` to add a watcher to | ||||
361 |
|||||
362 |
watcher | ||||
363 |
: the callback to call when the `Async_Future` has a result. | ||||
364 |
|||||
365 |
me | ||||
366 |
: the `me` value to pass to `watcher` when it is called back. | ||||
367 |
|||||
368 |
Add a watcher (callback) to be called when the future has a result. If the future is already complete, `watcher` is immediately called. The `me` value is passed to the watcher as its `me` parameter. It is assumed that a watcher, identified by the `(watcher, me)` pair, will only be added once. | ||||
369 |
|||||
8 months ago |
370 |
##### void Async_Future_RemoveWatcher(Async_Future *fut, Future_Watcher watcher, void *me) | |||
8 months ago |
371 |
||||
372 |
fut | ||||
373 |
: the `Async_Future` to remove a watcher from | ||||
374 |
|||||
375 |
watcher | ||||
376 |
: the callback of the watcher to remove. | ||||
377 |
|||||
378 |
me | ||||
379 |
: the `me` value of the watcher to remove. | ||||
380 |
|||||
381 |
Remove a watcher from a future. It is not an error if no watcher matching `(watcher, me)` is found - it has probably already been called back. | ||||
382 |
|||||
8 months ago |
383 |
##### bool Async_Future_Await(Async_Future *fut, void **res) | |||
8 months ago |
384 |
||||
385 |
(returns) | ||||
386 |
: whether the `Async_Future` was canceled. | ||||
387 |
|||||
388 |
fut | ||||
389 |
: The `Async_Future` to wait for. | ||||
390 |
|||||
391 |
res | ||||
392 |
: Where to store the `value` of the future when it is has a result. May be `NULL`. | ||||
393 |
|||||
394 |
The current `Async_Task` is paused until the `Async_Future` has a result. Other `Async_Task`s are run while this one is waiting. | ||||
395 |
|||||
8 months ago |
396 |
##### typedef bool (*Async_Entry)(void *param, void **res) | |||
8 months ago |
397 |
||||
8 months ago |
398 |
The entry function to an `Async_Task`. | |||
399 |
|||||
8 months ago |
400 |
##### void Async_Task_ctor(Async_Task *tsk, Async_Entry entry, void *param) | |||
8 months ago |
401 |
||||
402 |
tsk | ||||
403 |
: The task to construct. | ||||
404 |
|||||
405 |
entry | ||||
406 |
: The entry function for the task. | ||||
407 |
|||||
408 |
param | ||||
409 |
: The value for `param` to pass to `entry`. | ||||
410 |
|||||
411 |
Initialises an `Async_Task`. When you have finished with an `Async_Task` you must finalise it using `Async_Task_dtor()` | ||||
412 |
|||||
413 |
Async_Task tsk; | ||||
414 |
Async_Task_ctor(&tsk, mytask, myparam); | ||||
415 |
// tsk will run if you wait for a task or future | ||||
416 |
Async_Task_Await(&tsk, NULL); | ||||
417 |
Async_Task_dtor(&tsk); | ||||
418 |
|||||
8 months ago |
419 |
##### Async_Task *Async_Task_New(Async_Entry entry, void *param) | |||
8 months ago |
420 |
||||
421 |
(returns) | ||||
422 |
: The new `Async_Task`. | ||||
423 |
|||||
424 |
entry | ||||
425 |
: The entry function for the task. | ||||
426 |
|||||
427 |
param | ||||
428 |
: The value for `param` to pass to `entry`. | ||||
429 |
|||||
430 |
This allocates and initialises a new `Async_Task`. When you have finished with your task, you must `Async_Delete()` it. | ||||
431 |
|||||
432 |
Async_Task *tsk = Async_New(mytask, myparam); | ||||
433 |
// tsk will run if you wait for a task or future | ||||
434 |
Async_Task_Await(tsk, NULL); | ||||
435 |
Async_Task_Delete(tsk); | ||||
436 |
|||||
8 months ago |
437 |
##### void Async_Task_dtor(Async_Task *tsk) | |||
8 months ago |
438 |
||||
439 |
tsk | ||||
440 |
: The `Async_Task` to destruct. | ||||
441 |
|||||
442 |
This finalises an `Async_Task` you ealier initalised with `Async_Task_ctor()`. It is an error to attempt to destruct a task which is running. | ||||
443 |
|||||
444 |
Async_Task tsk; | ||||
445 |
Async_Task_ctor(&tsk, mytask, myparam); | ||||
446 |
// use tsk | ||||
447 |
Async_Task_dtor(&tsk); | ||||
448 |
|||||
8 months ago |
449 |
##### void Async_Task_Delete(Async_Task *tsk) | |||
8 months ago |
450 |
||||
451 |
tsk | ||||
452 |
: The `Async_Task` to delete. | ||||
453 |
|||||
454 |
This finalises and frees an `Async_Task` you ealier new'ed with `Async_Task_New()`. It is an error to attempt to delete a task which is running. | ||||
455 |
|||||
456 |
Async_Task *tsk = Async_Task_New(mytask, myparam); | ||||
457 |
// use tsk | ||||
458 |
Async_Task_Delete(tsk); | ||||
459 |
|||||
8 months ago |
460 |
##### static inline bool Async_Task_Await(Async_Task *tsk, void **res) | |||
8 months ago |
461 |
||||
462 |
(returns) | ||||
463 |
: Whether the task was canceled. | ||||
464 |
|||||
465 |
tsk | ||||
466 |
: The `Async_Task` to wait for. | ||||
467 |
|||||
468 |
res | ||||
469 |
: Where to store the `Async_Task`'s value when it finishes. This may be NULL. | ||||
470 |
|||||
471 |
The current `Async_Task` waits for `tsk` to finish, and returns the result. | ||||
472 |
|||||
8 months ago |
473 |
##### void Async_Task_Cancel(Async_Task *tsk, void *cancel_value) | |||
8 months ago |
474 |
||||
475 |
tsk | ||||
476 |
: The task to cancel. | ||||
477 |
|||||
478 |
cancel_value | ||||
479 |
: The value to set on any future this task waits on. | ||||
480 |
|||||
481 |
This marks a task as canceled. When that task waits on a future that future will be canceled too, using `cancel_value`. | ||||
482 |
|||||
8 months ago |
483 |
##### static inline bool Async_Task_IsCanceled(Async_Task *tsk) | |||
8 months ago |
484 |
||||
485 |
(returns) | ||||
486 |
: Whether the task is canceled. | ||||
487 |
|||||
488 |
tsk | ||||
489 |
: The task to get its canceled setting from. | ||||
490 |
|||||
8 months ago |
491 |
##### static inline Async_Future *Async_Task_GetAwaitedFuture(Async_Task *tsk) | |||
8 months ago |
492 |
||||
8 months ago |
493 |
(returns) | |||
494 |
: The future the task is waiting on. May be NULL. | ||||
495 |
|||||
496 |
tsk | ||||
497 |
: Teh task to read the future it is waiting on. | ||||
498 |
|||||
499 |
Return the future a task is waiting on. | ||||
500 |
|||||
8 months ago |
501 |
##### bool Async_Sleep(float delay, void **value) | |||
8 months ago |
502 |
||||
503 |
(returns) | ||||
504 |
: Whether the task was canceled. | ||||
505 |
|||||
506 |
delay | ||||
507 |
: How many seconds to delay for. | ||||
508 |
|||||
509 |
value | ||||
510 |
: Where to store the cancellation value. This may be NULL. | ||||
511 |
|||||
512 |
Sleep for `delay` seconds. `*value` will be set to NULL if the sleep is successful, and the `cancel_value` if the task is canceled. | ||||
513 |
|||||
8 months ago |
514 |
##### void Async_StartSystem() | |||
8 months ago |
515 |
||||
516 |
Start the async system on this thread. If you want to use async features, use this instead of `Coroutine_StartSystem()`. When you have finished with the async system, use `Async_StopSystem()`. | ||||
517 |
|||||
518 |
Async_StartSystem(); | ||||
519 |
// used async here | ||||
520 |
Async_StopSystem(); | ||||
521 |
|||||
8 months ago |
522 |
##### bool Async_Run(Async_Entry start, void *value, void **res) | |||
8 months ago |
523 |
||||
524 |
(returns) | ||||
525 |
: Whether `start` was canceled. | ||||
526 |
|||||
527 |
start | ||||
528 |
: The function to use as the main task. | ||||
529 |
|||||
530 |
value | ||||
531 |
: The value to pass to `start`. | ||||
532 |
|||||
533 |
res | ||||
534 |
: Where to store the result of `start`. | ||||
535 |
|||||
536 |
Runs `start` as an `Async_Task`. When `start` returns all other tasks must have been destructed, using `Async_Task_dtor()` or `Async_Task_Delete()`. | ||||
537 |
|||||
8 months ago |
538 |
##### void Async_StopSystem() | |||
8 months ago |
539 |
||||
8 months ago |
540 |
Stops the async system. When you have finished using the async system, you must stop it. | |||
8 months ago |
541 |
||||
8 months ago |
542 |
Async_Start(); | |||
543 |
// use the async system | ||||
544 |
Async_Stop(); | ||||
545 |
|||||
8 months ago |
546 |
## Generator | |||
8 months ago |
17 |
547 |
|||
8 months ago |
548 |
The pattern for a `Generator` is: | |||
549 |
|||||
8 months ago |
550 |
#### A loop which uses the `Generator | |||
8 months ago |
551 |
||||
8 months ago |
17 |
552 |
Generator gen; | ||
8 months ago |
553 |
Generator_ctor(&gen, mygen, ¶m); | |||
8 months ago |
17 |
554 |
|||
8 months ago |
555 |
void *value; | |||
556 |
while(Generator_Next(&gen, &value)){ | ||||
557 |
// use value here | ||||
8 months ago |
17 |
558 |
} | ||
8 months ago |
559 |
// value is now the return value from the Generator | |||
560 |
|||||
8 months ago |
17 |
561 |
Generator_dtor(&gen); | ||
562 |
|||||
8 months ago |
563 |
Or: | |||
8 months ago |
17 |
564 |
|||
8 months ago |
565 |
Generator *gen = Generator_New(mygen, ¶m); | |||
8 months ago |
17 |
566 |
|||
8 months ago |
567 |
void *value; | |||
568 |
while(Generator_Next(gen, &value)){ | ||||
569 |
// use value here | ||||
570 |
} | ||||
8 months ago |
17 |
571 |
|||
8 months ago |
572 |
Generator_Delete(gen); | |||
8 months ago |
17 |
573 |
|||
8 months ago |
574 |
`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. | |||
8 months ago |
17 |
575 |
|||
8 months ago |
576 |
#### A generator function | |||
8 months ago |
17 |
577 |
|||
8 months ago |
578 |
void *mygen(void *param){ | |||
579 |
bool domore = true; | ||||
580 |
// The parameter is a pointer to a string of chars | ||||
581 |
for (char *str = param; *str; ++str) { | ||||
582 |
// The value yielded is a pointer to a character in the string | ||||
583 |
domore = Generator_Yield(str); | ||||
584 |
if (!domore){ | ||||
585 |
break; | ||||
586 |
} | ||||
587 |
} | ||||
588 |
|||||
589 |
return (void *)domore; | ||||
8 months ago |
17 |
590 |
} | ||
591 |
|||||
8 months ago |
592 |
The `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. | |||
8 months ago |
17 |
593 |
|||
8 months ago |
594 |
##### void Generator_ctor(Generator *gen, void *(*start)(void *), void *param) | |||
8 months ago |
17 |
595 |
|||
8 months ago |
596 |
gen | |||
597 |
: The `Generator` to construct. | ||||
8 months ago |
17 |
598 |
|||
8 months ago |
599 |
start | |||
600 |
: The function which is the start/entry-point of the `Generator`. | ||||
601 |
|||||
602 |
param | ||||
603 |
: The value to pass to `start`. | ||||
604 |
|||||
605 |
Initialise a `Generator`. When you no longer need the `Generator`, use `Generator_dtor()` to destruct it. | ||||
606 |
|||||
8 months ago |
607 |
Generator gen; | |||
608 |
Generator_ctor(&gen, mystart, ¶ms); | ||||
8 months ago |
17 |
609 |
|||
8 months ago |
610 |
// Generator is used | |||
8 months ago |
17 |
611 |
|||
8 months ago |
612 |
// ... later: | |||
613 |
Generator_dtor(&gen); | ||||
614 |
|||||
8 months ago |
615 |
##### Generator *Generator_New(void *(*start)(void *), void *param) | |||
8 months ago |
616 |
||||
8 months ago |
617 |
start | |||
618 |
: The function which is the start/entry-point of the `Generator`. | ||||
8 months ago |
619 |
||||
8 months ago |
620 |
param | |||
621 |
: The value to pass to `start`. | ||||
622 |
|||||
623 |
`new` a `Generator` - malloc, and initialise it. When you no longer need the `Generator` use `Generator_dtor` to finalise it. | ||||
624 |
|||||
8 months ago |
625 |
Generator *gen = Generator_New(mystart, ¶ms); | |||
626 |
|||||
627 |
// Generator is used | ||||
628 |
|||||
629 |
// ... later: | ||||
630 |
Generator_Delete(gen); | ||||
631 |
|||||
8 months ago |
632 |
##### void Generator_dtor(Generator *gen) | |||
8 months ago |
633 |
||||
8 months ago |
634 |
gen | |||
635 |
: The `Generator` to destruct. | ||||
636 |
|||||
8 months ago |
637 |
Finalise a `Generator`. Once a `Generator` is no longer needed, it must be finalised: | |||
638 |
|||||
639 |
// earlier... | ||||
640 |
Generator gen; | ||||
641 |
Generator_ctor(&gen, mystart, ¶ms); | ||||
642 |
|||||
643 |
// Generator is used | ||||
644 |
|||||
645 |
// the Generator is no longer needed | ||||
646 |
Generator_dtor(&gen); | ||||
647 |
|||||
648 |
|||||
8 months ago |
649 |
##### void Generator_Delete(Generator *gen) | |||
8 months ago |
650 |
||||
8 months ago |
651 |
gen | |||
652 |
: The `Generator` to delete. | ||||
653 |
|||||
8 months ago |
654 |
Finalise then `free()` a `Generator`. Once a `new`ed `Generator` is no longer needed, it must be deleted: | |||
655 |
|||||
656 |
// earlier... | ||||
657 |
Generator *gen = Generator_New(mystart, ¶ms); | ||||
658 |
|||||
659 |
// Generator is used | ||||
660 |
|||||
661 |
// the Generator is no longer needed | ||||
662 |
Generator_Delete(gen); | ||||
663 |
|||||
664 |
|||||
8 months ago |
665 |
##### bool Generator_Next(Generator *gen, void **value) | |||
8 months ago |
666 |
||||
8 months ago |
667 |
(returns) | |||
668 |
: Whether there is a next value. `true` - there is a next value; `false` - the `Generator` has finished | ||||
669 |
|||||
670 |
gen | ||||
671 |
: The `Generator` to get the next value from. | ||||
672 |
|||||
673 |
value | ||||
674 |
: Where to store the next value. | ||||
675 |
|||||
8 months ago |
676 |
Get the next value yielded by the `Generator`. | |||
677 |
|||||
678 |
void *value; | ||||
679 |
while(Generator_Next(gen, &value)){ | ||||
680 |
// use value here | ||||
8 months ago |
17 |
681 |
} | ||
8 months ago |
682 |
||||
8 months ago |
683 |
The `Generator` feeds values to its client using `Generator_Yield()` - it is these values which `Generator_Next()` sets, in the example, `value` to. | |||
8 months ago |
684 |
||||
8 months ago |
685 |
When a `Generator` is finished it returns from `start`. When you call `Generator_Yield()` on a finished `Generator` it returns `false` and `value` will be the return value from `start`. | |||
686 |
|||||
8 months ago |
687 |
##### bool Generator_Yield(void *value) | |||
8 months ago |
688 |
||||
8 months ago |
689 |
(returns) | |||
690 |
: Whether the `Generator` should do more. | ||||
8 months ago |
691 |
||||
8 months ago |
692 |
value | |||
693 |
: The `Generator`'s next value. | ||||
694 |
|||||
695 |
Yield a value from a `Generator`. | ||||
696 |
|||||
8 months ago |
697 |
bool domore = Generator_Yield(value); | |||
698 |
|||||
8 months ago |
699 |
`value` is then provided by `Generator_Next()` as the next value from the generator. | |||
8 months ago |
700 |
||||
8 months ago |
701 |
The `bool` returned by `Generator_Yield()` says whether more values should be provided by your generator function. `true` - provide more values if there are any. `false` - close files, free memory, free up any other resources and `return`. `false` is returned when the `Generator` is being finalised before it has finished, ie the client has exited its `for`-loop early. | |||
702 |
|||||
8 months ago |
703 |
## Coroutine | |||
704 |
|||||
8 months ago |
705 |
##### void Coroutine_StartSystem() | |||
8 months ago |
706 |
||||
8 months ago |
707 |
Start the coroutine system on this thread. When you've finished with `Coroutine` must call `Coroutine_Stop()`. | |||
8 months ago |
708 |
||||
8 months ago |
709 |
Coroutine_Start(); | |||
710 |
// prepare | ||||
711 |
void *result = Coroutine_Run(...); | ||||
712 |
// use result | ||||
713 |
Coroutine_Stop(); | ||||
8 months ago |
714 |
||||
8 months ago |
715 |
`Coroutine` can be started & stopped many times. While `Coroutine` is started, `Coroutine_Run()` can be called any number of times. | |||
716 |
|||||
717 |
The total stack allowed for all coroutines running on any thread is the size of the call stack on that thread. | ||||
718 |
|||||
8 months ago |
719 |
##### void Coroutine_StopSystem() | |||
8 months ago |
720 |
||||
8 months ago |
721 |
Stop the coroutine system on this thread. | |||
722 |
|||||
8 months ago |
723 |
##### Coroutine_Start | |||
8 months ago |
724 |
||||
725 |
void *(*)(void *param) | ||||
726 |
|||||
8 months ago |
727 |
The 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 using `Coroutine_GetValue()`. | |||
8 months ago |
728 |
||||
8 months ago |
729 |
##### Coroutine *Coroutine_New(Coroutine_Start start) | |||
8 months ago |
730 |
||||
731 |
Create 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. | ||||
732 |
|||||
8 months ago |
733 |
##### void Coroutine_Run_Coroutine(Coroutine *cor, void *value) | |||
8 months ago |
734 |
||||
735 |
Run 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. | ||||
736 |
|||||
8 months ago |
737 |
##### void *Coroutine_Run(Coroutine_Start start, void *value) | |||
8 months ago |
738 |
||||
739 |
Convenience wrapper for `Coroutine_Run_Coroutine` which creates the `Coroutine` and retrieves its result. | ||||
740 |
|||||
8 months ago |
741 |
##### void Coroutine_Delete(Coroutine *cor) | |||
8 months ago |
742 |
||||
8 months ago |
743 |
Use `Coroutine_Delete()` to delete a coroutine when it is no longer needed. It is an error to attempt to delete a coroutine which is running. | |||
8 months ago |
744 |
||||
8 months ago |
745 |
##### void Coroutine_Continue(Coroutine *cor, void *value, bool early) | |||
8 months ago |
746 |
||||
747 |
Continue 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. | ||||
748 |
|||||
8 months ago |
749 |
##### void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *this) | |||
8 months ago |
750 |
||||
751 |
Yield `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()`. | ||||
752 |
|||||
8 months ago |
753 |
##### void *Coroutine_GetValue(Coroutine *cor) | |||
8 months ago |
754 |
||||
755 |
Return the `Coroutine`'s value - the value last yielded, or returned by its `start` routine. | ||||
756 |
|||||
8 months ago |
757 |
##### Coroutine *Coroutine_GetActive() | |||
8 months ago |
758 |
||||
759 |
Return whihc coroutine is currently running, ie the caller's `Coroutine`. | ||||
760 |
|||||
8 months ago |
761 |
##### bool Coroutine_IsRunning(Coroutine *cor) | |||
8 months ago |
762 |
||||
763 |
Return 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. | ||||
8 months ago |
764 |
||||
8 months ago |
765 |
##### int Coroutine_GetStackHeadroom() | |||
8 months ago |
766 |
||||
8 months ago |
767 |
Return the headroom available in the current coroutine's stack. This can be used to detect when your coroutine is nearing its stack limit, and then use `Coroutine_Chain()` to continue in a new chunk of coroutine stack. | |||
8 months ago |
768 |
||||
8 months ago |
769 |
##### bool Coroutine_HasCoroutinesInFreePool() | |||
8 months ago |
770 |
||||
771 |
Return whether the coroutine system has any coroutine stacks available in its coroutine stack free pool. | ||||
772 |
|||||
8 months ago |
773 |
##### void *Coroutine_GetCStackTop() | |||
8 months ago |
774 |
||||
775 |
Return an address which is near to the top of used C stack. | ||||
776 |
|||||
8 months ago |
777 |
##### void *Coroutine_Chain(Coroutine_Start start, void *value) | |||
8 months ago |
778 |
||||
779 |
Run `start` with `value` on a new coroutine, and return its return value. It is expected that `Coroutine_Chain()` will be used when your coroutine is running short of stack - it is not an alternative to `Coroutine_Run()`. | ||||
780 |