Skip to content

Commit

Permalink
Coroutine API change: Required coroutine struct member renamed:
Browse files Browse the repository at this point in the history
	int cco_state; => cco_state cco;
Macros cco_task_struct() and cco_iter_struct() changed to have a more C-like syntax.
  • Loading branch information
tylov committed Oct 22, 2024
1 parent 3fdabb3 commit 2375473
Show file tree
Hide file tree
Showing 13 changed files with 100 additions and 79 deletions.
35 changes: 19 additions & 16 deletions docs/coroutine_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ cancelled (not resumed until they are done).

A coroutine function may have almost any signature, but the implementation adds support for
coroutines which returns an int, indicating CCO_DONE, CCO_AWAIT, or CCO_YIELD. It should also
take a struct pointer as parameter which must contains the member `int cco_state`. The struct should
normally store all *local* variables to be used within the coroutine, along with *input* and *output* data
take a struct pointer as parameter which must contains the member `cco_state cco;`. The struct should
store all *local* variables to be used within the coroutine, along with *input* and *output* data
for the coroutine.

Note that this implementation is not limited to support a certain set of coroutine types,
Expand All @@ -83,7 +83,7 @@ yield or await from a (deeply) nested coroutine call using cco_task objects desc

The first example is a generator of Pythagorian triples, and stops when diagonal size > max_c.

