# now, time for channels! using namespace std use deque use stdio.h deque q typedef int (*process_func)(process *p) struct process process_func f int pc inline void start(process *p) q.push_back(p) inline int resume(process *p) int rv = (*p->f)(p) if rv p->pc = rv return rv struct simple_int_channel int value process *waiting # I will invent a keyword "next", which starts at 0 at the start of the function, # and increments after it is used in a label. This will enable to write # different coroutine ops such as yield, read, write as macros. # perhaps "next" is a bit too common a word to use though! # How about nextpc or pcnext? # I was thinking that the channel should be "full" all the time, except just # before the rendezvous. I've changed my mind, the channel should be "empty" # all the time, except during the rendezvous. Hmm, but if the channel were a # big static buffer, that would suck, it would have to be copied. Ok, so I'll # go back to the original idea, the channel should be "full" all the time, # except just before the rendezvous (i.e. when we know the receiver is waiting # for new data). # It might be good to have a "counted" wait, e.g. if we are waiting for three # things at once, and need all of them to be ready before we continue. Should # such a "counted wait" be implemented in the coroutine or in the scheduler? # will have to consider how to implement "child" or "sub-" processes # this is the "producer" # I am going to try putting the process structure inside the "data" structure, # this is a bit like C++ does inheritance. The sheduler's pointers to it # should still work. # I should implement plan9 cc's quasi-inheritance anon/transparent struct # inclusion thing in nipl. Will be easy to do once I've done namespaces. # Should do automatic dereffing for the component structures too. struct count_to_three process p simple_int_channel *out int count_to_three_f(process *p) count_to_three *d = (count_to_three *)p switch p->pc 0 if !d->out->waiting p->pc = 1 d->out->waiting = p return 0 1 d->out->value = 1 start(d->out->waiting) d->out->waiting = NULL if !d->out->waiting p->pc = 2 d->out->waiting = p return 0 2 d->out->value = 2 start(d->out->waiting) d->out->waiting = NULL if !d->out->waiting p->pc = 3 d->out->waiting = p return 0 3 d->out->value = 3 start(d->out->waiting) d->out->waiting = NULL return 0 # Could "optimise" this, the waiting = NULL ; if !waiting seems a bit dumb # Hopefully the compiler will manage to do that... # this is the "consumer" struct printer process p simple_int_channel *in int printer_f(process *p) printer *d = (printer *)p switch p->pc 0 while 1 if d->in->waiting start(d->in->waiting) p->pc = 1 d->in->waiting = p return 0 1 printf("%d\n", d->in->value) return 0 # My rendezvous code above puts the waiting processes back into the main # schedule queue, it doesn't run them right away. Perhaps it should. # no. that would be premature optimisation. struct count_to_n process p simple_int_channel *out int n int c int count_to_n_f(process *p) count_to_n *d = (count_to_n *)p switch p->pc 0 d->c = 1 while d->c <= d->n if !d->out->waiting p->pc = 1 d->out->waiting = p return 0 1 d->out->value = d->c start(d->out->waiting) d->out->waiting = NULL # I could count directly on the channel; # c is unnecessary - I'll try it that way next time ++ (d->c) return 0 # we don't need to call yield() in a loop that calls get or put # what to call them? read / write, get / put, send / receive, push / pull # I think I like get, put, nice and short. # next, use C++ to initialise the channels and processes # separate out "read_to_write" and "ready_to_read" channel methods run() while !q.empty() step() step() process *p = q.front() q.pop_front() if resume(p) q.push_back(p) int main() simple_int_channel ch ; ch.waiting = NULL count_to_three c3 = { { count_to_three_f, 0 }, &ch } printer pr = { { printer_f, 0 }, &ch } start(&c3.p) start(&pr.p) run() count_to_n cn = { { count_to_n_f, 0 }, &ch, 15 } start(&cn.p) run() return 0