2 contributors
856 lines25.8 KB
Newer
Older
-
+
commited
{line.log.rev}
on
9 months ago
1
Stackful coroutines in C.
9 months ago
17
2
9 months ago
3
* `Task` & `Future` coroutines which can pause, waiting for a future.
4
* `ASleep` an example of pausing `Task`s using `Future`s.
9 months ago
5
* `Generator` coroutines used as generators for loops.
9 months ago
6
* `Coroutine` the base coroutine engine.
9 months ago
17
7
9 months ago
8
Your code doesn't need to do anything special to be a coroutine. Only standard, or commonly available libraries are needed.
9 months ago
17
9
9 months ago
10
## Prerequisites
9 months ago
17
11
9 months ago
12
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.
9 months ago
17
13
9 months ago
14
You will need to build & link the code, `coroutine/*.c`, as part of your system, and ensure the headers, `include/*`, are available on your include path.
9 months ago
17
15
9 months ago
16
## Quick Start
9 months ago
17
17
9 months ago
18
### Tasks
9 months ago
17
19
9 months ago
20
To run `Task`s:
9 months ago
17
21
9 months ago
22
#!C
9 months ago
23
#include "coroutine.h"
24
#include "task.h"
9 months ago
25
main(){
9 months ago
26
Coroutine_StartSystem();
9 months ago
27
void *res = NULL;
9 months ago
28
bool canceled = Task_Run(maintask, &param, &res);
29
Coroutine_StopSystem();
9 months ago
30
}
31
9 months ago
32
`Task_Run` runs tasks, switching between them when the current task waits on an `Future`. `maintask()` is run as a task. The start function for any task looks like this:
9 months ago
33
9 months ago
34
#!C
9 months ago
35
bool mytask(void *param, void **res){
36
37
// do your thing here
38
39
return canceled;
40
}
41
42
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.
43
9 months ago
44
Within your main task, create `Task`s and `Task_Await()` them when you want to wait for their result:
9 months ago
45
9 months ago
46
#!C
9 months ago
47
Task task1;
48
Task_ctor(&task1, adifferenttask, &task1param);
9 months ago
49
50
void *result;
9 months ago
51
bool canceled = Task_Await(&task1, &result);
9 months ago
52
9 months ago
53
Task_dtor(&task1);
9 months ago
54
55
// use the result
56
57
When a task needs to wait for something, and wants to allow other tasks to run, it should use a `Future`:
58
9 months ago
59
#!C
9 months ago
60
Future future;
61
Future_ctor(&future);
9 months ago
62
63
// pass the future to the background-thing-which-might-take-a-while
64
9 months ago
65
void *res;
9 months ago
66
bool canceled = Future_Await(&future, &res);
9 months ago
67
9 months ago
68
Future_dtor(&future);
9 months ago
69
70
When the background-thing-which-might-take-a-while has a result:
71
9 months ago
72
#!C
9 months ago
73
Future_SetResult(future, false, result);
9 months ago
74
9 months ago
75
### ASleep
76
77
`ASleep()` needs its own system to be started to work:
78
9 months ago
79
#!C
9 months ago
80
ASleep_StartSystem()
81
Coroutine_StartSystem();
82
// Run tasks here which may now use ASLeep()
83
Coroutine_StopSystem();
84
ASleep_StopSystem();
85
86
Note that `ASleep_StartSystem()` / `ASleep_StopSystem()` is only needed once per process, whereas `Coroutine_StartSystem()` / `Coroutine_StopSystem()` is needed on each thread where coroutines are used.
87
88
Sleeping in a task:
89
9 months ago
90
#!C
9 months ago
91
bool mytask(void *param, void **result){
92
..
93
ASleep(time_to_sleep);
94
..
95
}
96
9 months ago
97
### Generators
98
9 months ago
99
The coroutine system needs to be started:
9 months ago
100
9 months ago
101
#!C
9 months ago
102
Coroutine_StartSYstem();
103
// you can use generators now
104
Coroutine_StopSystem();
105
106
Note that you need to start the coroutine system on each thread you want to use them.
107
9 months ago
108
You will need a generator function:
109
9 months ago
110
#!C
9 months ago
111
void *yield_my_things(void *param){
112
bool domore = true;
113
114
// loop/call functions to find more values to yield, and when you have one:
115
domore = Generator_Yield(thing);
116
// .. if domore is false, exit your generator - it is being destructed
117
118
// not actually used by generators, but this is a useful convention for bubbling
119
// the flag out to calling functions.
120
return (void *)domore;
121
}
122
123
And to use it:
124
9 months ago
125
#!C
9 months ago
126
Generator gen;
127
Generator_ctor(&gen, yield_my_things, "..");
128
void *thing;
129
while(Generator_Next(&gen, &thing)){
130
// use thing - a value yielded by your generator
131
}
132
Generator_dtor(&gen);
133
134
### Coroutines
135
136
While you can use coroutines directly, it's designed as a system to support more useful patterns, like `Async` and `Generators`.
137
138
The Coroutines system must be started:
139
9 months ago
140
#!C
9 months ago
141
Coroutine_StartSystem();
142
// use coroutines here
143
Coroutine_StopSystem();
144
145
Your coroutine will need to have a start function:
146
9 months ago
147
#!C
9 months ago
148
void *start(void *param){
149
...
150
}
151
152
When there is no coroutine running, start your 'main' coroutine:
153
9 months ago
154
#!C
9 months ago
155
void *result = Coroutine_Run(comain, param);
156
157
Create other coroutines like this:
158
9 months ago
159
#!C
9 months ago
160
Coroutine *cor = Coroutine_New(start);
161
162
When you want a Coroutine to run, or to return from a yield:
163
9 months ago
164
#!C
9 months ago
165
Coroutine_Continue(cor, value, run_early);
166
167
`value` will be start function's parameter, or the value returned from the yield.
168
169
Within the Coroutine, to yield a value:
170
9 months ago
171
#!C
9 months ago
172
void *Coroutine_Yield(value, on_yield, void *me);
173
174
The on_yield function is called after the coroutine has been 'wait'ed, but before the next coroutine is resumed.
175
176
## How it Works
177
9 months ago
178
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.
9 months ago
179
9 months ago
180
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.
9 months ago
181
182
## Style
183
9 months ago
184
The style is influenced by C++. For example, where possible, a `Something *Something_New(a, b, c)` and `Something_Delete(Something *)` 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()`.
9 months ago
17
185
9 months ago
186
#!C
9 months ago
17
187
Something *oneofthem = Something_New();
188
// use oneofthem
189
Something_Delete(oneofthem);
190
191
Can be also be done like this, and this will run faster:
192
9 months ago
193
#!C
9 months ago
17
194
Something oneofthem;
195
Something_ctor(&oneofthem);
196
// use oneofthem
197
Something_dtor(&oneofthem);
198
9 months ago
199
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.
200
9 months ago
17
201
## Usage
202
203
When you are using coroutines or generators:
204
9 months ago
205
#!C
9 months ago
17
206
void *myfunc(void *){
207
// your function here
208
}
209
210
Coroutine_StartSystem();
211
Coroutine_Run(myfunc, (void *)myparam);
212
Coroutine_StopSystem();
213
9 months ago
214
While the system is started, you can make many calls to `Coroutine_Run()` or `Task_Run()`, but only one of them can be running at once. A running system is thread local - each thread you want to use coroutines on will need to be `Coroutine_StartSystem()`ed.
9 months ago
17
215
9 months ago
216
## Stack Overruns
9 months ago
17
217
9 months ago
218
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:
9 months ago
17
219
9 months ago
220
* 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...
221
222
* 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...
223
224
* monitor stack headroom, and add another stack chunk if you need to:
225
226
In this last case you'll need to add some code at key points:
227
9 months ago
228
#!C
9 months ago
229
void *myfunction(void *param){
230
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_STACK){
231
return Coroutine_Chain(myfunction, param);
232
}
233
// do everything normally
234
}
235
236
More realistically:
237
9 months ago
238
#!C
9 months ago
239
struct myfunctionparams {
240
int a;
241
char *b;
242
struct dog *d;
243
}
244
245
void *mychain(void *param){
246
struct myfunctionparams *myparams = (struct myfunctionparams *)params;
247
return (void *)myfunction(myparams->a, myparams->b, *myparams->d);
248
}
249
250
int myfunction(int a, char *b, struct dog d){
251
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_STACK){
252
struct myfunctionparams params = {
253
a,
254
b,
255
&d
256
};
257
return (int)Coroutine_Chain(mychain, &params);
258
}
259
}
260
9 months ago
261
And if you want to panic if the C stack overruns:
262
9 months ago
263
#!C
9 months ago
264
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_COROUTINE_STACK){
265
if (Coroutine_HasCoroutinesInFreePool() ||
266
(char *)Coroutine_GetCStackTop() - c_stack_end >= MIN_ALLOWED_C_STACK) {
267
struct myfunctionparams params = {
268
a,
269
b,
270
&d
271
};
272
return (int)Coroutine_Chain(mychain, &params);
273
}
274
// panic now
275
}
276
9 months ago
277
# API
9 months ago
17
278
9 months ago
279
## Task & Future
9 months ago
280
281
The pattern for using async is:
282
9 months ago
283
#!C
9 months ago
284
bool mymaintask(void *param, void **result){
285
// do your main task things here, like starting more tasks
9 months ago
286
}
287
9 months ago
288
Coroutine_StartSystem();
9 months ago
289
void *res = NULL;
9 months ago
290
bool canceled = Task_Run(mymaintask, NULL, &res);
291
Coroutine_StopSystem();
9 months ago
292
9 months ago
293
To create and wait for a task:
9 months ago
294
9 months ago
295
#!C
9 months ago
296
Task task1;
297
Task_ctor(&task1, asynctask1, &task1param);
9 months ago
298
void *res = NULL;
9 months ago
299
bool canceled = Task_Await(&task1, void **res)
300
Task_dtor(&task1);
9 months ago
301
302
or, if you prefer new & delete:
303
9 months ago
304
#!C
9 months ago
305
Task *task1 = Task_New(asynctask1, &task1param);
9 months ago
306
void *res = NULL;
9 months ago
307
bool canceled = Task_Await(task1, void **res)
308
Task_Delete(task1);
9 months ago
309
310
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:
311
9 months ago
312
#!C
9 months ago
313
Future future;
314
Future_ctor(&future);
9 months ago
315
316
// keep &future to hand for when the background thing completes
9 months ago
317
bool canceled = Future_Await(&future, NULL);
9 months ago
318
9 months ago
319
Future_dtor(&future);
9 months ago
320
9 months ago
321
`Future_New()` and `Future_Delete()` are also available if you prefer that style.
9 months ago
322
323
Inside the callback when the background thing is complete:
324
9 months ago
325
#!C
9 months ago
326
// result is a void *
9 months ago
327
Future_SetResult(future, result, false);
9 months ago
328
329
or, if something went wrong:
330
9 months ago
331
#!C
9 months ago
332
// exception is a void *
9 months ago
333
Future_SetResult(future, exception, true);
9 months ago
334
335
Back in the task, you can respond to the future:
336
9 months ago
337
#!C
9 months ago
338
... Future_Await has returned
9 months ago
339
if (canceled){
340
// exit quickly - you've been canceled
341
// you could, for example, use the future's result as an exception, or error code here
342
}
343
// carry on - the future's result may be an actual result, that's up to you
344
345
9 months ago
346
##### void Future_ctor(Future *fut)
9 months ago
347
9 months ago
348
fut
9 months ago
349
: The `Future` being constructed
9 months ago
350
9 months ago
351
Initialise a future. When you no longer need it, use `Future_dtor()`.
9 months ago
352
9 months ago
353
##### Future *Future_New()
9 months ago
354
9 months ago
355
(returns)
9 months ago
356
: The new future
9 months ago
357
9 months ago
358
Allocates and initialises a future, When you no longer need it, use `Future_Delete()`.
9 months ago
359
9 months ago
360
##### void Future_dtor(Future *fut)
9 months ago
361
362
fut
9 months ago
363
: The `Future` being destructed
9 months ago
364
9 months ago
365
Destruct a future previously constructed with `Future_ctor()`.
9 months ago
366
9 months ago
367
##### void Future_Delete(Future *fut)
9 months ago
368
9 months ago
369
fut
9 months ago
370
: The `Future` to be destructed and freed
9 months ago
371
9 months ago
372
Delete (finalise and free) a future previously new'ed with `Future_New()`
9 months ago
373
9 months ago
374
##### void Future_SetResult(Future *fut, bool canceled, void *value)
9 months ago
375
9 months ago
376
fut
9 months ago
377
: The `Future` whose result is being set
9 months ago
378
9 months ago
379
canceled
380
: The future's `canceled` setting
9 months ago
381
9 months ago
382
value
9 months ago
383
: The future's result `value`
9 months ago
384
9 months ago
385
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 `Future` has a result, its watchers are called back.
9 months ago
386
9 months ago
387
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 *`.
9 months ago
388
9 months ago
389
##### bool Future_GetResult(Future *fut, void **res)
9 months ago
390
9 months ago
391
(returns)
9 months ago
392
: The `canceled` value of the `Future`.
9 months ago
393
9 months ago
394
res
9 months ago
395
: Where to store the value of the `Future`. This may be `NULL`.
9 months ago
396
9 months ago
397
Get the result of a future.
9 months ago
398
9 months ago
399
##### typedef void (*Future_Watcher)(void *me, Future *fut)
9 months ago
400
9 months ago
401
A `Future_Watcher` is a callback called when a future has a result. The `me` parameter is the one passed to `Future_AddWatcher()`. `fut` is the future which has just got its result.
9 months ago
402
9 months ago
403
##### void Future_AddWatcher(Future *fut, Future_Watcher watcher, void *me)
9 months ago
404
9 months ago
405
fut
9 months ago
406
: the `Future` to add a watcher to
9 months ago
407
408
watcher
9 months ago
409
: the callback to call when the `Future` has a result.
9 months ago
410
411
me
412
: the `me` value to pass to `watcher` when it is called back.
413
414
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.
415
9 months ago
416
##### void Future_RemoveWatcher(Future *fut, Future_Watcher watcher, void *me)
9 months ago
417
418
fut
9 months ago
419
: the `Future` to remove a watcher from
9 months ago
420
421
watcher
422
: the callback of the watcher to remove.
423
424
me
425
: the `me` value of the watcher to remove.
426
427
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.
428
9 months ago
429
##### bool Future_Await(Future *fut, void **res)
9 months ago
430
431
(returns)
9 months ago
432
: whether the `Future` was canceled.
9 months ago
433
434
fut
9 months ago
435
: The `Future` to wait for.
9 months ago
436
437
res
438
: Where to store the `value` of the future when it is has a result. May be `NULL`.
439
9 months ago
440
The current `Task` is paused until the `Future` has a result. Other `Task`s are run while this one is waiting.
9 months ago
441
9 months ago
442
##### typedef bool (*Task_Entry)(void *param, void **res)
9 months ago
443
9 months ago
444
The entry function to an `Task`.
9 months ago
445
9 months ago
446
##### void Task_ctor(Task *tsk, Task_Entry entry, void *param)
9 months ago
447
448
tsk
449
: The task to construct.
450
451
entry
452
: The entry function for the task.
453
454
param
455
: The value for `param` to pass to `entry`.
456
9 months ago
457
Initialises an `Task`. When you have finished with an `Task` you must finalise it using `Task_dtor()`
9 months ago
458
9 months ago
459
#!C
9 months ago
460
Task tsk;
461
Task_ctor(&tsk, mytask, myparam);
9 months ago
462
// tsk will run if you wait for a task or future
9 months ago
463
Task_Await(&tsk, NULL);
464
Task_dtor(&tsk);
9 months ago
465
9 months ago
466
##### Task *Task_New(Task_Entry entry, void *param)
9 months ago
467
468
(returns)
9 months ago
469
: The new `Task`.
9 months ago
470
471
entry
472
: The entry function for the task.
473
474
param
475
: The value for `param` to pass to `entry`.
476
9 months ago
477
This allocates and initialises a new `Task`. When you have finished with your task, you must `Task_Delete()` it.
9 months ago
478
9 months ago
479
#!C
9 months ago
480
Task *tsk = Task_New(mytask, myparam);
9 months ago
481
// tsk will run if you wait for a task or future
9 months ago
482
Task_Await(tsk, NULL);
483
Task_Delete(tsk);
9 months ago
484
9 months ago
485
##### void Task_dtor(Task *tsk)
9 months ago
486
487
tsk
9 months ago
488
: The `Task` to destruct.
9 months ago
489
9 months ago
490
This finalises an `Task` you ealier initalised with `Task_ctor()`. It is an error to attempt to destruct a task which is running.
9 months ago
491
9 months ago
492
#!C
9 months ago
493
Task tsk;
494
Task_ctor(&tsk, mytask, myparam);
9 months ago
495
// use tsk
9 months ago
496
Task_dtor(&tsk);
9 months ago
497
9 months ago
498
##### void Task_Delete(Task *tsk)
9 months ago
499
500
tsk
9 months ago
501
: The `Task` to delete.
9 months ago
502
9 months ago
503
This finalises and frees an `Task` you ealier new'ed with `Task_New()`. It is an error to attempt to delete a task which is running.
9 months ago
504
9 months ago
505
#!C
9 months ago
506
Task *tsk = Task_New(mytask, myparam);
9 months ago
507
// use tsk
9 months ago
508
Task_Delete(tsk);
9 months ago
509
9 months ago
510
##### static inline bool Task_Await(Task *tsk, void **res)
9 months ago
511
512
(returns)
513
: Whether the task was canceled.
514
515
tsk
9 months ago
516
: The `Task` to wait for.
9 months ago
517
518
res
9 months ago
519
: Where to store the `Task`'s value when it finishes. This may be NULL.
9 months ago
520
9 months ago
521
The current `Task` waits for `tsk` to finish, and returns the result.
9 months ago
522
9 months ago
523
##### void Task_Cancel(Task *tsk, void *cancel_value)
9 months ago
524
525
tsk
526
: The task to cancel.
527
528
cancel_value
529
: The value to set on any future this task waits on.
530
531
This marks a task as canceled. When that task waits on a future that future will be canceled too, using `cancel_value`.
532
9 months ago
533
##### static inline bool Task_IsCanceled(Task *tsk)
9 months ago
534
535
(returns)
536
: Whether the task is canceled.
537
538
tsk
539
: The task to get its canceled setting from.
540
9 months ago
541
##### static inline Future *Task_GetAwaitedFuture(Task *tsk)
9 months ago
542
9 months ago
543
(returns)
544
: The future the task is waiting on. May be NULL.
545
546
tsk
547
: Teh task to read the future it is waiting on.
548
549
Return the future a task is waiting on.
550
9 months ago
551
##### bool Task_Run(Task_Entry start, void *value, void **res)
9 months ago
552
553
(returns)
9 months ago
554
: Whether `start` was canceled.
9 months ago
555
9 months ago
556
start
557
: The function to use as the main task.
9 months ago
558
559
value
9 months ago
560
: The value to pass to `start`.
9 months ago
561
9 months ago
562
res
563
: Where to store the result of `start`.
9 months ago
564
9 months ago
565
Runs `start` as an `Task`. When `start` returns all other tasks must have been destructed, using `Task_dtor()` or `Task_Delete()`.
9 months ago
566
9 months ago
567
## ASleep
9 months ago
568
9 months ago
569
##### void ASleep_StartSystem()
9 months ago
570
9 months ago
571
You must start the `ASleep` system to use it. This needs to happen per process (whereas `Coroutine_StartSystem()` needs to happen per-thread). Once you've finished with `ASleep` you must `ASleep_StopSystem()`.
9 months ago
572
9 months ago
573
#!C
9 months ago
574
ASleep_StartSystem();
575
// Now you can use ASleep() on any thread
576
ASleep_StopSystem();
9 months ago
577
9 months ago
578
##### void ASleep_StopSystem()
9 months ago
579
9 months ago
580
Call this to stop the `ASleep` system.
9 months ago
581
9 months ago
582
##### bool ASleep(float delay, void **value)
9 months ago
583
9 months ago
584
(returns)
585
: Whether the task was canceled.
9 months ago
586
9 months ago
587
delay
588
: How many seconds to delay for.
9 months ago
589
9 months ago
590
value
591
: Where to store the cancellation value. This may be NULL.
9 months ago
592
9 months ago
593
Sleep for `delay` seconds. `*value` will be set to `NULL` if the sleep is successful, and the `cancel_value` if the task is canceled.
9 months ago
594
9 months ago
595
## Generator
9 months ago
17
596
9 months ago
597
The pattern for a `Generator` is:
598
9 months ago
599
#### A loop which uses the `Generator
9 months ago
600
9 months ago
601
#!C
9 months ago
17
602
Generator gen;
9 months ago
603
Generator_ctor(&gen, mygen, &param);
9 months ago
17
604
9 months ago
605
void *value;
606
while(Generator_Next(&gen, &value)){
607
// use value here
9 months ago
17
608
}
9 months ago
609
// value is now the return value from the Generator
610
9 months ago
17
611
Generator_dtor(&gen);
612
9 months ago
613
Or:
9 months ago
17
614
9 months ago
615
#!C
9 months ago
616
Generator *gen = Generator_New(mygen, &param);
9 months ago
17
617
9 months ago
618
void *value;
619
while(Generator_Next(gen, &value)){
620
// use value here
621
}
9 months ago
17
622
9 months ago
623
Generator_Delete(gen);
9 months ago
17
624
9 months ago
625
`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.
9 months ago
17
626
9 months ago
627
#### A generator function
9 months ago
17
628
9 months ago
629
#!C
9 months ago
630
void *mygen(void *param){
631
bool domore = true;
632
// The parameter is a pointer to a string of chars
633
for (char *str = param; *str; ++str) {
634
// The value yielded is a pointer to a character in the string
635
domore = Generator_Yield(str);
636
if (!domore){
637
break;
638
}
639
}
640
641
return (void *)domore;
9 months ago
17
642
}
643
9 months ago
644
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.
9 months ago
17
645
9 months ago
646
##### void Generator_ctor(Generator *gen, void *(*start)(void *), void *param)
9 months ago
17
647
9 months ago
648
gen
649
: The `Generator` to construct.
9 months ago
17
650
9 months ago
651
start
652
: The function which is the start/entry-point of the `Generator`.
653
654
param
655
: The value to pass to `start`.
656
657
Initialise a `Generator`. When you no longer need the `Generator`, use `Generator_dtor()` to destruct it.
658
9 months ago
659
#!C
9 months ago
660
Generator gen;
661
Generator_ctor(&gen, mystart, &params);
9 months ago
17
662
9 months ago
663
// Generator is used
9 months ago
17
664
9 months ago
665
// ... later:
666
Generator_dtor(&gen);
667
9 months ago
668
##### Generator *Generator_New(void *(*start)(void *), void *param)
9 months ago
669
9 months ago
670
start
671
: The function which is the start/entry-point of the `Generator`.
9 months ago
672
9 months ago
673
param
674
: The value to pass to `start`.
675
676
`new` a `Generator` - malloc, and initialise it. When you no longer need the `Generator` use `Generator_dtor` to finalise it.
677
9 months ago
678
#!C
9 months ago
679
Generator *gen = Generator_New(mystart, &params);
680
681
// Generator is used
682
683
// ... later:
684
Generator_Delete(gen);
685
9 months ago
686
##### void Generator_dtor(Generator *gen)
9 months ago
687
9 months ago
688
gen
689
: The `Generator` to destruct.
690
9 months ago
691
Finalise a `Generator`. Once a `Generator` is no longer needed, it must be finalised:
692
9 months ago
693
#!C
9 months ago
694
// earlier...
695
Generator gen;
696
Generator_ctor(&gen, mystart, &params);
697
698
// Generator is used
699
700
// the Generator is no longer needed
701
Generator_dtor(&gen);
702
703
9 months ago
704
##### void Generator_Delete(Generator *gen)
9 months ago
705
9 months ago
706
gen
707
: The `Generator` to delete.
708
9 months ago
709
Finalise then `free()` a `Generator`. Once a `new`ed `Generator` is no longer needed, it must be deleted:
710
9 months ago
711
#!C
9 months ago
712
// earlier...
713
Generator *gen = Generator_New(mystart, &params);
714
715
// Generator is used
716
717
// the Generator is no longer needed
718
Generator_Delete(gen);
719
720
9 months ago
721
##### bool Generator_Next(Generator *gen, void **value)
9 months ago
722
9 months ago
723
(returns)
724
: Whether there is a next value. `true` - there is a next value; `false` - the `Generator` has finished
725
726
gen
727
: The `Generator` to get the next value from.
728
729
value
730
: Where to store the next value.
731
9 months ago
732
Get the next value yielded by the `Generator`.
733
9 months ago
734
#!C
9 months ago
735
void *value;
736
while(Generator_Next(gen, &value)){
737
// use value here
9 months ago
17
738
}
9 months ago
739
9 months ago
740
The `Generator` feeds values to its client using `Generator_Yield()` - it is these values which `Generator_Next()` sets, in the example, `value` to.
9 months ago
741
9 months ago
742
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`.
743
9 months ago
744
##### bool Generator_Yield(void *value)
9 months ago
745
9 months ago
746
(returns)
747
: Whether the `Generator` should do more.
9 months ago
748
9 months ago
749
value
750
: The `Generator`'s next value.
751
752
Yield a value from a `Generator`.
753
9 months ago
754
#!C
9 months ago
755
bool domore = Generator_Yield(value);
756
9 months ago
757
`value` is then provided by `Generator_Next()` as the next value from the generator.
9 months ago
758
9 months ago
759
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.
760
9 months ago
761
## Coroutine
762
9 months ago
763
##### void Coroutine_StartSystem()
9 months ago
764
9 months ago
765
Start the coroutine system on this thread. When you've finished with `Coroutine` must call `Coroutine_Stop()`.
9 months ago
766
9 months ago
767
#!C
9 months ago
768
Coroutine_Start();
769
// prepare
770
void *result = Coroutine_Run(...);
771
// use result
772
Coroutine_Stop();
9 months ago
773
9 months ago
774
`Coroutine` can be started & stopped many times. While `Coroutine` is started, `Coroutine_Run()` can be called any number of times.
775
776
The total stack allowed for all coroutines running on any thread is the size of the call stack on that thread.
777
9 months ago
778
##### Coroutine_Report Coroutine_StopSystem()
9 months ago
779
9 months ago
780
Stop the coroutine system on this thread. A `Coroutine_Report` is returned, which summarises the coroutine activity on this thread:
9 months ago
781
9 months ago
782
#!C
9 months ago
783
typedef struct Coroutine_Report {
784
unsigned coroutines_created;
785
unsigned coroutines_pool_size;
786
unsigned lowest_headroom;
787
} Coroutine_Report;
788
789
coroutines_created
790
: How many coroutines were created
791
792
coroutines_pool_size
793
: The size of the coroutine pool (count of available, free `Coroutine` objects) when the system stopped. This is also the peak number of active coroutines. This will give you an idea of how much stack was needed for your coroutines.
794
795
lowest_headroom
796
: The lowest headroom (unused bytes before the guard word) any of the coroutine had.
797
9 months ago
798
##### Coroutine_Start
9 months ago
799
9 months ago
800
#!C
9 months ago
801
void *(*)(void *param)
802
9 months ago
803
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()`.
9 months ago
804
9 months ago
805
##### Coroutine *Coroutine_New(Coroutine_Start start)
9 months ago
806
807
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.
808
9 months ago
809
##### void Coroutine_Run_Coroutine(Coroutine *cor, void *value)
9 months ago
810
811
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.
812
9 months ago
813
##### void *Coroutine_Run(Coroutine_Start start, void *value)
9 months ago
814
815
Convenience wrapper for `Coroutine_Run_Coroutine` which creates the `Coroutine` and retrieves its result.
816
9 months ago
817
##### void Coroutine_Delete(Coroutine *cor)
9 months ago
818
9 months ago
819
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.
9 months ago
820
9 months ago
821
##### void Coroutine_Continue(Coroutine *cor, void *value, bool early)
9 months ago
822
823
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.
824
9 months ago
825
##### void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *this)
9 months ago
826
827
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()`.
828
9 months ago
829
##### void *Coroutine_GetValue(Coroutine *cor)
9 months ago
830
831
Return the `Coroutine`'s value - the value last yielded, or returned by its `start` routine.
832
9 months ago
833
##### Coroutine *Coroutine_GetActive()
9 months ago
834
835
Return whihc coroutine is currently running, ie the caller's `Coroutine`.
836
9 months ago
837
##### bool Coroutine_IsRunning(Coroutine *cor)
9 months ago
838
839
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.
9 months ago
840
9 months ago
841
##### int Coroutine_GetStackHeadroom()
9 months ago
842
9 months ago
843
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.
9 months ago
844
9 months ago
845
##### bool Coroutine_HasCoroutinesInFreePool()
9 months ago
846
847
Return whether the coroutine system has any coroutine stacks available in its coroutine stack free pool.
848
9 months ago
849
##### void *Coroutine_GetCStackTop()
9 months ago
850
851
Return an address which is near to the top of used C stack.
852
9 months ago
853
##### void *Coroutine_Chain(Coroutine_Start start, void *value)
9 months ago
854
855
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()`.
856