[ [Run this code](https://godbolt.org/z/cP1dWKvM3) ]
[ [Run this code](https://godbolt.org/z/r7h3K3e5j) ]
```c
// https://quuxplusone.github.io/blog/2019/03/06/pythagorean-triples/
#include <stdio.h>
Expand All @@ -92,7 +92,7 @@ The first example is a generator of Pythagorian triples, and stops when diagonal
struct triples {
int max_c; // input: max c.
int a, b, c; // output
int cco_state; // required member
cco_state cco; // required member
};

int triples(struct triples* i) {
Expand Down Expand Up @@ -130,7 +130,7 @@ The next variant skips the triples which are upscaled version of smaller ones by
the gcd() function. Note that the gcd1_triples struct contains the triples struct so that
both functions have separate call frames:
[ [Run this code](https://godbolt.org/z/14a1YM9fd) ]
[ [Run this code](https://godbolt.org/z/afPsf5xsn) ]
```c
int gcd(int a, int b) { // greatest common denominator
while (b) {
Expand All @@ -143,8 +143,8 @@ int gcd(int a, int b) { // greatest common denominator
struct gcd1_triples {
int max_n, max_c, count; // input: max_n, max_c limit #triples to be generated.
struct triples tri; // triples call frame
int cco_state;
struct triples tri; // triples call frame
cco_state cco; // required
};
int gcd1_triples(struct gcd1_triples* i)
Expand Down Expand Up @@ -206,10 +206,11 @@ by awaiting a few seconds before producing a number, using a timer.
#include "stc/cstr.h"
#include "stc/coroutine.h"

cco_task_struct (next_value,
cco_task_struct (next_value) {
next_value_state cco;
int val;
cco_timer tm;
);
};

int next_value(struct next_value* co, cco_runtime* rt)
{
Expand All @@ -232,16 +233,17 @@ void print_time()
}

// PRODUCER
cco_task_struct (produce_items,
cco_task_struct (produce_items) {
produce_items_state cco;
struct next_value next;
cstr text;
);
};

int produce_items(struct produce_items* p, cco_runtime* rt)
{
cco_scope (p) {
p->text = cstr_init();
p->next.cco_func = next_value;
p->next.cco.func = next_value;
while (true)
{
// await for CCO_YIELD (or CCO_DONE)
Expand All @@ -259,15 +261,16 @@ int produce_items(struct produce_items* p, cco_runtime* rt)
}

// CONSUMER
cco_task_struct (consume_items,
cco_task_struct (consume_items) {
consume_items_state cco;
int n, i;
struct produce_items produce;
);
};

int consume_items(struct consume_items* c, cco_runtime* rt)
{
cco_scope (c) {
c->produce.cco_func = produce_items;
c->produce.cco.func = produce_items;

for (c->i = 1; c->i <= c->n; ++c->i)
{
Expand All @@ -287,8 +290,8 @@ int consume_items(struct consume_items* c, cco_runtime* rt)
int main(void)
{
struct consume_items consume = {
.cco = {consume_items},
.n = 5,
.cco_func = consume_items,
};
cco_run_task(&consume);
}
Expand Down
65 changes: 38 additions & 27 deletions include/stc/coroutine.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
#include "stc/coroutine.h"
struct iterpair {
cco_state cco; // required member
int max_x, max_y;
int x, y;
int cco_state; // required member
};
int iterpair(struct iterpair* I) {
Expand Down Expand Up @@ -69,13 +69,17 @@ typedef enum {
CCO_YIELD = 1<<30,
} cco_result;

#define cco_initial(co) ((co)->cco_state == CCO_STATE_INIT)
#define cco_suspended(co) ((co)->cco_state > CCO_STATE_INIT)
#define cco_done(co) ((co)->cco_state == CCO_STATE_DONE)
#define cco_active(co) ((co)->cco_state != CCO_STATE_DONE)
typedef struct {
int state;
} cco_state;

#define cco_initial(co) ((co)->cco.state == CCO_STATE_INIT)
#define cco_suspended(co) ((co)->cco.state > CCO_STATE_INIT)
#define cco_done(co) ((co)->cco.state == CCO_STATE_DONE)
#define cco_active(co) ((co)->cco.state != CCO_STATE_DONE)

#define cco_scope(co) \
for (int* _state = &(co)->cco_state; *_state != CCO_STATE_DONE; *_state = CCO_STATE_DONE) \
for (int* _state = &(co)->cco.state; *_state != CCO_STATE_DONE; *_state = CCO_STATE_DONE) \
_resume: switch (*_state) case CCO_STATE_INIT: // thanks, @liigo!
#define cco_routine cco_scope // [deprecated]

Expand Down Expand Up @@ -126,10 +130,10 @@ typedef enum {
} while (0)

#define cco_stop(co) \
((co)->cco_state = (co)->cco_state >= CCO_STATE_INIT ? CCO_STATE_FINAL : CCO_STATE_DONE)
((co)->cco.state = (co)->cco.state >= CCO_STATE_INIT ? CCO_STATE_FINAL : CCO_STATE_DONE)

#define cco_reset(co) \
(void)((co)->cco_state = 0)
(void)((co)->cco.state = 0)


/* ============ ADVANCED, OPTIONAL ============= */
Expand All @@ -150,18 +154,23 @@ typedef enum {
printf("%d ", *i.ref);
*/

#define cco_iter_struct(Gen, ...) \
#define cco_iter_struct(Gen) \
typedef Gen Gen##_value; \
typedef struct Gen##_iter { \
Gen##_value* ref; \
int cco_state; \
__VA_ARGS__ \
} Gen##_iter
typedef struct Gen##_iter Gen##_iter; \
struct Gen##_iter

#define cco_default_begin(Gen) \
Gen##_iter Gen##_begin(Gen* g) { \
Gen##_iter it = {.ref = g}; \
Gen##_next(&it); \
return it; \
} struct Gen##_iter


/* Using c_filter with generators:
*/
#define cco_flt_take(n) (c_flt_take(n), _base.done ? _it.cco_state = CCO_STATE_FINAL : 1)
#define cco_flt_takewhile(pred) (c_flt_takewhile(pred), _base.done ? _it.cco_state = CCO_STATE_FINAL : 1)
#define cco_flt_take(n) (c_flt_take(n), _base.done ? _it.cco.state = CCO_STATE_FINAL : 1)
#define cco_flt_takewhile(pred) (c_flt_takewhile(pred), _base.done ? _it.cco.state = CCO_STATE_FINAL : 1)


/*
Expand All @@ -170,31 +179,33 @@ typedef enum {

struct cco_runtime;

#define cco_task_struct(Task, ...) \
struct Task { \
int (*cco_func)(struct Task*, struct cco_runtime*); \
int cco_state, cco_expect; \
__VA_ARGS__ \
}
#define cco_task_struct(Task) \
struct Task; \
typedef struct { \
int (*func)(struct Task*, struct cco_runtime*); \
int state, expect; \
} Task##_state; \
struct Task

typedef cco_task_struct(cco_task, /**/) cco_task; /* Define base Task struct type */
cco_task_struct(cco_task) { cco_task_state cco; }; /* Define base Task struct type */
typedef struct cco_task cco_task;

typedef struct cco_runtime {
int result, top;
struct cco_task* stack[];
} cco_runtime;

#define cco_cast_task(task) \
((struct cco_task *)(task) + 0*sizeof((task)->cco_func(task, (cco_runtime*)0) + ((int*)0 == &(task)->cco_state)))
((struct cco_task *)(task) + 0*sizeof((task)->cco.func(task, (cco_runtime*)0) + ((int*)0 == &(task)->cco.state)))

#define cco_resume_task(task, rt) \
(task)->cco_func(task, rt)
(task)->cco.func(task, rt)

#define cco_await_task(...) c_MACRO_OVERLOAD(cco_await_task, __VA_ARGS__)
#define cco_await_task_2(task, rt) cco_await_task_3(task, rt, CCO_DONE)
#define cco_await_task_3(task, rt, awaitbits) \
do { \
((rt)->stack[++(rt)->top] = cco_cast_task(task))->cco_expect = (awaitbits); \
((rt)->stack[++(rt)->top] = cco_cast_task(task))->cco.expect = (awaitbits); \
cco_yield_v(CCO_AWAIT); \
} while (0)

Expand All @@ -203,7 +214,7 @@ typedef struct cco_runtime {
#define cco_run_task_3(task, rt, STACKDEPTH) \
for (struct { int result, top; struct cco_task* stack[STACKDEPTH]; } rt = {.stack={cco_cast_task(task)}}; \
(((rt.result = cco_resume_task(rt.stack[rt.top], (cco_runtime*)&rt)) & \
~rt.stack[rt.top]->cco_expect) || --rt.top >= 0); )
~rt.stack[rt.top]->cco.expect) || --rt.top >= 0); )

/*
* Iterate containers with already defined iterator (prefer to use in coroutines only):
Expand Down
4 changes: 2 additions & 2 deletions misc/examples/coroutines/cointerleave.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
struct GenValue {
IVec *v;
IVec_iter it;
int cco_state;
cco_state cco;
};

static long get_value(struct GenValue* g)
Expand All @@ -21,9 +21,9 @@ static long get_value(struct GenValue* g)

struct Generator {
struct GenValue x, y;
int cco_state;
bool xact, yact;
long value;
cco_state cco;
};

int interleaved(struct Generator* g)
Expand Down
4 changes: 2 additions & 2 deletions misc/examples/coroutines/coread.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

struct file_read {
const char* filename;
int cco_state;
FILE* fp;
cstr line;
cco_state cco;
};

int file_read(struct file_read* g)
Expand All @@ -31,7 +31,7 @@ int file_read(struct file_read* g)

int main(void)
{
struct file_read g = {__FILE__};
struct file_read g = {.filename=__FILE__};
int n = 0;
cco_run_coroutine(file_read(&g))
{
Expand Down
6 changes: 3 additions & 3 deletions misc/examples/coroutines/coroutines.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ bool is_prime(long long i) {
struct prime {
int count;
long long value;
int cco_state;
cco_state cco;
};

enum { YIELD_PRM = 1<<0, YIELD_FIB = 1<<1};
Expand Down Expand Up @@ -46,7 +46,7 @@ int prime(struct prime* g) {
struct fibonacci {
int count;
long long value, b;
int cco_state;
cco_state cco;
};

int fibonacci(struct fibonacci* g) {
Expand Down Expand Up @@ -75,7 +75,7 @@ int fibonacci(struct fibonacci* g) {
struct combined {
struct prime prm;
struct fibonacci fib;
int cco_state;
cco_state cco;
};

int sequenced(struct combined* g) {
Expand Down
6 changes: 3 additions & 3 deletions misc/examples/coroutines/cotasks1.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

struct next_value {
int val;
int cco_state;
cco_timer tm;
cco_state cco;
};

int next_value(struct next_value* co)
Expand Down Expand Up @@ -36,7 +36,7 @@ void print_time(void)
struct produce_items {
struct next_value next;
cstr text;
int cco_state;
cco_state cco;
};

int produce_items(struct produce_items* p)
Expand All @@ -62,7 +62,7 @@ int produce_items(struct produce_items* p)

struct consume_items {
int n, i;
int cco_state;
cco_state cco;
};

int consume_items(struct consume_items* c, struct produce_items* p)
Expand Down
Loading

0 comments on commit 2375473

Please sign in to comment.