1225 lines37.2 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
5 days ago
10
## Quick Start
9 months ago
17
11
5 days ago
12
### Installing
9 months ago
17
13
5 days ago
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(){
5 days ago
24
size_t min_stack_size = 8192 * sizeof(void *);
9 months ago
25
void *res = NULL;
5 days ago
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;
5 days ago
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
5 days ago
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
5 days ago
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
5 days ago
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
5 days ago
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
5 days ago
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
5 days ago
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
5 days ago
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
5 days ago
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;
5 days ago
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
9 hours ago
377
`coroutine_names_def.h` and `coroutine_names_undef.h` allows your installation to rename the
378
coroutine functions. These headers exist so that code within Coroutine can use the functions'
379
normal name, eg `Coroutine_New`, and allow your installation to use a different name, eg `Py_Coroutine_New`.
Last month
380
9 hours ago
381
#define Coroutine_NS(name)
382
: Adjust this to set your installation's naming convention.
Last month
383
384
Coroutine_API_FUNC(TYPE)
385
: This allows tagging of functions to be accessible to loaded dlls.
386
9 months ago
387
# API
9 months ago
17
388
9 months ago
389
## Task & Future
9 months ago
390
391
The pattern for using async is:
392
9 months ago
393
#!C
9 months ago
394
bool mymaintask(void *param, void **result){
395
// do your main task things here, like starting more tasks
9 months ago
396
}
397
398
void *res = NULL;
9 months ago
399
bool canceled = Task_Run(mymaintask, NULL, &res);
9 months ago
400
9 months ago
401
To create and wait for a task:
9 months ago
402
9 months ago
403
#!C
9 months ago
404
Task task1;
9 hours ago
405
Task_ctor(&task1, min_stack, min_stack_headorom, asynctask1, &task1param);
9 months ago
406
void *res = NULL;
9 months ago
407
bool canceled = Task_Await(&task1, void **res)
408
Task_dtor(&task1);
9 months ago
409
410
or, if you prefer new & delete:
411
9 months ago
412
#!C
9 hours ago
413
Task *task1 = Task_New(min_stack, min_stack_headorom, asynctask1, &task1param);
9 months ago
414
void *res = NULL;
9 months ago
415
bool canceled = Task_Await(task1, void **res)
416
Task_Delete(task1);
9 months ago
417
418
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:
419
9 months ago
420
#!C
9 months ago
421
Future future;
422
Future_ctor(&future);
9 months ago
423
424
// keep &future to hand for when the background thing completes
9 months ago
425
bool canceled = Future_Await(&future, NULL);
9 months ago
426
9 months ago
427
Future_dtor(&future);
9 months ago
428
9 months ago
429
`Future_New()` and `Future_Delete()` are also available if you prefer that style.
9 months ago
430
431
Inside the callback when the background thing is complete:
432
9 months ago
433
#!C
9 months ago
434
// result is a void *
9 months ago
435
Future_SetResult(future, result, false);
9 months ago
436
437
or, if something went wrong:
438
9 months ago
439
#!C
9 months ago
440
// exception is a void *
9 months ago
441
Future_SetResult(future, exception, true);
9 months ago
442
443
Back in the task, you can respond to the future:
444
9 months ago
445
#!C
9 months ago
446
... Future_Await has returned
9 months ago
447
if (canceled){
448
// exit quickly - you've been canceled
449
// you could, for example, use the future's result as an exception, or error code here
450
}
451
// carry on - the future's result may be an actual result, that's up to you
452
453
9 months ago
454
##### void Future_ctor(Future *fut)
9 months ago
455
9 months ago
456
fut
9 months ago
457
: The `Future` being constructed
9 months ago
458
9 months ago
459
Initialise a future. When you no longer need it, use `Future_dtor()`.
9 months ago
460
9 months ago
461
##### Future *Future_New()
9 months ago
462
9 months ago
463
(returns)
9 months ago
464
: The new future
9 months ago
465
9 months ago
466
Allocates and initialises a future, When you no longer need it, use `Future_Delete()`.
9 months ago
467
9 months ago
468
##### void Future_dtor(Future *fut)
9 months ago
469
470
fut
9 months ago
471
: The `Future` being destructed
9 months ago
472
9 months ago
473
Destruct a future previously constructed with `Future_ctor()`.
9 months ago
474
9 months ago
475
##### void Future_Delete(Future *fut)
9 months ago
476
9 months ago
477
fut
9 months ago
478
: The `Future` to be destructed and freed
9 months ago
479
9 months ago
480
Delete (finalise and free) a future previously new'ed with `Future_New()`
9 months ago
481
9 months ago
482
##### void Future_SetResult(Future *fut, bool canceled, void *value)
9 months ago
483
9 months ago
484
fut
9 months ago
485
: The `Future` whose result is being set
9 months ago
486
9 months ago
487
canceled
488
: The future's `canceled` setting
9 months ago
489
9 months ago
490
value
9 months ago
491
: The future's result `value`
9 months ago
492
9 months ago
493
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
494
9 months ago
495
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
496
9 months ago
497
##### bool Future_GetResult(Future *fut, void **res)
9 months ago
498
9 months ago
499
(returns)
9 months ago
500
: The `canceled` value of the `Future`.
9 months ago
501
9 months ago
502
res
9 months ago
503
: Where to store the value of the `Future`. This may be `NULL`.
9 months ago
504
9 months ago
505
Get the result of a future.
9 months ago
506
9 months ago
507
##### typedef void (*Future_Watcher)(void *me, Future *fut)
9 months ago
508
9 months ago
509
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
510
9 months ago
511
##### void Future_AddWatcher(Future *fut, Future_Watcher watcher, void *me)
9 months ago
512
9 months ago
513
fut
9 months ago
514
: the `Future` to add a watcher to
9 months ago
515
516
watcher
9 months ago
517
: the callback to call when the `Future` has a result.
9 months ago
518
519
me
520
: the `me` value to pass to `watcher` when it is called back.
521
522
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.
523
9 months ago
524
##### void Future_RemoveWatcher(Future *fut, Future_Watcher watcher, void *me)
9 months ago
525
526
fut
9 months ago
527
: the `Future` to remove a watcher from
9 months ago
528
529
watcher
530
: the callback of the watcher to remove.
531
532
me
533
: the `me` value of the watcher to remove.
534
535
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.
536
9 months ago
537
##### bool Future_Await(Future *fut, void **res)
9 months ago
538
539
(returns)
9 months ago
540
: whether the `Future` was canceled.
9 months ago
541
542
fut
9 months ago
543
: The `Future` to wait for.
9 months ago
544
545
res
546
: Where to store the `value` of the future when it is has a result. May be `NULL`.
547
9 months ago
548
The current `Task` is paused until the `Future` has a result. Other `Task`s are run while this one is waiting.
9 months ago
549
9 months ago
550
##### typedef bool (*Task_Entry)(void *param, void **res)
9 months ago
551
9 months ago
552
The entry function to an `Task`.
9 months ago
553
9 hours ago
554
##### void Task_ctor(Task *tsk, size_t min_stack, size_t min_stack_headroom, Task_Entry entry, void *param)
9 months ago
555
556
tsk
557
: The task to construct.
558
9 hours ago
559
min_stack
560
: The minimum stack to keep for this task.
561
562
min_stack_headroom
563
: The minimum stack headroom to keep for this task.
564
9 months ago
565
entry
566
: The entry function for the task.
567
568
param
569
: The value for `param` to pass to `entry`.
570
9 months ago
571
Initialises an `Task`. When you have finished with an `Task` you must finalise it using `Task_dtor()`
9 months ago
572
9 months ago
573
#!C
9 months ago
574
Task tsk;
575
Task_ctor(&tsk, mytask, myparam);
9 months ago
576
// tsk will run if you wait for a task or future
9 months ago
577
Task_Await(&tsk, NULL);
578
Task_dtor(&tsk);
9 months ago
579
9 hours ago
580
##### Task *Task_New(size_t min_stack, size_t min_stack_headroom, Task_Entry entry, void *param)
9 months ago
581
582
(returns)
9 months ago
583
: The new `Task`.
9 months ago
584
9 hours ago
585
min_stack
586
: The minimum stack to keep for this task.
587
588
min_stack_headroom
589
: The minimum stack headroom to keep for this task.
590
9 months ago
591
entry
592
: The entry function for the task.
593
594
param
595
: The value for `param` to pass to `entry`.
596
9 months ago
597
This allocates and initialises a new `Task`. When you have finished with your task, you must `Task_Delete()` it.
9 months ago
598
9 months ago
599
#!C
9 months ago
600
Task *tsk = Task_New(mytask, myparam);
9 months ago
601
// tsk will run if you wait for a task or future
9 months ago
602
Task_Await(tsk, NULL);
603
Task_Delete(tsk);
9 months ago
604
9 months ago
605
##### void Task_dtor(Task *tsk)
9 months ago
606
607
tsk
9 months ago
608
: The `Task` to destruct.
9 months ago
609
9 months ago
610
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
611
9 months ago
612
#!C
9 months ago
613
Task tsk;
614
Task_ctor(&tsk, mytask, myparam);
9 months ago
615
// use tsk
9 months ago
616
Task_dtor(&tsk);
9 months ago
617
9 months ago
618
##### void Task_Delete(Task *tsk)
9 months ago
619
620
tsk
9 months ago
621
: The `Task` to delete.
9 months ago
622
9 months ago
623
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
624
9 months ago
625
#!C
9 months ago
626
Task *tsk = Task_New(mytask, myparam);
9 months ago
627
// use tsk
9 months ago
628
Task_Delete(tsk);
9 months ago
629
9 months ago
630
##### static inline bool Task_Await(Task *tsk, void **res)
9 months ago
631
632
(returns)
633
: Whether the task was canceled.
634
635
tsk
9 months ago
636
: The `Task` to wait for.
9 months ago
637
638
res
9 months ago
639
: Where to store the `Task`'s value when it finishes. This may be NULL.
9 months ago
640
9 months ago
641
The current `Task` waits for `tsk` to finish, and returns the result.
9 months ago
642
9 months ago
643
##### void Task_Cancel(Task *tsk, void *cancel_value)
9 months ago
644
645
tsk
646
: The task to cancel.
647
648
cancel_value
649
: The value to set on any future this task waits on.
650
651
This marks a task as canceled. When that task waits on a future that future will be canceled too, using `cancel_value`.
652
9 months ago
653
##### static inline bool Task_IsCanceled(Task *tsk)
9 months ago
654
655
(returns)
656
: Whether the task is canceled.
657
658
tsk
659
: The task to get its canceled setting from.
660
9 months ago
661
##### static inline Future *Task_GetAwaitedFuture(Task *tsk)
9 months ago
662
9 months ago
663
(returns)
664
: The future the task is waiting on. May be NULL.
665
666
tsk
667
: Teh task to read the future it is waiting on.
668
669
Return the future a task is waiting on.
670
9 months ago
671
##### bool Task_Run(Task_Entry start, void *value, void **res)
9 months ago
672
673
(returns)
9 months ago
674
: Whether `start` was canceled.
9 months ago
675
9 months ago
676
start
677
: The function to use as the main task.
9 months ago
678
679
value
9 months ago
680
: The value to pass to `start`.
9 months ago
681
9 months ago
682
res
683
: Where to store the result of `start`.
9 months ago
684
9 months ago
685
Runs `start` as an `Task`. When `start` returns all other tasks must have been destructed, using `Task_dtor()` or `Task_Delete()`.
9 months ago
686
9 months ago
687
## ASleep
9 months ago
688
9 months ago
689
##### void ASleep_StartSystem()
9 months ago
690
4 months ago
691
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
692
9 months ago
693
#!C
9 months ago
694
ASleep_StartSystem();
695
// Now you can use ASleep() on any thread
696
ASleep_StopSystem();
9 months ago
697
9 months ago
698
##### void ASleep_StopSystem()
9 months ago
699
9 months ago
700
Call this to stop the `ASleep` system.
9 months ago
701
9 months ago
702
##### bool ASleep(float delay, void **value)
9 months ago
703
9 months ago
704
(returns)
705
: Whether the task was canceled.
9 months ago
706
9 months ago
707
delay
708
: How many seconds to delay for.
9 months ago
709
9 months ago
710
value
711
: Where to store the cancellation value. This may be NULL.
9 months ago
712
9 months ago
713
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
714
9 months ago
715
## Generator
9 months ago
17
716
9 months ago
717
The pattern for a `Generator` is:
718
9 months ago
719
#### A loop which uses the `Generator
9 months ago
720
9 months ago
721
#!C
9 months ago
17
722
Generator gen;
9 hours ago
723
Generator_ctor(&gen, min_stack, min_stack_headroom, mygen, &param);
9 months ago
17
724
9 months ago
725
void *value;
726
while(Generator_Next(&gen, &value)){
727
// use value here
9 months ago
17
728
}
9 months ago
729
// value is now the return value from the Generator
730
9 months ago
17
731
Generator_dtor(&gen);
732
9 months ago
733
Or:
9 months ago
17
734
9 months ago
735
#!C
9 hours ago
736
Generator *gen = Generator_New(min_stack, min_stack_headroom, mygen, &param);
9 months ago
17
737
9 months ago
738
void *value;
739
while(Generator_Next(gen, &value)){
740
// use value here
741
}
9 months ago
17
742
9 months ago
743
Generator_Delete(gen);
9 months ago
17
744
9 months ago
745
`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
746
The `generator_stack_size` is the stack amount made available to your generator.
9 months ago
17
747
9 months ago
748
#### A generator function
9 months ago
17
749
9 months ago
750
#!C
9 months ago
751
void *mygen(void *param){
752
bool domore = true;
753
// The parameter is a pointer to a string of chars
754
for (char *str = param; *str; ++str) {
755
// The value yielded is a pointer to a character in the string
756
domore = Generator_Yield(str);
757
if (!domore){
758
break;
759
}
760
}
761
762
return (void *)domore;
9 months ago
17
763
}
764
9 months ago
765
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
766
9 hours ago
767
##### void Generator_ctor(Generator *gen, size_t min_stack, size_t min_stack_headroom, void *(*start)(void *), void *param)
9 months ago
17
768
9 months ago
769
gen
770
: The `Generator` to construct.
9 months ago
17
771
9 hours ago
772
min_stack
773
: The minimum amount of stack to keep for this generator.
6 months ago
774
9 hours ago
775
min_stack_headroom
776
: The minimum stack headroom to keep for this generator
777
9 months ago
778
start
779
: The function which is the start/entry-point of the `Generator`.
780
781
param
782
: The value to pass to `start`.
783
784
Initialise a `Generator`. When you no longer need the `Generator`, use `Generator_dtor()` to destruct it.
785
9 months ago
786
#!C
9 months ago
787
Generator gen;
9 hours ago
788
Generator_ctor(&gen, min_stack, min_stack_headroom, mystart, &params);
9 months ago
17
789
9 months ago
790
// Generator is used
9 months ago
17
791
9 months ago
792
// ... later:
793
Generator_dtor(&gen);
794
9 hours ago
795
##### Generator *Generator_New(size_t min_stack, size_t min_stack_headroom, void *(*start)(void *), void *param)
9 months ago
796
9 hours ago
797
min_stack
798
: The minimum amount of stack to keep for the generator.
6 months ago
799
9 hours ago
800
min_stack_headroom
801
: The amount of stack headroom to keep for the generator.
802
9 months ago
803
start
804
: The function which is the start/entry-point of the `Generator`.
9 months ago
805
9 months ago
806
param
807
: The value to pass to `start`.
808
809
`new` a `Generator` - malloc, and initialise it. When you no longer need the `Generator` use `Generator_dtor` to finalise it.
810
9 months ago
811
#!C
9 hours ago
812
Generator *gen = Generator_New(my_stack, my_stack_headroom, mystart, &params);
9 months ago
813
814
// Generator is used
815
816
// ... later:
817
Generator_Delete(gen);
818
9 months ago
819
##### void Generator_dtor(Generator *gen)
9 months ago
820
9 months ago
821
gen
822
: The `Generator` to destruct.
823
9 months ago
824
Finalise a `Generator`. Once a `Generator` is no longer needed, it must be finalised:
825
9 months ago
826
#!C
9 months ago
827
// earlier...
828
Generator gen;
9 hours ago
829
Generator_ctor(&gen, my_stack, my_stack_headroom, mystart, &params);
9 months ago
830
831
// Generator is used
832
833
// the Generator is no longer needed
834
Generator_dtor(&gen);
835
836
9 months ago
837
##### void Generator_Delete(Generator *gen)
9 months ago
838
9 months ago
839
gen
840
: The `Generator` to delete.
841
9 months ago
842
Finalise then `free()` a `Generator`. Once a `new`ed `Generator` is no longer needed, it must be deleted:
843
9 months ago
844
#!C
9 months ago
845
// earlier...
846
Generator *gen = Generator_New(mystart, &params);
847
848
// Generator is used
849
850
// the Generator is no longer needed
851
Generator_Delete(gen);
852
853
9 months ago
854
##### bool Generator_Next(Generator *gen, void **value)
9 months ago
855
9 months ago
856
(returns)
857
: Whether there is a next value. `true` - there is a next value; `false` - the `Generator` has finished
858
859
gen
860
: The `Generator` to get the next value from.
861
862
value
863
: Where to store the next value.
864
9 months ago
865
Get the next value yielded by the `Generator`.
866
9 months ago
867
#!C
9 months ago
868
void *value;
869
while(Generator_Next(gen, &value)){
870
// use value here
9 months ago
17
871
}
9 months ago
872
9 months ago
873
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
874
9 months ago
875
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`.
876
9 months ago
877
##### bool Generator_Yield(void *value)
9 months ago
878
9 months ago
879
(returns)
880
: Whether the `Generator` should do more.
9 months ago
881
9 months ago
882
value
883
: The `Generator`'s next value.
884
885
Yield a value from a `Generator`.
886
9 months ago
887
#!C
9 months ago
888
bool domore = Generator_Yield(value);
889
9 months ago
890
`value` is then provided by `Generator_Next()` as the next value from the generator.
9 months ago
891
9 months ago
892
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.
893
9 months ago
894
## Coroutine
895
4 months ago
896
##### Coroutine_Err
9 months ago
897
4 months ago
898
The enum of errors:
9 months ago
899
4 months ago
900
Coroutine_OK
901
: Everything is OK. This is 0
9 months ago
902
4 months ago
903
Coroutine_Err_SystemNotRunning
904
: A `Coroutine` must be running to do this
9 months ago
905
4 months ago
906
Coroutine_Err_SystemRunning
907
: The `Coroutine` system must not be running to do this
9 months ago
908
4 months ago
909
Coroutine_Err_NoStack
910
: Not enough stack is available
911
912
Coroutine_Err_CoroutineFromWrongThread
913
: Trying to do something on one thread to a `Coroutine` from a different thread
914
915
Coroutine_Err_ACoroutineIsAlreadyRunning
916
: Trying `Coroutine_RunCoroutine` a `Coroutine`
917
918
Coroutine_Err_ExitWithRunningCoroutines
919
: All `Coroutine`s must be complete
920
921
Coroutine_Err_StackOverrun
922
: Stack overrun detected
923
924
Coroutine_Err_InternalInsistency
925
: Something didn't match inside the system
926
927
Coroutine_Err_CouldNotInitialiseSystem
928
: Something went wrong initialising (eg couldn't create a lock)
929
930
Coroutine_Err_WrongState
931
: It's in the wrong statem, eg trying to `Coroutine_Continue` a completed `Coroutine`
932
933
Coroutine_Err_Canceled
934
: It's canceled
935
7 months ago
936
##### Coroutine_SetStackLimit(void *limit)
937
6 months ago
938
limit
939
: The location (low address) of the stack's end.
940
7 months ago
941
Set the limit of the stack. This is used to determine more accurately whether `Coroutine_CanStartCoroutine()`
942
4 months ago
943
##### Coroutine_Report Coroutine_GetReport()
9 months ago
944
6 months ago
945
(returns)
946
: A report from this run of the Coroutine system.
947
9 months ago
948
#!C
9 months ago
949
typedef struct Coroutine_Report {
950
unsigned coroutines_created;
951
unsigned coroutines_pool_size;
952
unsigned lowest_headroom;
953
} Coroutine_Report;
954
955
coroutines_created
956
: How many coroutines were created
957
958
coroutines_pool_size
959
: 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.
960
961
lowest_headroom
4 months ago
962
: The lowest headroom (unused
9 months ago
963
4 months ago
964
largest_stack
965
: The largest stack requested for any `Coroutine`.
966
4 months ago
967
##### Coroutine_CheckIntegrity()
4 months ago
968
4 months ago
969
(returns)
970
: `Coroutine_Err` for any problem
4 months ago
971
4 months ago
972
Check the integrity of the coroutine system, and `printf()` any problems.
973
9 months ago
974
##### Coroutine_Start
9 months ago
975
9 months ago
976
#!C
9 months ago
977
void *(*)(void *param)
978
9 months ago
979
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
980
4 months ago
981
##### Coroutine_SystemStart
982
983
#!C
9 hours ago
984
Coroutine_Err (*)(void *param, Coroutine *root_coroutine)
4 months ago
985
986
The entry function for `Coroutine_RunSystem`.
987
9 hours ago
988
(returns)
989
: An error if one occurs.
4 months ago
990
9 hours ago
991
param
992
: The parameter passed in to `Coroutine_RunSystem`
993
994
root_coroutine
995
: The coroutine that this callback is running in
996
997
##### Coroutine_Err Coroutine_RunSystem(size_t min_size, size_t min_headroom, Coroutine_SystemStart start, void *value)
998
4 months ago
999
(returns)
1000
: `Coroutine_OK` or an error. If the system starts, this will be the value returned by `start`.
1001
9 hours ago
1002
min_size
1003
: The minimum stack size to keep for the root coroutine.
1004
1005
min_headroom
1006
: The minimum headroom to keep for the root coroutine.
1007
4 months ago
1008
start
1009
: The function to call with the `Coroutine` system started. It is expected that this routine will
1010
start a `Coroutine`.
1011
1012
value
1013
: The value to pass to `start`.
1014
9 hours ago
1015
##### Coroutine *Coroutine_New(size_t min_size, size_t min_headroom, Coroutine_Start start)
9 months ago
1016
6 months ago
1017
(returns)
1018
A new Coroutine, or `NULL` if there was a failure, such as insufficient stack for the new Coroutine.
1019
9 hours ago
1020
min_size
1021
: The minimum stack size to keep for this Coroutine.
6 months ago
1022
9 hours ago
1023
min_headorom
1024
: The minimum stack headroom to keep for this Coroutine.
1025
6 months ago
1026
start
1027
: The routine called to start the Coroutine.
1028
6 months ago
1029
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
1030
9 hours ago
1031
##### Coroutine_Err Coroutine_Run(size_t min_size, size_t min_headroom, Coroutine_Start start, void *value, void **result)
9 months ago
1032
4 months ago
1033
(returns)
9 hours ago
1034
: `Coroutine_OK` or any problem
4 months ago
1035
9 hours ago
1036
min_size
1037
: The minimum stack size to keep for this Coroutine.
6 months ago
1038
9 hours ago
1039
min_headorom
1040
: The minimum stack headroom to keep for this Coroutine.
6 months ago
1041
1042
start
1043
: The routine to start the Coroutine.
1044
1045
value
1046
: The value to pass to `start()`.
1047
1048
result
1049
: Where to store the return value from `start(value)`. This may be `NULL`.
1050
6 months ago
1051
`start(value)` is called from within a coroutine and its value returned in `*result`.
1052
If this completes without any failure, `false` is returned, otherwise, typically
1053
because `Coroutine_New()` returned `NULL`, `true` is returned. `result` may be `NULL` if you don't
1054
need the resturn value from `start()`.
1055
When the coroutine system is active - you are already running in a coroutine - `start(value)`
1056
is simply called and its result returned in `*result`. When the Coroutine system is not running,
1057
`Coroutine_Run()` starts it, creates a `Coroutine` and runs that Coroutine to call `start(calue)`
6 months ago
1058
and return value is returned in `*result`, then stops the Coroutine system. If you need to force
1059
a new Coroutine to be created, with a particular stack size to call `start(value)`, then use
1060
`Coroutine_Chain()` instead.
9 months ago
1061
4 months ago
1062
The total stack allowed for all coroutines running on any thread is the size of the call stack on that thread.
1063
9 months ago
1064
##### void Coroutine_Delete(Coroutine *cor)
9 months ago
1065
6 months ago
1066
cor
1067
: The Coroutine to delete.
1068
9 months ago
1069
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
1070
4 months ago
1071
##### Coroutine_Err Coroutine_Continue(Coroutine *cor, void *value, bool early)
9 months ago
1072
4 months ago
1073
(returns)
4 months ago
1074
: `Coroutine_OK` or any error.
4 months ago
1075
6 months ago
1076
cor
1077
: The Coroutine to continue.
1078
1079
value
1080
: The value to return from `cor`'s yield function.
1081
1082
early
1083
: Whether to continue `cor` early (`true`), or late (`false`). Early means before other Coroutines which are waiting
1084
to be called, whereas late means after them.
1085
4 months ago
1086
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
1087
9 months ago
1088
##### void *Coroutine_Yield(void *value, Coroutine_YieldCallback on_yield, void *this)
9 months ago
1089
6 months ago
1090
value
1091
: The value to yield fropm the coroutine.
1092
1093
on_yield
1094
: A callback to be called once this Coroutine has yielded, but before another one has been continued.
1095
1096
this
1097
: The parameter to pass to `on_yield`.
1098
9 hours ago
1099
Yield `value` from the current coroutine; this coroutine is moved to the list of coroutines waiting to be continued.
9 months ago
1100
9 hours ago
1101
When the current coroutine had been paused, `on_yield(this)` is called, with the expectation it might adjust which coroutines are ready to be run.
1102
1103
When `on_yield(this)` returns, 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()`.
1104
9 months ago
1105
##### void *Coroutine_GetValue(Coroutine *cor)
9 months ago
1106
6 months ago
1107
(returns)
1108
: The Coroutine's value - the last yielded or returned value.
1109
1110
cor
1111
: The Coroutine to query.
1112
9 months ago
1113
Return the `Coroutine`'s value - the value last yielded, or returned by its `start` routine.
1114
9 months ago
1115
##### Coroutine *Coroutine_GetActive()
9 months ago
1116
6 months ago
1117
(returns)
1118
: The currently active Coroutine.
1119
9 months ago
1120
Return whihc coroutine is currently running, ie the caller's `Coroutine`.
1121
9 months ago
1122
##### bool Coroutine_IsRunning(Coroutine *cor)
9 months ago
1123
6 months ago
1124
(returns)
1125
: Whether `cor` is running - it's the active coroutine or waiting to be continued.
1126
1127
cor
1128
: The Coroutine to query.
1129
9 months ago
1130
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
1131
7 months ago
1132
##### bool Coroutine_IsComplete(Coroutine *cor)
1133
6 months ago
1134
(returns)
1135
: Whether `cor` is complete, ie has returned from `start()`./
1136
1137
cor
1138
: The Coroutine to query.
1139
7 months ago
1140
Return whether the given coroutine is complete - is has returned from its `start` function.
1141
8 months ago
1142
##### intptr_t Coroutine_GetStackHeadroom()
9 months ago
1143
6 months ago
1144
(returns)
1145
: The amount of stack headroom.
1146
9 months ago
1147
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
1148
6 months ago
1149
##### bool Coroutine_CanStartCoroutine(size_t size)
9 months ago
1150
6 months ago
1151
(returns)
1152
: Whether a Coroutine with the given amount of stack could be created.
1153
1154
size
1155
: The amount of stack in the Coroutine we might want to create.
1156
7 months ago
1157
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
1158
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
1159
stack use `Coroutine_SetStackLimit()`
9 months ago
1160
7 months ago
1161
##### void *Coroutine_GetStackHWM(void)
1162
6 months ago
1163
(returns)
1164
: The lowest address where the active Coroutine's stack has grown to ever.
1165
7 months ago
1166
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:
1167
1168
#!C
1169
Coroutine_ClearStackForHWM();
1170
char *before = (char *)Coroutine_GetStackHWM();
1171
// do the thing you want to measure here
1172
char *after = (char *)Coroutine_GetStackHWM();
1173
intptr_t amount_used = before - after;
1174
1175
##### void Coroutine_ClearStackForHWM(void)
1176
1177
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:
1178
1179
#!C
1180
Coroutine_ClearStackForHWM();
1181
char *before = (char *)Coroutine_GetStackHWM();
1182
// do the thing you want to measure here
1183
char *after = (char *)Coroutine_GetStackHWM();
1184
intptr_t amount_used = before - after;
1185
9 months ago
1186
##### void *Coroutine_GetCStackTop()
9 months ago
1187
6 months ago
1188
(returns)
1189
: Where the Coroutine system has reached in the C stack.
1190
9 months ago
1191
Return an address which is near to the top of used C stack.
1192
9 hours ago
1193
##### Coroutine_Err Coroutine_Chain(size_t min_size, size_t min_headroom, Coroutine_Start start, void *value, void **result)
9 months ago
1194
6 months ago
1195
(returns)
4 months ago
1196
: Whether there was a problem. `Coroutine_OK` - `start(value)` was run; an error - there was a problem.
6 months ago
1197
9 hours ago
1198
min_size
1199
: The amount of stack to keep for the chained Coroutine.
6 months ago
1200
9 hours ago
1201
min_headroom
1202
: The amount of stack headroom to keep for the chained Coroutine.
1203
6 months ago
1204
start
1205
: The entry point ot the chained Coroutine.
1206
1207
value
1208
: The value to pass to `start()`
1209
1210
result
1211
: Where to store the return value from `start(value)`. This may be `NULL`.
1212
6 months ago
1213
Run `start` with `value` on a new coroutine, and return its return value in `*result`. `result`
1214
may be NULL. `Coroutine_Run()` returns `false` if nothing fails, and `true` if something went wrong,
6 months ago
1215
usually when `Coroutine_New()` ran out of stack. `stack_size` is the amount of stack made available
1216
to the chained Coroutine.
6 months ago
1217
It is expected that `Coroutine_Chain()` will be used when your coroutine is running short
1218
of stack - it is not an alternative to `Coroutine_Run()`.
6 months ago
1219
Last month
1220
##### void Coroutine_Dump_()
6 months ago
1221
1222
*Do not use this function in production code*
1223
1224
This prints the current state of the Coroutine system. It is used for development, and is not part of the official interface.
1225