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