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