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