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