Coroutines in C, using standard libraries.
The coroutine engine.
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).
Provides asynchronous tasks. Each task volunteers to be switched away-from by waiting on a Async_Future.
Note that generator and async can be used together.
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.
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 malloced, 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().
Something *oneofthem = Something_New();
// use oneofthem
Something_Delete(oneofthem);
Can be also be done like this, and this will run faster:
Something oneofthem;
Something_ctor(&oneofthem);
// use oneofthem
Something_dtor(&oneofthem);
When you are using coroutines or generators:
void *myfunc(void *){
// your function here
}
Coroutine_StartSystem();
Coroutine_Run(myfunc, (void *)myparam);
Coroutine_StopSystem();
If you also use async, then:
bool myfunc(void *myparam, void **res){
// your async function here
}
Async_StartSystem();
void *res = NULL;
bool canceled = Async_Run(myfunc, myparam, &res);
Async_StopSystem();
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.
void *yield_files(void *param){
bool domore = true;
// loop/call functions to find more values to yield, and when you have one:
domore = Generator_Yield(res);
// .. if domore is false, exit your generator - it is being destructed
// not actually used by generators, but this is a useful convention for bubbling
// the flag out to calling functions.
return (void *)domore;
}
Generator gen;
Generator_ctor(&gen, yield_files, "..");
int count = 0;
void *res;
while(Generator_Next(&gen, &res)){
// use res - a value yielded by your generator
printf("%d) %s\n", count, (char *)res);
free(res);
// exit your loop early if you want to
if (++count>16000) break;
}
Generator_dtor(&gen);
To run an Async program:
Async_StartSystem();
void *res = NULL;
bool canceled = Async_Run(asyncmain, ¶m, &res);
Async_StopSystem();
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:
bool asyncmain(void *param, void **res){
// do your thing here
return canceled;
}
Tasks can complete, or be canceled. Return whether your task was canceled or not.
Within your async task, create Async_Tasks 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:
bool asyncmain(void *param, void **res){
...
Async_Task task1;
Async_Task_ctor(&task1, anasynctask, &task1param);
bool canceled = Async_Future_Await(&task1.base);
// use the result stored in the future
Async_Task_dtor(&task1);
...
}