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