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