1127 lines33.9 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(){
5 months ago
28
size_t mytask_stack_size = 8192 * sizeof(void *);
8 months ago
29
void *res = NULL;
5 months ago
30
bool canceled = Task_Run(mytask_stack_size, maintask, &param, &res);
8 months ago
31
}
32
8 months ago
33
`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
34
8 months ago
35
#!C
8 months ago
36
bool mytask(void *param, void **res){
37
38
// do your thing here
39
40
return canceled;
41
}
42
43
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.
44
8 months ago
45
Within your main task, create `Task`s and `Task_Await()` them when you want to wait for their result:
8 months ago
46
8 months ago
47
#!C
8 months ago
48
Task task1;
5 months ago
49
Task_ctor(&task1, mytask_stack_size, adifferenttask, &task1param);
8 months ago
50
51
void *result;
8 months ago
52
bool canceled = Task_Await(&task1, &result);
8 months ago
53
8 months ago
54
Task_dtor(&task1);
8 months ago
55
56
// use the result
57
58
When a task needs to wait for something, and wants to allow other tasks to run, it should use a `Future`:
59
8 months ago
60
#!C
8 months ago
61
Future future;
62
Future_ctor(&future);
8 months ago
63
64
// pass the future to the background-thing-which-might-take-a-while
65
8 months ago
66
void *res;
8 months ago
67
bool canceled = Future_Await(&future, &res);
8 months ago
68
8 months ago
69
Future_dtor(&future);
8 months ago
70
71
When the background-thing-which-might-take-a-while has a result:
72
8 months ago
73
#!C
8 months ago
74
Future_SetResult(future, false, result);
8 months ago
75
8 months ago
76
### ASleep
77
78
`ASleep()` needs its own system to be started to work:
79
8 months ago
80
#!C
8 months ago
81
ASleep_StartSystem()
82
// Run tasks here which may now use ASLeep()
83
ASleep_StopSystem();
84
3 months ago
85
Note that `ASleep_StartSystem()` / `ASleep_StopSystem()` is only needed once per process.
8 months ago
86
87
Sleeping in a task:
88
8 months ago
89
#!C
8 months ago
90
bool mytask(void *param, void **result){
91
..
92
ASleep(time_to_sleep);
93
..
94
}
95
8 months ago
96
### Generators
97
3 months ago
98
Your code needs to be in a `Coroutine` to use a `Generator`:
8 months ago
99
8 months ago
100
#!C
3 months ago
101
void *mycoroutine(void *param){
102
// You can use a Generator here
6 months ago
103
}
3 months ago
104
105
Coroutine_Run(StackSpace, mycoroutine, NULL, NULL);
6 months ago
106
8 months ago
107
You will need a generator function:
108
8 months ago
109
#!C
8 months ago
110
void *yield_my_things(void *param){
111
bool domore = true;
112
113
// loop/call functions to find more values to yield, and when you have one:
114
domore = Generator_Yield(thing);
115
// .. if domore is false, exit your generator - it is being destructed
116
117
// not actually used by generators, but this is a useful convention for bubbling
118
// the flag out to calling functions.
119
return (void *)domore;
120
}
121
122
And to use it:
123
8 months ago
124
#!C
8 months ago
125
Generator gen;
5 months ago
126
Generator_ctor(&gen, generator_stack_size, yield_my_things, "..");
8 months ago
127
void *thing;
128
while(Generator_Next(&gen, &thing)){
129
// use thing - a value yielded by your generator
130
}
131
Generator_dtor(&gen);
132
133
### Coroutines
134
135
While you can use coroutines directly, it's designed as a system to support more useful patterns, like `Async` and `Generators`.
136
137
Your coroutine will need to have a start function:
138
8 months ago
139
#!C
8 months ago
140
void *start(void *param){
141
...
142
}
143
144
When there is no coroutine running, start your 'main' coroutine:
145
8 months ago
146
#!C
5 months ago
147
if (Coroutine_Run(coroutine_stack_size, comain, param, &result)){
5 months ago
148
// handle the failure
149
}
8 months ago
150
151
Create other coroutines like this:
152
8 months ago
153
#!C
8 months ago
154
Coroutine *cor = Coroutine_New(start);
155
3 months ago
156
When you want a Coroutine to be queued to run:
8 months ago
157
8 months ago
158
#!C
8 months ago
159
Coroutine_Continue(cor, value, run_early);
160
161
`value` will be start function's parameter, or the value returned from the yield.
162
3 months ago
163
Within the Coroutine, to yield a value, and allow other `Coroutine`s to run:
8 months ago
164
8 months ago
165
#!C
8 months ago
166
void *Coroutine_Yield(value, on_yield, void *me);
167
168
The on_yield function is called after the coroutine has been 'wait'ed, but before the next coroutine is resumed.
169
170
## How it Works
171
5 months ago
172
The coroutine system uses the stack divided into smaller stacks for the coroutines. This means you may need to consider whether each coroutine's stack size, is right for each Coroutine, and whether your C stack size is enough for the number of coroutines you might run.
8 months ago
173
8 months ago
174
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
175
176
## Style
177
8 months ago
178
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
179
8 months ago
180
#!C
8 months ago
17
181
Something *oneofthem = Something_New();
182
// use oneofthem
183
Something_Delete(oneofthem);
184
185
Can be also be done like this, and this will run faster:
186
8 months ago
187
#!C
8 months ago
17
188
Something oneofthem;
189
Something_ctor(&oneofthem);
190
// use oneofthem
191
Something_dtor(&oneofthem);
192
8 months ago
193
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.
194
8 months ago
17
195
## Usage
196
197
When you are using coroutines or generators:
198
8 months ago
199
#!C
8 months ago
17
200
void *myfunc(void *){
201
// your function here
202
}
203
5 months ago
204
size_t coroutine_stack_size = 8192 * sizeov(void *);
205
if (Coroutine_Run(coroutine_stack_size, myfunc, (void *)myparam, NULL)){
5 months ago
206
// handle the failure
207
}
8 months ago
17
208
5 months ago
209
You can make many calls to `Coroutine_Run()` or `Task_Run()`. `Coroutine_Run()` ensures the system is started, and that `myfunc` is called
210
from inside a Coroutine. In paeticular, if the Coroutine system is running and `Coroutine_Run()` is called from inside a coroutine, then `myfunc` is simply called.
8 months ago
17
211
8 months ago
212
## Stack Overruns
8 months ago
17
213
3 months ago
214
The C stack is divided into smaller stacks. There's one, the startup stack, to give some room for `start` in `Coroutine_RunSystem` to work, and then each `Coroutine` has its own stack. 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
215
3 months ago
216
* Use less stack. This is, sometimes, the right advice, especially if the startup stack overruns. The expectation is that very little is done by `start` in `Coroutine_RunSystem`. If your situation needs more doing, you can...
8 months ago
217
3 months ago
218
* increase the stack size for your `Coroutine`. 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...
8 months ago
219
220
* monitor stack headroom, and add another stack chunk if you need to:
221
222
In this last case you'll need to add some code at key points:
223
8 months ago
224
#!C
8 months ago
225
void *myfunction(void *param){
226
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_STACK){
5 months ago
227
void *result;
3 months ago
228
Coroutine_Err err = Coroutine_Chain(my_stack_size, myfunction, param, &result);
229
if (err){
5 months ago
230
// handle failure
231
}
232
return result;
8 months ago
233
}
234
// do everything normally
235
}
236
237
More realistically:
238
8 months ago
239
#!C
8 months ago
240
struct myfunctionparams {
241
int a;
242
char *b;
243
struct dog *d;
244
}
245
246
void *mychain(void *param){
247
struct myfunctionparams *myparams = (struct myfunctionparams *)params;
248
return (void *)myfunction(myparams->a, myparams->b, *myparams->d);
249
}
250
251
int myfunction(int a, char *b, struct dog d){
252
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_STACK){
253
struct myfunctionparams params = {
254
a,
255
b,
256
&d
257
};
5 months ago
258
void *result;
3 months ago
259
Coroutine_Err err = Coroutine_Chain(my_stack_size, mychain, &params, &result);
260
if (err){
5 months ago
261
// handle failure
262
}
263
return (int)(intptr_t)result;
8 months ago
264
}
265
}
266
8 months ago
267
And if you want to panic if the C stack overruns:
268
8 months ago
269
#!C
8 months ago
270
if (Coroutine_GetStackHeadroom() < MIN_ALLOWED_COROUTINE_STACK){
271
if (Coroutine_HasCoroutinesInFreePool() ||
272
(char *)Coroutine_GetCStackTop() - c_stack_end >= MIN_ALLOWED_C_STACK) {
273
struct myfunctionparams params = {
274
a,
275
b,
276
&d
277
};
5 months ago
278
void *result;
5 months ago
279
if (Coroutine_Chain(my_stack_size, mychain, &params, &result)){
5 months ago
280
// handle failure
281
}
282
return (int)(intptr_t)result;
8 months ago
283
}
284
// panic now
285
}
286
5 months ago
287
## Configuring for Your Use Case
288
289
There's a number of adjustments which you may need to make for your situation. This are, mostly, in `cor_platform.h`.
290
291
There's two options in `coroutine.h` which you may need to adjust:
292
293
COROUTINE_STARTUP_STACK_SIZE
3 months ago
294
: The amount of stack set aside for `start` in `Coroutine_RunSystem()`.
5 months ago
295
296
COROUTINE_MINIMUM_STACK_SIZE
297
: The minimum stack size you'll ask for. The C stack is managed as a heap. If one of the free blocks in that
3 months ago
298
heap is big enough for your new Coroutine, and has spare, if that spare is too small for a `Coroutine` wanting
5 months ago
299
`COROUTINE_MINIMUM_STACK_SIZE` of stack, then the whole free block is given to your new Coroutine, instead
300
of being split into two.
301
302
`cor_platform.h` focuses on customisation for your particular use case.
303
304
_Cor_thread_local
305
: How to declare a variable to be thread local
306
307
COROUTINE_HAVE_ALLOCA_H
308
: Whether your system can `#incude <alloca.h>`.
309
310
_Cor_Mutex and related routines
311
: Your system's mutex.
312
313
_Cor_Realtime_Now
314
: Return a realtime clock value compatible with _Cor_Semaphore_Wait.
315
316
_Cor_Semaphore and related routines
317
: Semaphores on your system.
318
319
_Cor_Thread and related routines
320
: Threads on your system.
321
8 months ago
322
# API
8 months ago
17
323
8 months ago
324
## Task & Future
8 months ago
325
326
The pattern for using async is:
327
8 months ago
328
#!C
8 months ago
329
bool mymaintask(void *param, void **result){
330
// do your main task things here, like starting more tasks
8 months ago
331
}
332
333
void *res = NULL;
8 months ago
334
bool canceled = Task_Run(mymaintask, NULL, &res);
8 months ago
335
8 months ago
336
To create and wait for a task:
8 months ago
337
8 months ago
338
#!C
8 months ago
339
Task task1;
340
Task_ctor(&task1, asynctask1, &task1param);
8 months ago
341
void *res = NULL;
8 months ago
342
bool canceled = Task_Await(&task1, void **res)
343
Task_dtor(&task1);
8 months ago
344
345
or, if you prefer new & delete:
346
8 months ago
347
#!C
8 months ago
348
Task *task1 = Task_New(asynctask1, &task1param);
8 months ago
349
void *res = NULL;
8 months ago
350
bool canceled = Task_Await(task1, void **res)
351
Task_Delete(task1);
8 months ago
352
353
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:
354
8 months ago
355
#!C
8 months ago
356
Future future;
357
Future_ctor(&future);
8 months ago
358
359
// keep &future to hand for when the background thing completes
8 months ago
360
bool canceled = Future_Await(&future, NULL);
8 months ago
361
8 months ago
362
Future_dtor(&future);
8 months ago
363
8 months ago
364
`Future_New()` and `Future_Delete()` are also available if you prefer that style.
8 months ago
365
366
Inside the callback when the background thing is complete:
367
8 months ago
368
#!C
8 months ago
369
// result is a void *
8 months ago
370
Future_SetResult(future, result, false);
8 months ago
371
372
or, if something went wrong:
373
8 months ago
374
#!C
8 months ago
375
// exception is a void *
8 months ago
376
Future_SetResult(future, exception, true);
8 months ago
377
378
Back in the task, you can respond to the future:
379
8 months ago
380
#!C
8 months ago
381
... Future_Await has returned
8 months ago
382
if (canceled){
383
// exit quickly - you've been canceled
384
// you could, for example, use the future's result as an exception, or error code here
385
}
386
// carry on - the future's result may be an actual result, that's up to you
387
388
8 months ago
389
##### void Future_ctor(Future *fut)
8 months ago
390
8 months ago
391
fut
8 months ago
392
: The `Future` being constructed
8 months ago
393
8 months ago
394
Initialise a future. When you no longer need it, use `Future_dtor()`.
8 months ago
395
8 months ago
396
##### Future *Future_New()
8 months ago
397
8 months ago
398
(returns)
8 months ago
399
: The new future
8 months ago
400
8 months ago
401
Allocates and initialises a future, When you no longer need it, use `Future_Delete()`.
8 months ago
402
8 months ago
403
##### void Future_dtor(Future *fut)
8 months ago
404
405
fut
8 months ago
406
: The `Future` being destructed
8 months ago
407
8 months ago
408
Destruct a future previously constructed with `Future_ctor()`.
8 months ago
409
8 months ago
410
##### void Future_Delete(Future *fut)
8 months ago
411
8 months ago
412
fut
8 months ago
413
: The `Future` to be destructed and freed
8 months ago
414
8 months ago
415
Delete (finalise and free) a future previously new'ed with `Future_New()`
8 months ago
416
8 months ago
417
##### void Future_SetResult(Future *fut, bool canceled, void *value)
8 months ago
418
8 months ago
419
fut
8 months ago
420
: The `Future` whose result is being set
8 months ago
421
8 months ago
422
canceled
423
: The future's `canceled` setting
8 months ago
424
8 months ago
425
value
8 months ago
426
: The future's result `value`
8 months ago
427
8 months ago
428
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
429
8 months ago
430
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
431
8 months ago
432
##### bool Future_GetResult(Future *fut, void **res)
8 months ago
433
8 months ago
434
(returns)
8 months ago
435
: The `canceled` value of the `Future`.
8 months ago
436
8 months ago
437
res
8 months ago
438
: Where to store the value of the `Future`. This may be `NULL`.
8 months ago
439
8 months ago
440
Get the result of a future.
8 months ago
441
8 months ago
442
##### typedef void (*Future_Watcher)(void *me, Future *fut)
8 months ago
443
8 months ago
444
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
445
8 months ago
446
##### void Future_AddWatcher(Future *fut, Future_Watcher watcher, void *me)
8 months ago
447
8 months ago
448
fut
8 months ago
449
: the `Future` to add a watcher to
8 months ago
450
451
watcher
8 months ago
452
: the callback to call when the `Future` has a result.
8 months ago
453
454
me
455
: the `me` value to pass to `watcher` when it is called back.
456
457
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.
458
8 months ago
459
##### void Future_RemoveWatcher(Future *fut, Future_Watcher watcher, void *me)
8 months ago
460
461
fut
8 months ago
462
: the `Future` to remove a watcher from
8 months ago
463
464
watcher
465
: the callback of the watcher to remove.
466
467
me
468
: the `me` value of the watcher to remove.
469
470
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.
471
8 months ago
472
##### bool Future_Await(Future *fut, void **res)
8 months ago
473
474
(returns)
8 months ago
475
: whether the `Future` was canceled.
8 months ago
476
477
fut
8 months ago
478
: The `Future` to wait for.
8 months ago
479
480
res
481
: Where to store the `value` of the future when it is has a result. May be `NULL`.
482
8 months ago
483
The current `Task` is paused until the `Future` has a result. Other `Task`s are run while this one is waiting.
8 months ago
484
8 months ago
485
##### typedef bool (*Task_Entry)(void *param, void **res)
8 months ago
486
8 months ago
487
The entry function to an `Task`.
8 months ago
488
8 months ago
489
##### void Task_ctor(Task *tsk, Task_Entry entry, void *param)
8 months ago
490
491
tsk
492
: The task to construct.
493
494
entry
495
: The entry function for the task.
496
497
param
498
: The value for `param` to pass to `entry`.
499
8 months ago
500
Initialises an `Task`. When you have finished with an `Task` you must finalise it using `Task_dtor()`
8 months ago
501
8 months ago
502
#!C
8 months ago
503
Task tsk;
504
Task_ctor(&tsk, mytask, myparam);
8 months ago
505
// tsk will run if you wait for a task or future
8 months ago
506
Task_Await(&tsk, NULL);
507
Task_dtor(&tsk);
8 months ago
508
8 months ago
509
##### Task *Task_New(Task_Entry entry, void *param)
8 months ago
510
511
(returns)
8 months ago
512
: The new `Task`.
8 months ago
513
514
entry
515
: The entry function for the task.
516
517
param
518
: The value for `param` to pass to `entry`.
519
8 months ago
520
This allocates and initialises a new `Task`. When you have finished with your task, you must `Task_Delete()` it.
8 months ago
521
8 months ago
522
#!C
8 months ago
523
Task *tsk = Task_New(mytask, myparam);
8 months ago
524
// tsk will run if you wait for a task or future
8 months ago
525
Task_Await(tsk, NULL);
526
Task_Delete(tsk);
8 months ago
527
8 months ago
528
##### void Task_dtor(Task *tsk)
8 months ago
529
530
tsk
8 months ago
531
: The `Task` to destruct.
8 months ago
532
8 months ago
533
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
534
8 months ago
535
#!C
8 months ago
536
Task tsk;
537
Task_ctor(&tsk, mytask, myparam);
8 months ago
538
// use tsk
8 months ago
539
Task_dtor(&tsk);
8 months ago
540
8 months ago
541
##### void Task_Delete(Task *tsk)
8 months ago
542
543
tsk
8 months ago
544
: The `Task` to delete.
8 months ago
545
8 months ago
546
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
547
8 months ago
548
#!C
8 months ago
549
Task *tsk = Task_New(mytask, myparam);
8 months ago
550
// use tsk
8 months ago
551
Task_Delete(tsk);
8 months ago
552
8 months ago
553
##### static inline bool Task_Await(Task *tsk, void **res)
8 months ago
554
555
(returns)
556
: Whether the task was canceled.
557
558
tsk
8 months ago
559
: The `Task` to wait for.
8 months ago
560
561
res
8 months ago
562
: Where to store the `Task`'s value when it finishes. This may be NULL.
8 months ago
563
8 months ago
564
The current `Task` waits for `tsk` to finish, and returns the result.
8 months ago
565
8 months ago
566
##### void Task_Cancel(Task *tsk, void *cancel_value)
8 months ago
567
568
tsk
569
: The task to cancel.
570
571
cancel_value
572
: The value to set on any future this task waits on.
573
574
This marks a task as canceled. When that task waits on a future that future will be canceled too, using `cancel_value`.
575
8 months ago
576
##### static inline bool Task_IsCanceled(Task *tsk)
8 months ago
577
578
(returns)
579
: Whether the task is canceled.
580
581
tsk
582
: The task to get its canceled setting from.
583
8 months ago
584
##### static inline Future *Task_GetAwaitedFuture(Task *tsk)
8 months ago
585
8 months ago
586
(returns)
587
: The future the task is waiting on. May be NULL.
588
589
tsk
590
: Teh task to read the future it is waiting on.
591
592
Return the future a task is waiting on.
593
8 months ago
594
##### bool Task_Run(Task_Entry start, void *value, void **res)
8 months ago
595
596
(returns)
8 months ago
597
: Whether `start` was canceled.
8 months ago
598
8 months ago
599
start
600
: The function to use as the main task.
8 months ago
601
602
value
8 months ago
603
: The value to pass to `start`.
8 months ago
604
8 months ago
605
res
606
: Where to store the result of `start`.
8 months ago
607
8 months ago
608
Runs `start` as an `Task`. When `start` returns all other tasks must have been destructed, using `Task_dtor()` or `Task_Delete()`.
8 months ago
609
8 months ago
610
## ASleep
8 months ago
611
8 months ago
612
##### void ASleep_StartSystem()
8 months ago
613
3 months ago
614
You must start the `ASleep` system to use it. This needs to happen per process. Once you've finished with `ASleep` you must `ASleep_StopSystem()`.
8 months ago
615
8 months ago
616
#!C
8 months ago
617
ASleep_StartSystem();
618
// Now you can use ASleep() on any thread
619
ASleep_StopSystem();
8 months ago
620
8 months ago
621
##### void ASleep_StopSystem()
8 months ago
622
8 months ago
623
Call this to stop the `ASleep` system.
8 months ago
624
8 months ago
625
##### bool ASleep(float delay, void **value)
8 months ago
626
8 months ago
627
(returns)
628
: Whether the task was canceled.
8 months ago
629
8 months ago
630
delay
631
: How many seconds to delay for.
8 months ago
632
8 months ago
633
value
634
: Where to store the cancellation value. This may be NULL.
8 months ago
635
8 months ago
636
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
637
8 months ago
638
## Generator
8 months ago
17
639
8 months ago
640
The pattern for a `Generator` is:
641
8 months ago
642
#### A loop which uses the `Generator
8 months ago
643
8 months ago
644
#!C
8 months ago
17
645
Generator gen;
5 months ago
646
Generator_ctor(&gen, generator_stack_size, mygen, &param);
8 months ago
17
647
8 months ago
648
void *value;
649
while(Generator_Next(&gen, &value)){
650
// use value here
8 months ago
17
651
}
8 months ago
652
// value is now the return value from the Generator
653
8 months ago
17
654
Generator_dtor(&gen);
655
8 months ago
656
Or:
8 months ago
17
657
8 months ago
658
#!C
5 months ago
659
Generator *gen = Generator_New(generator_stack_size, mygen, &param);
8 months ago
17
660
8 months ago
661
void *value;
662
while(Generator_Next(gen, &value)){
663
// use value here
664
}
8 months ago
17
665
8 months ago
666
Generator_Delete(gen);
8 months ago
17
667
8 months ago
668
`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.
5 months ago
669
The `generator_stack_size` is the stack amount made available to your generator.
8 months ago
17
670
8 months ago
671
#### A generator function
8 months ago
17
672
8 months ago
673
#!C
8 months ago
674
void *mygen(void *param){
675
bool domore = true;
676
// The parameter is a pointer to a string of chars
677
for (char *str = param; *str; ++str) {
678
// The value yielded is a pointer to a character in the string
679
domore = Generator_Yield(str);
680
if (!domore){
681
break;
682
}
683
}
684
685
return (void *)domore;
8 months ago
17
686
}
687
8 months ago
688
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
689
5 months ago
690
##### void Generator_ctor(Generator *gen, size_t generator_stack_size, void *(*start)(void *), void *param)
8 months ago
17
691
8 months ago
692
gen
693
: The `Generator` to construct.
8 months ago
17
694
5 months ago
695
generator_stack_size
696
: The amount of stack given to the generator.
697
8 months ago
698
start
699
: The function which is the start/entry-point of the `Generator`.
700
701
param
702
: The value to pass to `start`.
703
704
Initialise a `Generator`. When you no longer need the `Generator`, use `Generator_dtor()` to destruct it.
705
8 months ago
706
#!C
8 months ago
707
Generator gen;
5 months ago
708
Generator_ctor(&gen, generator_stack_size, mystart, &params);
8 months ago
17
709
8 months ago
710
// Generator is used
8 months ago
17
711
8 months ago
712
// ... later:
713
Generator_dtor(&gen);
714
5 months ago
715
##### Generator *Generator_New(size_t generator_stack_size, void *(*start)(void *), void *param)
8 months ago
716
5 months ago
717
generator_stack_size
718
: The amount of stack to give to the generator.
719
8 months ago
720
start
721
: The function which is the start/entry-point of the `Generator`.
8 months ago
722
8 months ago
723
param
724
: The value to pass to `start`.
725
726
`new` a `Generator` - malloc, and initialise it. When you no longer need the `Generator` use `Generator_dtor` to finalise it.
727
8 months ago
728
#!C
8 months ago
729
Generator *gen = Generator_New(mystart, &params);
730
731
// Generator is used
732
733
// ... later:
734
Generator_Delete(gen);
735
8 months ago
736
##### void Generator_dtor(Generator *gen)
8 months ago
737
8 months ago
738
gen
739
: The `Generator` to destruct.
740
8 months ago
741
Finalise a `Generator`. Once a `Generator` is no longer needed, it must be finalised:
742
8 months ago
743
#!C
8 months ago
744
// earlier...
745
Generator gen;
5 months ago
746
Generator_ctor(&gen, generator_stack_size, mystart, &params);
8 months ago
747
748
// Generator is used
749
750
// the Generator is no longer needed
751
Generator_dtor(&gen);
752
753
8 months ago
754
##### void Generator_Delete(Generator *gen)
8 months ago
755
8 months ago
756
gen
757
: The `Generator` to delete.
758
8 months ago
759
Finalise then `free()` a `Generator`. Once a `new`ed `Generator` is no longer needed, it must be deleted:
760
8 months ago
761
#!C
8 months ago
762
// earlier...
763
Generator *gen = Generator_New(mystart, &params);
764
765
// Generator is used
766
767
// the Generator is no longer needed
768
Generator_Delete(gen);
769
770
8 months ago
771
##### bool Generator_Next(Generator *gen, void **value)
8 months ago
772
8 months ago
773
(returns)
774
: Whether there is a next value. `true` - there is a next value; `false` - the `Generator` has finished
775
776
gen
777
: The `Generator` to get the next value from.
778
779
value
780
: Where to store the next value.
781
8 months ago
782
Get the next value yielded by the `Generator`.
783
8 months ago
784
#!C
8 months ago
785
void *value;
786
while(Generator_Next(gen, &value)){
787
// use value here
8 months ago
17
788
}
8 months ago
789
8 months ago
790
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
791
8 months ago
792
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`.
793
8 months ago
794
##### bool Generator_Yield(void *value)
8 months ago
795
8 months ago
796
(returns)
797
: Whether the `Generator` should do more.
8 months ago
798
8 months ago
799
value
800
: The `Generator`'s next value.
801
802
Yield a value from a `Generator`.
803
8 months ago
804
#!C
8 months ago
805
bool domore = Generator_Yield(value);
806
8 months ago
807
`value` is then provided by `Generator_Next()` as the next value from the generator.
8 months ago
808
8 months ago
809
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.
810
8 months ago
811
## Coroutine
812
3 months ago
813
##### Coroutine_Err
8 months ago
814
3 months ago
815
The enum of errors:
8 months ago
816
3 months ago
817
Coroutine_OK
818
: Everything is OK. This is 0
8 months ago
819
3 months ago
820
Coroutine_Err_SystemNotRunning
821
: A `Coroutine` must be running to do this
8 months ago
822
3 months ago
823
Coroutine_Err_SystemRunning
824
: The `Coroutine` system must not be running to do this
8 months ago
825
3 months ago
826
Coroutine_Err_NoStack
827
: Not enough stack is available
828
829
Coroutine_Err_CoroutineFromWrongThread
830
: Trying to do something on one thread to a `Coroutine` from a different thread
831
832
Coroutine_Err_ACoroutineIsAlreadyRunning
833
: Trying `Coroutine_RunCoroutine` a `Coroutine`
834
835
Coroutine_Err_ExitWithRunningCoroutines
836
: All `Coroutine`s must be complete
837
838
Coroutine_Err_StackOverrun
839
: Stack overrun detected
840
841
Coroutine_Err_InternalInsistency
842
: Something didn't match inside the system
843
844
Coroutine_Err_CouldNotInitialiseSystem
845
: Something went wrong initialising (eg couldn't create a lock)
846
847
Coroutine_Err_WrongState
848
: It's in the wrong statem, eg trying to `Coroutine_Continue` a completed `Coroutine`
849
850
Coroutine_Err_Canceled
851
: It's canceled
852
6 months ago
853
##### Coroutine_SetStackLimit(void *limit)
854
5 months ago
855
limit
856
: The location (low address) of the stack's end.
857
6 months ago
858
Set the limit of the stack. This is used to determine more accurately whether `Coroutine_CanStartCoroutine()`
859
3 months ago
860
##### Coroutine_Report Coroutine_GetReport()
8 months ago
861
5 months ago
862
(returns)
863
: A report from this run of the Coroutine system.
864
8 months ago
865
#!C
8 months ago
866
typedef struct Coroutine_Report {
867
unsigned coroutines_created;
868
unsigned coroutines_pool_size;
869
unsigned lowest_headroom;
870
} Coroutine_Report;
871
872
coroutines_created
873
: How many coroutines were created
874
875
coroutines_pool_size
876
: 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.
877
878
lowest_headroom
3 months ago
879
: The lowest headroom (unused
8 months ago
880
3 months ago
881
largest_stack
882
: The largest stack requested for any `Coroutine`.
883
3 months ago
884
##### Coroutine_CheckIntegrity()
3 months ago
885
3 months ago
886
(returns)
887
: `Coroutine_Err` for any problem
3 months ago
888
3 months ago
889
Check the integrity of the coroutine system, and `printf()` any problems.
890
8 months ago
891
##### Coroutine_Start
8 months ago
892
8 months ago
893
#!C
8 months ago
894
void *(*)(void *param)
895
8 months ago
896
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
897
3 months ago
898
##### Coroutine_SystemStart
899
900
#!C
901
Coroutine_Err (*)(void *)
902
903
The entry function for `Coroutine_RunSystem`.
904
905
##### Coroutine_Err Coroutine_RunSystem(Coroutine_SystemStart start, void *value)
906
907
(returns)
908
: `Coroutine_OK` or an error. If the system starts, this will be the value returned by `start`.
909
910
start
911
: The function to call with the `Coroutine` system started. It is expected that this routine will
912
start a `Coroutine`.
913
914
value
915
: The value to pass to `start`.
916
5 months ago
917
##### Coroutine *Coroutine_New(size_t size, Coroutine_Start start)
8 months ago
918
5 months ago
919
(returns)
920
A new Coroutine, or `NULL` if there was a failure, such as insufficient stack for the new Coroutine.
921
922
size
923
: The stack size given to this Coroutine.
924
925
start
926
: The routine called to start the Coroutine.
927
5 months ago
928
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.
8 months ago
929
3 months ago
930
##### Coroutine_Err Coroutine_Run_Coroutine(Coroutine *cor, void *value)
8 months ago
931
3 months ago
932
(returns)
933
: any problem, or `Coroutine_OK`
934
5 months ago
935
cor
936
: The Coroutine to run.
937
938
value
939
: The value to pass to the Coroutine's `start` routine.
940
6 months ago
941
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.
8 months ago
942
3 months ago
943
##### Coroutine_Err Coroutine_Run(size_t size, Coroutine_Start start, void *value, void **result)
8 months ago
944
5 months ago
945
(returns)
3 months ago
946
: `Coroutine_OK` or any problem
5 months ago
947
948
size
949
: The stack size to give to the Corotuine.
950
951
start
952
: The routine to start the Coroutine.
953
954
value
955
: The value to pass to `start()`.
956
957
result
958
: Where to store the return value from `start(value)`. This may be `NULL`.
959
5 months ago
960
`start(value)` is called from within a coroutine and its value returned in `*result`.
961
If this completes without any failure, `false` is returned, otherwise, typically
962
because `Coroutine_New()` returned `NULL`, `true` is returned. `result` may be `NULL` if you don't
963
need the resturn value from `start()`.
964
When the coroutine system is active - you are already running in a coroutine - `start(value)`
965
is simply called and its result returned in `*result`. When the Coroutine system is not running,
966
`Coroutine_Run()` starts it, creates a `Coroutine` and runs that Coroutine to call `start(calue)`
5 months ago
967
and return value is returned in `*result`, then stops the Coroutine system. If you need to force
968
a new Coroutine to be created, with a particular stack size to call `start(value)`, then use
969
`Coroutine_Chain()` instead.
8 months ago
970
3 months ago
971
The total stack allowed for all coroutines running on any thread is the size of the call stack on that thread.
972
8 months ago
973
##### void Coroutine_Delete(Coroutine *cor)
8 months ago
974
5 months ago
975
cor
976
: The Coroutine to delete.
977
8 months ago
978
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
979
3 months ago
980
##### Coroutine_Err Coroutine_Continue(Coroutine *cor, void *value, bool early)
8 months ago
981
3 months ago
982
(returns)
3 months ago
983
: `Coroutine_OK` or any error.
3 months ago
984
5 months ago
985
cor
986
: The Coroutine to continue.
987
988
value
989
: The value to return from `cor`'s yield function.
990
991
early
992
: Whether to continue `cor` early (`true`), or late (`false`). Early means before other Coroutines which are waiting
993
to be called, whereas late means after them.
994
3 months ago
995
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 will be run next, or after all the other, currently runnable, coroutines. If the `Coroutine` is already runnable, nothing is done, and `false` is returned. If the `Coroutine` is free, or complete, nothing is done and `true` is returned to show there was a problem.
8 months ago
996
8 months ago
997
##### void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *this)
8 months ago
998
5 months ago
999
value
1000
: The value to yield fropm the coroutine.
1001
1002
on_yield
1003
: A callback to be called once this Coroutine has yielded, but before another one has been continued.
1004
1005
this
1006
: The parameter to pass to `on_yield`.
1007
8 months ago
1008
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()`.
1009
8 months ago
1010
##### void *Coroutine_GetValue(Coroutine *cor)
8 months ago
1011
5 months ago
1012
(returns)
1013
: The Coroutine's value - the last yielded or returned value.
1014
1015
cor
1016
: The Coroutine to query.
1017
8 months ago
1018
Return the `Coroutine`'s value - the value last yielded, or returned by its `start` routine.
1019
8 months ago
1020
##### Coroutine *Coroutine_GetActive()
8 months ago
1021
5 months ago
1022
(returns)
1023
: The currently active Coroutine.
1024
8 months ago
1025
Return whihc coroutine is currently running, ie the caller's `Coroutine`.
1026
8 months ago
1027
##### bool Coroutine_IsRunning(Coroutine *cor)
8 months ago
1028
5 months ago
1029
(returns)
1030
: Whether `cor` is running - it's the active coroutine or waiting to be continued.
1031
1032
cor
1033
: The Coroutine to query.
1034
8 months ago
1035
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
1036
6 months ago
1037
##### bool Coroutine_IsComplete(Coroutine *cor)
1038
5 months ago
1039
(returns)
1040
: Whether `cor` is complete, ie has returned from `start()`./
1041
1042
cor
1043
: The Coroutine to query.
1044
6 months ago
1045
Return whether the given coroutine is complete - is has returned from its `start` function.
1046
7 months ago
1047
##### intptr_t Coroutine_GetStackHeadroom()
8 months ago
1048
5 months ago
1049
(returns)
1050
: The amount of stack headroom.
1051
8 months ago
1052
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
1053
5 months ago
1054
##### bool Coroutine_CanStartCoroutine(size_t size)
8 months ago
1055
5 months ago
1056
(returns)
1057
: Whether a Coroutine with the given amount of stack could be created.
1058
1059
size
1060
: The amount of stack in the Coroutine we might want to create.
1061
6 months ago
1062
Return whether the coroutine system can start a new coroutine. This check can only be done with the coroutine system active (currently running
6 months ago
1063
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
1064
stack use `Coroutine_SetStackLimit()`
8 months ago
1065
6 months ago
1066
##### void *Coroutine_GetStackHWM(void)
1067
5 months ago
1068
(returns)
1069
: The lowest address where the active Coroutine's stack has grown to ever.
1070
6 months ago
1071
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:
1072
1073
#!C
1074
Coroutine_ClearStackForHWM();
1075
char *before = (char *)Coroutine_GetStackHWM();
1076
// do the thing you want to measure here
1077
char *after = (char *)Coroutine_GetStackHWM();
1078
intptr_t amount_used = before - after;
1079
1080
##### void Coroutine_ClearStackForHWM(void)
1081
1082
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:
1083
1084
#!C
1085
Coroutine_ClearStackForHWM();
1086
char *before = (char *)Coroutine_GetStackHWM();
1087
// do the thing you want to measure here
1088
char *after = (char *)Coroutine_GetStackHWM();
1089
intptr_t amount_used = before - after;
1090
8 months ago
1091
##### void *Coroutine_GetCStackTop()
8 months ago
1092
5 months ago
1093
(returns)
1094
: Where the Coroutine system has reached in the C stack.
1095
8 months ago
1096
Return an address which is near to the top of used C stack.
1097
3 months ago
1098
##### Coroutine_Err Coroutine_Chain(size_t size, Coroutine_Start start, void *value, void **result)
8 months ago
1099
5 months ago
1100
(returns)
3 months ago
1101
: Whether there was a problem. `Coroutine_OK` - `start(value)` was run; an error - there was a problem.
5 months ago
1102
1103
size
1104
: The amount of stack to give the chained Coroutine.
1105
1106
start
1107
: The entry point ot the chained Coroutine.
1108
1109
value
1110
: The value to pass to `start()`
1111
1112
result
1113
: Where to store the return value from `start(value)`. This may be `NULL`.
1114
5 months ago
1115
Run `start` with `value` on a new coroutine, and return its return value in `*result`. `result`
1116
may be NULL. `Coroutine_Run()` returns `false` if nothing fails, and `true` if something went wrong,
5 months ago
1117
usually when `Coroutine_New()` ran out of stack. `stack_size` is the amount of stack made available
1118
to the chained Coroutine.
5 months ago
1119
It is expected that `Coroutine_Chain()` will be used when your coroutine is running short
1120
of stack - it is not an alternative to `Coroutine_Run()`.
5 months ago
1121
1122
##### void _Coroutine_Dump()
1123
1124
*Do not use this function in production code*
1125
1126
This prints the current state of the Coroutine system. It is used for development, and is not part of the official interface.
1127