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