936 lines29.1 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){
6 months ago
251
void *result;
252
bool fail = Coroutine_Chain(myfunction, param, &result);
253
if (fail){
254
// handle failure
255
}
256
return result;
9 months ago
257
}
258
// do everything normally
259
}
260
261
More realistically:
262
9 months ago
263
#!C
9 months ago
264
struct myfunctionparams {
265
int a;
266
char *b;
267
struct dog *d;
268
}
269
270
void *mychain(void *param){
271
struct myfunctionparams *myparams = (struct myfunctionparams *)params;
272
return (void *)myfunction(myparams->a, myparams->b, *myparams->d);
273
}
274
275
int myfunction(int a, char *b, struct dog d){
276
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_STACK){
277
struct myfunctionparams params = {
278
a,
279
b,
280
&d
281
};
6 months ago
282
void *result;
283
bool fail = Coroutine_Chain(mychain, &params, &result);
284
if (fail){
285
// handle failure
286
}
287
return (int)(intptr_t)result;
9 months ago
288
}
289
}
290
9 months ago
291
And if you want to panic if the C stack overruns:
292
9 months ago
293
#!C
9 months ago
294
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_COROUTINE_STACK){
295
if (Coroutine_HasCoroutinesInFreePool() ||
296
(char *)Coroutine_GetCStackTop() - c_stack_end >= MIN_ALLOWED_C_STACK) {
297
struct myfunctionparams params = {
298
a,
299
b,
300
&d
301
};
6 months ago
302
void *result;
303
if (Coroutine_Chain(mychain, &params, &result)){
304
// handle failure
305
}
306
return (int)(intptr_t)result;
9 months ago
307
}
308
// panic now
309
}
310
9 months ago
311
# API
9 months ago
17
312
9 months ago
313
## Task & Future
9 months ago
314
315
The pattern for using async is:
316
9 months ago
317
#!C
9 months ago
318
bool mymaintask(void *param, void **result){
319
// do your main task things here, like starting more tasks
9 months ago
320
}
321
9 months ago
322
Coroutine_StartSystem();
9 months ago
323
void *res = NULL;
9 months ago
324
bool canceled = Task_Run(mymaintask, NULL, &res);
325
Coroutine_StopSystem();
9 months ago
326
9 months ago
327
To create and wait for a task:
9 months ago
328
9 months ago
329
#!C
9 months ago
330
Task task1;
331
Task_ctor(&task1, asynctask1, &task1param);
9 months ago
332
void *res = NULL;
9 months ago
333
bool canceled = Task_Await(&task1, void **res)
334
Task_dtor(&task1);
9 months ago
335
336
or, if you prefer new & delete:
337
9 months ago
338
#!C
9 months ago
339
Task *task1 = Task_New(asynctask1, &task1param);
9 months ago
340
void *res = NULL;
9 months ago
341
bool canceled = Task_Await(task1, void **res)
342
Task_Delete(task1);
9 months ago
343
344
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:
345
9 months ago
346
#!C
9 months ago
347
Future future;
348
Future_ctor(&future);
9 months ago
349
350
// keep &future to hand for when the background thing completes
9 months ago
351
bool canceled = Future_Await(&future, NULL);
9 months ago
352
9 months ago
353
Future_dtor(&future);
9 months ago
354
9 months ago
355
`Future_New()` and `Future_Delete()` are also available if you prefer that style.
9 months ago
356
357
Inside the callback when the background thing is complete:
358
9 months ago
359
#!C
9 months ago
360
// result is a void *
9 months ago
361
Future_SetResult(future, result, false);
9 months ago
362
363
or, if something went wrong:
364
9 months ago
365
#!C
9 months ago
366
// exception is a void *
9 months ago
367
Future_SetResult(future, exception, true);
9 months ago
368
369
Back in the task, you can respond to the future:
370
9 months ago
371
#!C
9 months ago
372
... Future_Await has returned
9 months ago
373
if (canceled){
374
// exit quickly - you've been canceled
375
// you could, for example, use the future's result as an exception, or error code here
376
}
377
// carry on - the future's result may be an actual result, that's up to you
378
379
9 months ago
380
##### void Future_ctor(Future *fut)
9 months ago
381
9 months ago
382
fut
9 months ago
383
: The `Future` being constructed
9 months ago
384
9 months ago
385
Initialise a future. When you no longer need it, use `Future_dtor()`.
9 months ago
386
9 months ago
387
##### Future *Future_New()
9 months ago
388
9 months ago
389
(returns)
9 months ago
390
: The new future
9 months ago
391
9 months ago
392
Allocates and initialises a future, When you no longer need it, use `Future_Delete()`.
9 months ago
393
9 months ago
394
##### void Future_dtor(Future *fut)
9 months ago
395
396
fut
9 months ago
397
: The `Future` being destructed
9 months ago
398
9 months ago
399
Destruct a future previously constructed with `Future_ctor()`.
9 months ago
400
9 months ago
401
##### void Future_Delete(Future *fut)
9 months ago
402
9 months ago
403
fut
9 months ago
404
: The `Future` to be destructed and freed
9 months ago
405
9 months ago
406
Delete (finalise and free) a future previously new'ed with `Future_New()`
9 months ago
407
9 months ago
408
##### void Future_SetResult(Future *fut, bool canceled, void *value)
9 months ago
409
9 months ago
410
fut
9 months ago
411
: The `Future` whose result is being set
9 months ago
412
9 months ago
413
canceled
414
: The future's `canceled` setting
9 months ago
415
9 months ago
416
value
9 months ago
417
: The future's result `value`
9 months ago
418
9 months ago
419
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
420
9 months ago
421
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
422
9 months ago
423
##### bool Future_GetResult(Future *fut, void **res)
9 months ago
424
9 months ago
425
(returns)
9 months ago
426
: The `canceled` value of the `Future`.
9 months ago
427
9 months ago
428
res
9 months ago
429
: Where to store the value of the `Future`. This may be `NULL`.
9 months ago
430
9 months ago
431
Get the result of a future.
9 months ago
432
9 months ago
433
##### typedef void (*Future_Watcher)(void *me, Future *fut)
9 months ago
434
9 months ago
435
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
436
9 months ago
437
##### void Future_AddWatcher(Future *fut, Future_Watcher watcher, void *me)
9 months ago
438
9 months ago
439
fut
9 months ago
440
: the `Future` to add a watcher to
9 months ago
441
442
watcher
9 months ago
443
: the callback to call when the `Future` has a result.
9 months ago
444
445
me
446
: the `me` value to pass to `watcher` when it is called back.
447
448
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.
449
9 months ago
450
##### void Future_RemoveWatcher(Future *fut, Future_Watcher watcher, void *me)
9 months ago
451
452
fut
9 months ago
453
: the `Future` to remove a watcher from
9 months ago
454
455
watcher
456
: the callback of the watcher to remove.
457
458
me
459
: the `me` value of the watcher to remove.
460
461
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.
462
9 months ago
463
##### bool Future_Await(Future *fut, void **res)
9 months ago
464
465
(returns)
9 months ago
466
: whether the `Future` was canceled.
9 months ago
467
468
fut
9 months ago
469
: The `Future` to wait for.
9 months ago
470
471
res
472
: Where to store the `value` of the future when it is has a result. May be `NULL`.
473
9 months ago
474
The current `Task` is paused until the `Future` has a result. Other `Task`s are run while this one is waiting.
9 months ago
475
9 months ago
476
##### typedef bool (*Task_Entry)(void *param, void **res)
9 months ago
477
9 months ago
478
The entry function to an `Task`.
9 months ago
479
9 months ago
480
##### void Task_ctor(Task *tsk, Task_Entry entry, void *param)
9 months ago
481
482
tsk
483
: The task to construct.
484
485
entry
486
: The entry function for the task.
487
488
param
489
: The value for `param` to pass to `entry`.
490
9 months ago
491
Initialises an `Task`. When you have finished with an `Task` you must finalise it using `Task_dtor()`
9 months ago
492
9 months ago
493
#!C
9 months ago
494
Task tsk;
495
Task_ctor(&tsk, mytask, myparam);
9 months ago
496
// tsk will run if you wait for a task or future
9 months ago
497
Task_Await(&tsk, NULL);
498
Task_dtor(&tsk);
9 months ago
499
9 months ago
500
##### Task *Task_New(Task_Entry entry, void *param)
9 months ago
501
502
(returns)
9 months ago
503
: The new `Task`.
9 months ago
504
505
entry
506
: The entry function for the task.
507
508
param
509
: The value for `param` to pass to `entry`.
510
9 months ago
511
This allocates and initialises a new `Task`. When you have finished with your task, you must `Task_Delete()` it.
9 months ago
512
9 months ago
513
#!C
9 months ago
514
Task *tsk = Task_New(mytask, myparam);
9 months ago
515
// tsk will run if you wait for a task or future
9 months ago
516
Task_Await(tsk, NULL);
517
Task_Delete(tsk);
9 months ago
518
9 months ago
519
##### void Task_dtor(Task *tsk)
9 months ago
520
521
tsk
9 months ago
522
: The `Task` to destruct.
9 months ago
523
9 months ago
524
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
525
9 months ago
526
#!C
9 months ago
527
Task tsk;
528
Task_ctor(&tsk, mytask, myparam);
9 months ago
529
// use tsk
9 months ago
530
Task_dtor(&tsk);
9 months ago
531
9 months ago
532
##### void Task_Delete(Task *tsk)
9 months ago
533
534
tsk
9 months ago
535
: The `Task` to delete.
9 months ago
536
9 months ago
537
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
538
9 months ago
539
#!C
9 months ago
540
Task *tsk = Task_New(mytask, myparam);
9 months ago
541
// use tsk
9 months ago
542
Task_Delete(tsk);
9 months ago
543
9 months ago
544
##### static inline bool Task_Await(Task *tsk, void **res)
9 months ago
545
546
(returns)
547
: Whether the task was canceled.
548
549
tsk
9 months ago
550
: The `Task` to wait for.
9 months ago
551
552
res
9 months ago
553
: Where to store the `Task`'s value when it finishes. This may be NULL.
9 months ago
554
9 months ago
555
The current `Task` waits for `tsk` to finish, and returns the result.
9 months ago
556
9 months ago
557
##### void Task_Cancel(Task *tsk, void *cancel_value)
9 months ago
558
559
tsk
560
: The task to cancel.
561
562
cancel_value
563
: The value to set on any future this task waits on.
564
565
This marks a task as canceled. When that task waits on a future that future will be canceled too, using `cancel_value`.
566
9 months ago
567
##### static inline bool Task_IsCanceled(Task *tsk)
9 months ago
568
569
(returns)
570
: Whether the task is canceled.
571
572
tsk
573
: The task to get its canceled setting from.
574
9 months ago
575
##### static inline Future *Task_GetAwaitedFuture(Task *tsk)
9 months ago
576
9 months ago
577
(returns)
578
: The future the task is waiting on. May be NULL.
579
580
tsk
581
: Teh task to read the future it is waiting on.
582
583
Return the future a task is waiting on.
584
9 months ago
585
##### bool Task_Run(Task_Entry start, void *value, void **res)
9 months ago
586
587
(returns)
9 months ago
588
: Whether `start` was canceled.
9 months ago
589
9 months ago
590
start
591
: The function to use as the main task.
9 months ago
592
593
value
9 months ago
594
: The value to pass to `start`.
9 months ago
595
9 months ago
596
res
597
: Where to store the result of `start`.
9 months ago
598
9 months ago
599
Runs `start` as an `Task`. When `start` returns all other tasks must have been destructed, using `Task_dtor()` or `Task_Delete()`.
9 months ago
600
9 months ago
601
## ASleep
9 months ago
602
9 months ago
603
##### void ASleep_StartSystem()
9 months ago
604
9 months ago
605
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
606
9 months ago
607
#!C
9 months ago
608
ASleep_StartSystem();
609
// Now you can use ASleep() on any thread
610
ASleep_StopSystem();
9 months ago
611
9 months ago
612
##### void ASleep_StopSystem()
9 months ago
613
9 months ago
614
Call this to stop the `ASleep` system.
9 months ago
615
9 months ago
616
##### bool ASleep(float delay, void **value)
9 months ago
617
9 months ago
618
(returns)
619
: Whether the task was canceled.
9 months ago
620
9 months ago
621
delay
622
: How many seconds to delay for.
9 months ago
623
9 months ago
624
value
625
: Where to store the cancellation value. This may be NULL.
9 months ago
626
9 months ago
627
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
628
9 months ago
629
## Generator
9 months ago
17
630
9 months ago
631
The pattern for a `Generator` is:
632
9 months ago
633
#### A loop which uses the `Generator
9 months ago
634
9 months ago
635
#!C
9 months ago
17
636
Generator gen;
9 months ago
637
Generator_ctor(&gen, mygen, &param);
9 months ago
17
638
9 months ago
639
void *value;
640
while(Generator_Next(&gen, &value)){
641
// use value here
9 months ago
17
642
}
9 months ago
643
// value is now the return value from the Generator
644
9 months ago
17
645
Generator_dtor(&gen);
646
9 months ago
647
Or:
9 months ago
17
648
9 months ago
649
#!C
9 months ago
650
Generator *gen = Generator_New(mygen, &param);
9 months ago
17
651
9 months ago
652
void *value;
653
while(Generator_Next(gen, &value)){
654
// use value here
655
}
9 months ago
17
656
9 months ago
657
Generator_Delete(gen);
9 months ago
17
658
9 months ago
659
`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
660
9 months ago
661
#### A generator function
9 months ago
17
662
9 months ago
663
#!C
9 months ago
664
void *mygen(void *param){
665
bool domore = true;
666
// The parameter is a pointer to a string of chars
667
for (char *str = param; *str; ++str) {
668
// The value yielded is a pointer to a character in the string
669
domore = Generator_Yield(str);
670
if (!domore){
671
break;
672
}
673
}
674
675
return (void *)domore;
9 months ago
17
676
}
677
9 months ago
678
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
679
9 months ago
680
##### void Generator_ctor(Generator *gen, void *(*start)(void *), void *param)
9 months ago
17
681
9 months ago
682
gen
683
: The `Generator` to construct.
9 months ago
17
684
9 months ago
685
start
686
: The function which is the start/entry-point of the `Generator`.
687
688
param
689
: The value to pass to `start`.
690
691
Initialise a `Generator`. When you no longer need the `Generator`, use `Generator_dtor()` to destruct it.
692
9 months ago
693
#!C
9 months ago
694
Generator gen;
695
Generator_ctor(&gen, mystart, &params);
9 months ago
17
696
9 months ago
697
// Generator is used
9 months ago
17
698
9 months ago
699
// ... later:
700
Generator_dtor(&gen);
701
9 months ago
702
##### Generator *Generator_New(void *(*start)(void *), void *param)
9 months ago
703
9 months ago
704
start
705
: The function which is the start/entry-point of the `Generator`.
9 months ago
706
9 months ago
707
param
708
: The value to pass to `start`.
709
710
`new` a `Generator` - malloc, and initialise it. When you no longer need the `Generator` use `Generator_dtor` to finalise it.
711
9 months ago
712
#!C
9 months ago
713
Generator *gen = Generator_New(mystart, &params);
714
715
// Generator is used
716
717
// ... later:
718
Generator_Delete(gen);
719
9 months ago
720
##### void Generator_dtor(Generator *gen)
9 months ago
721
9 months ago
722
gen
723
: The `Generator` to destruct.
724
9 months ago
725
Finalise a `Generator`. Once a `Generator` is no longer needed, it must be finalised:
726
9 months ago
727
#!C
9 months ago
728
// earlier...
729
Generator gen;
730
Generator_ctor(&gen, mystart, &params);
731
732
// Generator is used
733
734
// the Generator is no longer needed
735
Generator_dtor(&gen);
736
737
9 months ago
738
##### void Generator_Delete(Generator *gen)
9 months ago
739
9 months ago
740
gen
741
: The `Generator` to delete.
742
9 months ago
743
Finalise then `free()` a `Generator`. Once a `new`ed `Generator` is no longer needed, it must be deleted:
744
9 months ago
745
#!C
9 months ago
746
// earlier...
747
Generator *gen = Generator_New(mystart, &params);
748
749
// Generator is used
750
751
// the Generator is no longer needed
752
Generator_Delete(gen);
753
754
9 months ago
755
##### bool Generator_Next(Generator *gen, void **value)
9 months ago
756
9 months ago
757
(returns)
758
: Whether there is a next value. `true` - there is a next value; `false` - the `Generator` has finished
759
760
gen
761
: The `Generator` to get the next value from.
762
763
value
764
: Where to store the next value.
765
9 months ago
766
Get the next value yielded by the `Generator`.
767
9 months ago
768
#!C
9 months ago
769
void *value;
770
while(Generator_Next(gen, &value)){
771
// use value here
9 months ago
17
772
}
9 months ago
773
9 months ago
774
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
775
9 months ago
776
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`.
777
9 months ago
778
##### bool Generator_Yield(void *value)
9 months ago
779
9 months ago
780
(returns)
781
: Whether the `Generator` should do more.
9 months ago
782
9 months ago
783
value
784
: The `Generator`'s next value.
785
786
Yield a value from a `Generator`.
787
9 months ago
788
#!C
9 months ago
789
bool domore = Generator_Yield(value);
790
9 months ago
791
`value` is then provided by `Generator_Next()` as the next value from the generator.
9 months ago
792
9 months ago
793
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.
794
9 months ago
795
## Coroutine
796
9 months ago
797
##### void Coroutine_StartSystem()
9 months ago
798
7 months ago
799
Start the coroutine system on this thread. When you've finished with `Coroutine` must call `Coroutine_StopSystem()`.
9 months ago
800
9 months ago
801
#!C
7 months ago
802
Coroutine_StartSystem();
9 months ago
803
// prepare
6 months ago
804
void *result;
805
if (Coroutine_Run(..., &result)){
806
// handle the failure
807
}
9 months ago
808
// use result
7 months ago
809
Coroutine_StopSystem();
9 months ago
810
7 months ago
811
`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
812
813
The total stack allowed for all coroutines running on any thread is the size of the call stack on that thread.
814
7 months ago
815
##### Coroutine_SetStackLimit(void *limit)
816
817
Set the limit of the stack. This is used to determine more accurately whether `Coroutine_CanStartCoroutine()`
818
9 months ago
819
##### Coroutine_Report Coroutine_StopSystem()
9 months ago
820
9 months ago
821
Stop the coroutine system on this thread. A `Coroutine_Report` is returned, which summarises the coroutine activity on this thread:
9 months ago
822
9 months ago
823
#!C
9 months ago
824
typedef struct Coroutine_Report {
825
unsigned coroutines_created;
826
unsigned coroutines_pool_size;
827
unsigned lowest_headroom;
828
} Coroutine_Report;
829
830
coroutines_created
831
: How many coroutines were created
832
833
coroutines_pool_size
834
: 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.
835
836
lowest_headroom
837
: The lowest headroom (unused bytes before the guard word) any of the coroutine had.
838
9 months ago
839
##### Coroutine_Start
9 months ago
840
9 months ago
841
#!C
9 months ago
842
void *(*)(void *param)
843
9 months ago
844
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
845
9 months ago
846
##### Coroutine *Coroutine_New(Coroutine_Start start)
9 months ago
847
6 months ago
848
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
849
9 months ago
850
##### void Coroutine_Run_Coroutine(Coroutine *cor, void *value)
9 months ago
851
7 months ago
852
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
853
6 months ago
854
##### bool Coroutine_Run(Coroutine_Start start, void *value, void **result)
9 months ago
855
6 months ago
856
`start(value)` is called from within a coroutine and its value returned in `*result`.
857
If this completes without any failure, `false` is returned, otherwise, typically
858
because `Coroutine_New()` returned `NULL`, `true` is returned. `result` may be `NULL` if you don't
859
need the resturn value from `start()`.
860
When the coroutine system is active - you are already running in a coroutine - `start(value)`
861
is simply called and its result returned in `*result`. When the Coroutine system is not running,
862
`Coroutine_Run()` starts it, creates a `Coroutine` and runs that Coroutine to call `start(calue)`
863
and return value is returned in `*result`, then stops the Coroutine system.
9 months ago
864
9 months ago
865
##### void Coroutine_Delete(Coroutine *cor)
9 months ago
866
9 months ago
867
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
868
9 months ago
869
##### void Coroutine_Continue(Coroutine *cor, void *value, bool early)
9 months ago
870
871
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.
872
9 months ago
873
##### void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *this)
9 months ago
874
875
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()`.
876
9 months ago
877
##### void *Coroutine_GetValue(Coroutine *cor)
9 months ago
878
879
Return the `Coroutine`'s value - the value last yielded, or returned by its `start` routine.
880
9 months ago
881
##### Coroutine *Coroutine_GetActive()
9 months ago
882
883
Return whihc coroutine is currently running, ie the caller's `Coroutine`.
884
9 months ago
885
##### bool Coroutine_IsRunning(Coroutine *cor)
9 months ago
886
887
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
888
7 months ago
889
##### bool Coroutine_IsComplete(Coroutine *cor)
890
891
Return whether the given coroutine is complete - is has returned from its `start` function.
892
8 months ago
893
##### intptr_t Coroutine_GetStackHeadroom()
9 months ago
894
9 months ago
895
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
896
7 months ago
897
##### bool Coroutine_CanStartCoroutine()
9 months ago
898
7 months ago
899
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
900
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
901
stack use `Coroutine_SetStackLimit()`
9 months ago
902
7 months ago
903
##### void *Coroutine_GetStackHWM(void)
904
905
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:
906
907
#!C
908
Coroutine_ClearStackForHWM();
909
char *before = (char *)Coroutine_GetStackHWM();
910
// do the thing you want to measure here
911
char *after = (char *)Coroutine_GetStackHWM();
912
intptr_t amount_used = before - after;
913
914
##### void Coroutine_ClearStackForHWM(void)
915
916
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:
917
918
#!C
919
Coroutine_ClearStackForHWM();
920
char *before = (char *)Coroutine_GetStackHWM();
921
// do the thing you want to measure here
922
char *after = (char *)Coroutine_GetStackHWM();
923
intptr_t amount_used = before - after;
924
9 months ago
925
##### void *Coroutine_GetCStackTop()
9 months ago
926
927
Return an address which is near to the top of used C stack.
928
6 months ago
929
##### bool Coroutine_Chain(Coroutine_Start start, void *value, void *result)
9 months ago
930
6 months ago
931
Run `start` with `value` on a new coroutine, and return its return value in `*result`. `result`
932
may be NULL. `Coroutine_Run()` returns `false` if nothing fails, and `true` if something went wrong,
933
usually when `Coroutine_New()` ran out of stack.
934
It is expected that `Coroutine_Chain()` will be used when your coroutine is running short
935
of stack - it is not an alternative to `Coroutine_Run()`.
936