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