diff options
Diffstat (limited to 'misc/examples/coroutines')
| -rw-r--r-- | misc/examples/coroutines/cointerleave.c | 62 | ||||
| -rw-r--r-- | misc/examples/coroutines/coread.c | 41 | ||||
| -rw-r--r-- | misc/examples/coroutines/coroutines.c | 112 | ||||
| -rw-r--r-- | misc/examples/coroutines/cotasks1.c | 100 | ||||
| -rw-r--r-- | misc/examples/coroutines/cotasks2.c | 98 | ||||
| -rw-r--r-- | misc/examples/coroutines/dining_philosophers.c | 105 | ||||
| -rw-r--r-- | misc/examples/coroutines/filetask.c | 80 | ||||
| -rw-r--r-- | misc/examples/coroutines/generator.c | 66 | ||||
| -rw-r--r-- | misc/examples/coroutines/scheduler.c | 67 | ||||
| -rw-r--r-- | misc/examples/coroutines/triples.c | 75 |
10 files changed, 806 insertions, 0 deletions
diff --git a/misc/examples/coroutines/cointerleave.c b/misc/examples/coroutines/cointerleave.c new file mode 100644 index 00000000..80494176 --- /dev/null +++ b/misc/examples/coroutines/cointerleave.c @@ -0,0 +1,62 @@ +// https://www.youtube.com/watch?v=8sEe-4tig_A +#include <stdio.h> +#include <stc/coroutine.h> +#define i_type IVec +#define i_key int +#include <stc/cvec.h> + +struct GenValue { + IVec *v; + IVec_iter it; + int cco_state; +}; + +static int get_value(struct GenValue* g) +{ + cco_routine(g) { + for (g->it = IVec_begin(g->v); g->it.ref; IVec_next(&g->it)) + cco_yield_v(*g->it.ref); + } + return -1; +} + +struct Generator { + struct GenValue x, y; + int cco_state; + int value; +}; + +cco_result interleaved(struct Generator* g) +{ + cco_routine(g) { + while (!(cco_done(&g->x) & cco_done(&g->y))) { + g->value = get_value(&g->x); + if (!cco_done(&g->x)) + cco_yield(); + + g->value = get_value(&g->y); + if (!cco_done(&g->y)) + cco_yield(); + } + } + return CCO_DONE; +} + +void Use(void) +{ + IVec a = c_init(IVec, {2, 4, 6, 8, 10, 11}); + IVec b = c_init(IVec, {3, 5, 7, 9}); + + struct Generator g = {{&a}, {&b}}; + + cco_blocking_call(interleaved(&g)) { + printf("%d ", g.value); + } + puts(""); + c_drop(IVec, &a, &b); +} + +int main(void) +{ + Use(); +} diff --git a/misc/examples/coroutines/coread.c b/misc/examples/coroutines/coread.c new file mode 100644 index 00000000..6d3acdd7 --- /dev/null +++ b/misc/examples/coroutines/coread.c @@ -0,0 +1,41 @@ +#define i_implement +#include <stc/cstr.h> +#include <stc/coroutine.h> +#include <errno.h> + +// Read file line by line using coroutines: + +struct file_read { + const char* filename; + int cco_state; + FILE* fp; + cstr line; +}; + +int file_read(struct file_read* g) +{ + cco_routine(g) { + g->fp = fopen(g->filename, "r"); + if (!g->fp) cco_return; + g->line = cstr_init(); + + cco_await(!cstr_getline(&g->line, g->fp)); + + cco_final: + printf("finish\n"); + cstr_drop(&g->line); + if (g->fp) fclose(g->fp); + } + return 0; +} + +int main(void) +{ + struct file_read g = {__FILE__}; + int n = 0; + cco_blocking_call(file_read(&g)) + { + printf("%3d %s\n", ++n, cstr_str(&g.line)); + //if (n == 10) cco_stop(&g); + } +} diff --git a/misc/examples/coroutines/coroutines.c b/misc/examples/coroutines/coroutines.c new file mode 100644 index 00000000..802a976a --- /dev/null +++ b/misc/examples/coroutines/coroutines.c @@ -0,0 +1,112 @@ +#include <stc/coroutine.h> +#include <stdio.h> +#include <stdint.h> + +// Demonstrate to call another coroutine from a coroutine: +// First create prime generator, then call fibonacci sequence: + +bool is_prime(long long i) { + for (long long j=2; j*j <= i; ++j) + if (i % j == 0) return false; + return true; +} + +struct prime { + int count, idx; + long long result, pos; + int cco_state; +}; + +int prime(struct prime* g) { + cco_routine(g) { + if (g->result < 2) g->result = 2; + if (g->result == 2) { + if (g->count-- == 0) cco_return; + ++g->idx; + cco_yield(); + } + g->result += !(g->result & 1); + for (g->pos = g->result; g->count > 0; g->pos += 2) { + if (is_prime(g->pos)) { + --g->count; + ++g->idx; + g->result = g->pos; + cco_yield(); + } + } + cco_final: + printf("final prm\n"); + } + return 0; +} + + +// Use coroutine to create a fibonacci sequence generator: + +struct fibonacci { + int count, idx; + long long result, b; + int cco_state; +}; + +int fibonacci(struct fibonacci* g) { + assert(g->count < 94); + + long long sum; + cco_routine(g) { + g->idx = 0; + g->result = 0; + g->b = 1; + for (;;) { + if (g->count-- == 0) + cco_return; + if (++g->idx > 1) { + // NB! locals lasts only until next yield/await! + sum = g->result + g->b; + g->result = g->b; + g->b = sum; + } + cco_yield(); + } + cco_final: + printf("final fib\n"); + } + return 0; +} + +// Combine + +struct combined { + struct prime prm; + struct fibonacci fib; + int cco_state; +}; + +int combined(struct combined* g) { + cco_routine(g) { + cco_await_call(prime(&g->prm)); + cco_await_call(fibonacci(&g->fib)); + + // Reuse the g->prm context and extend the count: + g->prm.count = 8, g->prm.result += 2; + cco_reset(&g->prm); + cco_await_call(prime(&g->prm)); + + cco_final: + puts("final combined"); + } + return 0; +} + +int main(void) +{ + struct combined c = {.prm={.count=8}, .fib={14}}; + int res; + + cco_blocking_call(res = combined(&c)) { + if (res == CCO_YIELD) + printf("Prime(%d)=%lld, Fib(%d)=%lld\n", + c.prm.idx, c.prm.result, + c.fib.idx, c.fib.result); + } +} diff --git a/misc/examples/coroutines/cotasks1.c b/misc/examples/coroutines/cotasks1.c new file mode 100644 index 00000000..cffd6620 --- /dev/null +++ b/misc/examples/coroutines/cotasks1.c @@ -0,0 +1,100 @@ +// https://mariusbancila.ro/blog/2020/06/22/a-cpp20-coroutine-example/ + +#include <time.h> +#include <stdio.h> +#define i_static +#include <stc/cstr.h> +#include <stc/coroutine.h> + +struct next_value { + int val; + int cco_state; + cco_timer tm; +}; + +int next_value(struct next_value* co) +{ + cco_routine (co) { + while (true) { + cco_await_timer(&co->tm, 1 + rand() % 2); + co->val = rand(); + cco_yield(); + } + } + return 0; +} + +void print_time() +{ + time_t now = time(NULL); + char mbstr[64]; + strftime(mbstr, sizeof(mbstr), "[%H:%M:%S]", localtime(&now)); + printf("%s ", mbstr); +} + +// PRODUCER + +struct produce_items { + struct next_value next; + cstr text; + int cco_state; +}; + +int produce_items(struct produce_items* p) +{ + cco_routine (p) { + p->text = cstr_init(); + while (true) + { + cco_await(next_value(&p->next) != CCO_AWAIT); + cstr_printf(&p->text, "item %d", p->next.val); + print_time(); + printf("produced %s\n", cstr_str(&p->text)); + cco_yield(); + } + cco_final: + cstr_drop(&p->text); + puts("done produce"); + } + return 0; +} + +// CONSUMER + +struct consume_items { + int n, i; + int cco_state; +}; + +int consume_items(struct consume_items* c, struct produce_items* p) +{ + cco_routine (c) { + for (c->i = 1; c->i <= c->n; ++c->i) + { + printf("consume #%d\n", c->i); + cco_await(produce_items(p) != CCO_AWAIT); + print_time(); + printf("consumed %s\n", cstr_str(&p->text)); + } + cco_final: + puts("done consume"); + } + return 0; +} + +int main(void) +{ + struct produce_items produce = {0}; + struct consume_items consume = {.n=5}; + int count = 0; + + cco_blocking_call(consume_items(&consume, &produce)) + { + ++count; + //cco_sleep(0.001); + //if (consume.i == 3) cco_stop(&consume); + } + cco_stop(&produce); + produce_items(&produce); + printf("count: %d\n", count); +}
\ No newline at end of file diff --git a/misc/examples/coroutines/cotasks2.c b/misc/examples/coroutines/cotasks2.c new file mode 100644 index 00000000..558df118 --- /dev/null +++ b/misc/examples/coroutines/cotasks2.c @@ -0,0 +1,98 @@ +// https://mariusbancila.ro/blog/2020/06/22/a-cpp20-coroutine-example/ + +#include <time.h> +#include <stdio.h> +#define i_static +#include <stc/cstr.h> +#include <stc/coroutine.h> + +cco_task_struct (next_value, + int val; + cco_timer tm; +); + +int next_value(struct next_value* co, cco_runtime* rt) +{ + cco_routine (co) { + while (true) { + cco_await_timer(&co->tm, 1 + rand() % 2); + co->val = rand(); + cco_yield(); + } + } + return 0; +} + +void print_time() +{ + time_t now = time(NULL); + char mbstr[64]; + strftime(mbstr, sizeof(mbstr), "[%H:%M:%S]", localtime(&now)); + printf("%s ", mbstr); +} + +// PRODUCER + +cco_task_struct (produce_items, + struct next_value next; + cstr text; +); + +int produce_items(struct produce_items* p, cco_runtime* rt) +{ + cco_routine (p) { + p->text = cstr_init(); + p->next.cco_func = next_value; + while (true) + { + // await for next CCO_YIELD (or CCO_DONE) in next_value + cco_await_task(&p->next, rt, CCO_YIELD); + cstr_printf(&p->text, "item %d", p->next.val); + print_time(); + printf("produced %s\n", cstr_str(&p->text)); + cco_yield(); + } + + cco_final: + cstr_drop(&p->text); + puts("done produce"); + } + return 0; +} + +// CONSUMER + +cco_task_struct (consume_items, + int n, i; + struct produce_items produce; +); + +int consume_items(struct consume_items* c, cco_runtime* rt) +{ + cco_routine (c) { + c->produce.cco_func = produce_items; + + for (c->i = 1; c->i <= c->n; ++c->i) + { + printf("consume #%d\n", c->i); + cco_await_task(&c->produce, rt, CCO_YIELD); + print_time(); + printf("consumed %s\n", cstr_str(&c->produce.text)); + } + + cco_final: + cco_stop(&c->produce); + cco_resume_task(&c->produce, rt); + puts("done consume"); + } + return 0; +} + +int main(void) +{ + struct consume_items consume = { + .cco_func = consume_items, + .n = 5, + }; + cco_blocking_task(&consume); +} diff --git a/misc/examples/coroutines/dining_philosophers.c b/misc/examples/coroutines/dining_philosophers.c new file mode 100644 index 00000000..d353b3b9 --- /dev/null +++ b/misc/examples/coroutines/dining_philosophers.c @@ -0,0 +1,105 @@ +// https://en.wikipedia.org/wiki/Dining_philosophers_problem +#include <stdio.h> +#include <time.h> +#include <stc/crand.h> +#include <stc/coroutine.h> + +// Define the number of philosophers and forks +enum { + num_philosophers = 5, + num_forks = num_philosophers, +}; + +struct Philosopher { + int id; + cco_timer tm; + cco_sem* left_fork; + cco_sem* right_fork; + int cco_state; // required +}; + +struct Dining { + // Define semaphores for the forks + cco_sem forks[num_forks]; + struct Philosopher ph[num_philosophers]; + int cco_state; // required +}; + + +// Philosopher coroutine +int philosopher(struct Philosopher* p) +{ + double duration; + cco_routine(p) { + while (1) { + duration = 1.0 + crandf()*2.0; + printf("Philosopher %d is thinking for %.0f minutes...\n", p->id, duration*10); + cco_await_timer(&p->tm, duration); + + printf("Philosopher %d is hungry...\n", p->id); + cco_await_sem(p->left_fork); + cco_await_sem(p->right_fork); + + duration = 0.5 + crandf(); + printf("Philosopher %d is eating for %.0f minutes...\n", p->id, duration*10); + cco_await_timer(&p->tm, duration); + + cco_sem_release(p->left_fork); + cco_sem_release(p->right_fork); + } + + cco_final: + printf("Philosopher %d finished\n", p->id); + } + return 0; +} + + +// Dining coroutine +int dining(struct Dining* d) +{ + cco_routine(d) { + for (int i = 0; i < num_forks; ++i) + cco_sem_set(&d->forks[i], 1); // all forks available + for (int i = 0; i < num_philosophers; ++i) { + cco_reset(&d->ph[i]); + d->ph[i].id = i + 1; + d->ph[i].left_fork = &d->forks[i]; + d->ph[i].right_fork = &d->forks[(i + 1) % num_forks]; + } + + while (1) { + // per-"frame" logic resume each philosopher + for (int i = 0; i < num_philosophers; ++i) { + philosopher(&d->ph[i]); + } + cco_yield(); // suspend, return control back to main + } + + cco_final: + for (int i = 0; i < num_philosophers; ++i) { + cco_stop(&d->ph[i]); + philosopher(&d->ph[i]); + } + puts("Dining finished"); + } + return 0; +} + +int main(void) +{ + struct Dining dine; + cco_reset(&dine); + int n=0; + cco_timer tm = cco_timer_from(15.0); // seconds + csrand((uint64_t)time(NULL)); + + cco_blocking_call(dining(&dine)) + { + if (cco_timer_expired(&tm)) + cco_stop(&dine); + cco_sleep(0.001); + ++n; + } + printf("n=%d\n", n); +} diff --git a/misc/examples/coroutines/filetask.c b/misc/examples/coroutines/filetask.c new file mode 100644 index 00000000..9650cb60 --- /dev/null +++ b/misc/examples/coroutines/filetask.c @@ -0,0 +1,80 @@ +// https://github.com/lewissbaker/cppcoro#taskt + +#include <time.h> +#include <stdio.h> +#define i_static +#include <stc/cstr.h> +#include <stc/coroutine.h> + +cco_task_struct(file_read, + const char* path; + cstr line; + FILE* fp; + cco_timer tm; +); + +int file_read(struct file_read* co, cco_runtime* rt) +{ + cco_routine (co) { + co->fp = fopen(co->path, "r"); + co->line = cstr_init(); + + while (true) { + // emulate async io: await 10ms per line + cco_await_timer(&co->tm, 0.010); + + if (!cstr_getline(&co->line, co->fp)) + break; + cco_yield(); + } + + cco_final: + fclose(co->fp); + cstr_drop(&co->line); + puts("done file_read"); + } + return 0; +} + +cco_task_struct(count_line, + cstr path; + struct file_read reader; + int lineCount; +); + +int count_line(struct count_line* co, cco_runtime* rt) +{ + cco_routine (co) { + co->reader.cco_func = file_read; + co->reader.path = cstr_str(&co->path); + while (true) + { + // await for next CCO_YIELD (or CCO_DONE) in file_read() + cco_await_task(&co->reader, rt, CCO_YIELD); + if (rt->result == CCO_DONE) break; + co->lineCount += 1; + cco_yield(); + } + + cco_final: + cstr_drop(&co->path); + puts("done count_line"); + } + return 0; +} + +int main(void) +{ + // Creates a new task + struct count_line countTask = { + .cco_func = count_line, + .path = cstr_from(__FILE__), + }; + + // Execute coroutine as top-level blocking + int loop = 0; + cco_blocking_task(&countTask) { ++loop; } + + printf("line count = %d\n", countTask.lineCount); + printf("exec count = %d\n", loop); +} diff --git a/misc/examples/coroutines/generator.c b/misc/examples/coroutines/generator.c new file mode 100644 index 00000000..96498498 --- /dev/null +++ b/misc/examples/coroutines/generator.c @@ -0,0 +1,66 @@ +// https://quuxplusone.github.io/blog/2019/03/06/pythagorean-triples/ + +#include <stdio.h> +#include <stc/coroutine.h> +#include <stc/algorithm.h> + +typedef struct { + int max_triples; + int a, b, c; +} Triple; + +// Create an iterable generator over Triple with count items. +// Requires coroutine Triple_next() and function Triple_begin() to be defined. +cco_iter_struct(Triple, + int count; +); + +int Triple_next(Triple_iter* it) { + Triple* g = it->ref; // note: before cco_routine + cco_routine(it) + { + for (g->c = 5;; ++g->c) { + for (g->a = 1; g->a < g->c; ++g->a) { + for (g->b = g->a; g->b < g->c; ++g->b) { + if (g->a*g->a + g->b*g->b == g->c*g->c) { + if (it->count++ == g->max_triples) + cco_return; + cco_yield(); + } + } + } + } + cco_final: + it->ref = NULL; // stop the iterator + } + return 0; +} + +Triple_iter Triple_begin(Triple* g) { + Triple_iter it = {.ref=g}; + Triple_next(&it); + return it; +} + + +int main(void) +{ + puts("Pythagorean triples.\nGet max 200 triples with c < 50:"); + Triple triple = {.max_triples=200}; + + c_foreach (i, Triple, triple) { + if (i.ref->c < 50) + printf("%u: (%d, %d, %d)\n", i.count, i.ref->a, i.ref->b, i.ref->c); + else + cco_stop(&i); + } + + puts("\nGet the 10 first triples with odd a's and a <= 20:"); + c_forfilter (i, Triple, triple, + i.ref->a <= 20 && + (i.ref->a & 1) && + c_flt_take(i, 10) + ){ + printf("%d: (%d, %d, %d)\n", c_flt_getcount(i), i.ref->a, i.ref->b, i.ref->c); + } +} diff --git a/misc/examples/coroutines/scheduler.c b/misc/examples/coroutines/scheduler.c new file mode 100644 index 00000000..be1810d2 --- /dev/null +++ b/misc/examples/coroutines/scheduler.c @@ -0,0 +1,67 @@ +// https://www.youtube.com/watch?v=8sEe-4tig_A +#include <stdio.h> +#include <stc/coroutine.h> + +#define i_type cco_tasks +#define i_key cco_task* +#define i_keydrop(x) { puts("free task"); free(*x); } +#define i_no_clone +#include <stc/cqueue.h> + +typedef struct { + cco_tasks tasks; +} cco_scheduler; + +void cco_scheduler_drop(cco_scheduler* sched) { + cco_tasks_drop(&sched->tasks); +} + +int cco_scheduler_run(cco_scheduler* sched) { + while (!cco_tasks_empty(&sched->tasks)) { + cco_task* task = cco_tasks_pull(&sched->tasks); + if (cco_resume_task(task, NULL)) + cco_tasks_push(&sched->tasks, task); + else + cco_tasks_value_drop(&task); + } + return 0; +} + +static int taskA(cco_task* task, cco_runtime* rt) { + cco_routine(task) { + puts("Hello, from task A"); + cco_yield(); + puts("A is back doing work"); + cco_yield(); + puts("A is back doing more work"); + cco_yield(); + puts("A is back doing even more work"); + } + return 0; +} + +static int taskB(cco_task* task, cco_runtime* rt) { + cco_routine(task) { + puts("Hello, from task B"); + cco_yield(); + puts("B is back doing work"); + cco_yield(); + puts("B is back doing more work"); + } + return 0; +} + +void Use(void) { + cco_scheduler sched = {.tasks = c_init(cco_tasks, { + c_new(cco_task, {.cco_func=taskA}), + c_new(cco_task, {.cco_func=taskB}), + })}; + + cco_scheduler_run(&sched); + cco_scheduler_drop(&sched); +} + +int main(void) +{ + Use(); +} diff --git a/misc/examples/coroutines/triples.c b/misc/examples/coroutines/triples.c new file mode 100644 index 00000000..d6ce2791 --- /dev/null +++ b/misc/examples/coroutines/triples.c @@ -0,0 +1,75 @@ +// https://quuxplusone.github.io/blog/2019/03/06/pythagorean-triples/ + +#include <stdio.h> +#include <stc/coroutine.h> + +void triples_vanilla(int max_c) { + for (int c = 5, i = 0;; ++c) { + for (int a = 1; a < c; ++a) { + for (int b = a + 1; b < c; ++b) { + if ((int64_t)a*a + (int64_t)b*b == (int64_t)c*c) { + if (c > max_c) + goto done; + printf("%d: {%d, %d, %d}\n", ++i, a, b, c); + } + } + } + } + done:; +} + +struct triples { + int max_c; + int a, b, c; + int cco_state; +}; + +int triples_coro(struct triples* t) { + cco_routine(t) { + for (t->c = 5;; ++t->c) { + for (t->a = 1; t->a < t->c; ++t->a) { + for (t->b = t->a + 1; t->b < t->c; ++t->b) { + if ((int64_t)t->a * t->a + + (int64_t)t->b * t->b == + (int64_t)t->c * t->c) + { + if (t->c > t->max_c) + cco_return; + cco_yield(); + } + } + } + } + cco_final: + puts("done"); + } + return 0; +} + +int gcd(int a, int b) { + while (b) { + int t = a % b; + a = b; + b = t; + } + return a; +} + +int main(void) +{ + puts("Vanilla triples:"); + triples_vanilla(20); + + puts("\nCoroutine triples with GCD = 1:"); + struct triples t = {.max_c = 100}; + int n = 0; + + cco_blocking_call(triples_coro(&t)) { + if (gcd(t.a, t.b) > 1) + continue; + if (++n <= 20) + printf("%d: {%d, %d, %d}\n", n, t.a, t.b, t.c); + else + cco_stop(&t); + } +} |
