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