10 months ago |
1 |
1 |
# ccoroutines | ||
9 months ago |
2 |
Coroutines in C, using standard libraries. | |||
3 |
|||||
4 |
## What's Here | ||||
5 |
|||||
6 |
### ccoroutines | ||||
7 |
The coroutine engine. | ||||
8 |
|||||
9 |
### generator | ||||
10 |
Provides generators - coroutines which yield values. These are especially useful for nested searches which provide values to a for loop (eg searching for files in a directory tree). | ||||
11 |
|||||
12 |
### async | ||||
13 |
Provides asynchronous tasks. Each task volunteers to be switched away-from by waiting on a `Async_Future`. | ||||
14 |
|||||
15 |
Note that `generator` and `async` can be used together. | ||||
16 |
|||||
17 |
## Prerequisites | ||||
18 |
|||||
19 |
These 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. | ||||
20 |
|||||
21 |
### Style | ||||
22 |
|||||
23 |
The style is influenced by C++. For example, where possible, a `Something *Something_New(a, b, c)` and `Something_Delete(Something *)`, where a `Something` is `malloc`ed, 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()`. | ||||
24 |
|||||
25 |
Something *oneofthem = Something_New(); | ||||
26 |
// use oneofthem | ||||
27 |
Something_Delete(oneofthem); | ||||
28 |
|||||
29 |
Can be also be done like this, and this will run faster: | ||||
30 |
|||||
31 |
Something oneofthem; | ||||
32 |
Something_ctor(&oneofthem); | ||||
33 |
// use oneofthem | ||||
34 |
Something_dtor(&oneofthem); | ||||
35 |
|||||
36 |
## Usage | ||||
37 |
|||||
38 |
When you are using coroutines or generators: | ||||
39 |
|||||
40 |
void *myfunc(void *){ | ||||
41 |
// your function here | ||||
42 |
} | ||||
43 |
|||||
44 |
Coroutine_StartSystem(); | ||||
45 |
Coroutine_Run(myfunc, (void *)myparam); | ||||
46 |
Coroutine_StopSystem(); | ||||
47 |
|||||
48 |
If you also use async, then: | ||||
49 |
|||||
50 |
bool myfunc(void *myparam, void **res){ | ||||
51 |
// your async function here | ||||
52 |
} | ||||
53 |
|||||
54 |
Async_StartSystem(); | ||||
55 |
void *res = NULL; | ||||
56 |
bool canceled = Async_Run(myfunc, myparam, &res); | ||||
57 |
Async_StopSystem(); | ||||
58 |
|||||
59 |
While the system is started, you can make many calls to `Coroutine_Run()` or `Async_Run()`. A running system is thread local - each thread you want to use coroutines on will need to be `Coroutine_StartSystem()`ed or `Async_StartSystem()`ed. | ||||
60 |
|||||
61 |
### Generators | ||||
62 |
|||||
63 |
void *yield_files(void *param){ | ||||
64 |
bool domore = true; | ||||
65 |
|||||
66 |
// loop/call functions to find more values to yield, and when you have one: | ||||
67 |
domore = Generator_Yield(res); | ||||
68 |
// .. if domore is false, exit your generator - it is being destructed | ||||
69 |
|||||
70 |
// not actually used by generators, but this is a useful convention for bubbling | ||||
71 |
// the flag out to calling functions. | ||||
72 |
return (void *)domore; | ||||
73 |
} | ||||
74 |
|||||
75 |
Generator gen; | ||||
76 |
Generator_ctor(&gen, yield_files, ".."); | ||||
77 |
int count = 0; | ||||
78 |
void *res; | ||||
79 |
while(Generator_Next(&gen, &res)){ | ||||
80 |
// use res - a value yielded by your generator | ||||
81 |
printf("%d) %s\n", count, (char *)res); | ||||
82 |
free(res); | ||||
83 |
|||||
84 |
// exit your loop early if you want to | ||||
85 |
if (++count>16000) break; | ||||
86 |
} | ||||
87 |
Generator_dtor(&gen); | ||||
88 |
|||||
89 |
### Async | ||||
90 |
|||||
91 |
To run an Async program: | ||||
92 |
|||||
93 |
Async_StartSystem(); | ||||
94 |
void *res = NULL; | ||||
95 |
bool canceled = Async_Run(asyncmain, ¶m, &res); | ||||
96 |
Async_StopSystem(); | ||||
97 |
|||||
98 |
Async runs tasks, switching between them when the current task waits on an `Async_Future`. The async main is also a task. The entry function for any task looks like this: | ||||
99 |
|||||
100 |
bool asyncmain(void *param, void **res){ | ||||
101 |
|||||
102 |
// do your thing here | ||||
103 |
|||||
104 |
return canceled; | ||||
105 |
} | ||||
106 |
|||||
107 |
Tasks can complete, or be canceled. Return whether your task was canceled or not. | ||||
108 |
|||||
109 |
Within your async task, create `Async_Task`s and `Async_Future_Await()` them when you want to wait for their result. Note that an `Async_Task` has a member, `base`, which is an `Async_Future`, which is what you should wait on: | ||||
110 |
|||||
111 |
bool asyncmain(void *param, void **res){ | ||||
112 |
... | ||||
113 |
Async_Task task1; | ||||
114 |
Async_Task_ctor(&task1, anasynctask, &task1param); | ||||
115 |
|||||
116 |
bool canceled = Async_Future_Await(&task1.base); | ||||
117 |
|||||
118 |
// use the result stored in the future | ||||
119 |
|||||
120 |
Async_Task_dtor(&task1); | ||||
121 |
... | ||||
122 |
} | ||||
123 |