// TO DO: use pthreads/* and dequeue-phreads.c #include <stdio.h> #include <stdlib.h> /* how to handle custom blocks don't try to mirror the procedure structure like I've been doing here - instead, make all the code flat, regardless of which sprite or hat block or custom block it is in - then treat the whole shebang like an emulator, or more accurately a static binary translation, with procedure calling being just another emulated instruction - one which *doesn't* cause a real procedure call on the CPU's stack, but which only uses an explicit soft return address stack and possibly a parameter stack. Remembering that each thread (clone) may need its own stack... Maybe use a linked list instead of an array for the stack, which would allow multiple stacks to be entwined. (Use the old trick of keeping a freelist inside the stack cells. Needs a single atomic allocate and free. Can we use omp for this? Take advantage of multiple cores? */ // Experimenting with calling custom blocks as coroutines. It may // work because there are no local variables except the parameters. // Also can extend this code to write a queue scheduler for simulations. // TO DO: wait(0) waits for end of frame // wait(n) waits for abs time currenttime+n // waiting process does not keep checking the clock. instead they should go // into an ordered queue, and only the top of the queue is checked, so that // anything due to run later than the head of the queue doesn't need to be checked. // (be careful with multiple events scheduled for the same time) // do we have a run queue and a waiting queue, or should there only be one queue? typedef void COROUTINE (int my_clone_id); typedef struct { COROUTINE *proc; int clone_id; } QUEUE; static int next_clone_id = 0; QUEUE queue[100]; // should be off the heap, and realloc (x*2)+1 units whenever it's full void start(COROUTINE x) { queue[next_clone_id].proc = x; queue[next_clone_id].clone_id = next_clone_id; next_clone_id++; } void run_a_little(int clone_id) { queue[clone_id].proc(clone_id); } #define _enter_ \ static int lastline = 0; \ switch(lastline) { \ case 0: #define _yield_ \ lastline = __LINE__; \ goto done; \ case __LINE__: // return has to do something to cause the program flow to revert to the original caller. // perhaps when making the call, take the caller out of the queue altogether, and when // returning, put the caller back in at the head of the queue? // clone IDs will be significant in this process to avoid fuck ups where two threads are // calling the same block - don't want them to return to the wrong caller! #define _return_(_results_) do {_results_; return;} while (0) /* TO DO: still need a way to return to caller on final exit */ #define _exit_(_results_) \ } \ done: \ _return_(_results_) static int proc1_i; void proc1_gf_1(int my_clone_id) { _enter_; printf("proc1 starting\n"); proc1_i = 1; for (;;) { proc1_i += 1; printf("proc1 executing %d\n", proc1_i); _yield_; // placed at the foot of every loop } _exit_(); } void push(int p1) { // TO DO } int pop(void) { return 42; // TO DO } static int fib_param_n; static int fib_result; static int fib_localvar_temp1, fib_localvar_temp2; void fib_init(int n) { push(fib_param_n); fib_param_n = n; // preserve procedure parameters for recursion } void fib(int n); void fib_body(int my_clone_id) // pseudo-automatic translation of Scratch code { _enter_; if ( fib_param_n == 0 ) { fib_result = 0; _return_(fib_param_n = pop()); } else if (fib_param_n == 1 ) { fib_result = 1; _return_(fib_param_n = pop()); } else { fib(fib_param_n-1); fib_localvar_temp1 = fib_result; _yield_; fib(fib_param_n-2); fib_localvar_temp2 = fib_result; _yield_; fib_result = ( fib_localvar_temp1 + fib_localvar_temp2 ); // will this actually timeslice or do I need to yield *after* each procedure call too? _return_(fib_param_n = pop()); } _exit_(fib_param_n = pop()); // restore any procedure parameters } // fib is not a good example but it's a good awkward test case! void fib(int n) { fib_init(n); // set up parameters start(fib_body); // TO DO: block until the coroutine exits } static int proc2_i; void proc1_gf_2(int my_close_id) { _enter_; printf("proc2 starting\n"); proc2_i = 1; for (;;) { fib(38); _yield_; proc2_i += 1; printf("proc2 executing %d, fib(38) = %d\n", proc2_i, fib_result); _yield_; // placed at the foot of every loop (and after every procedure call? Or before?) } _exit_(); } // there's something wrong with this. Surely we need a stack of state for recursive calls? // Or is it all implicit? int main(int argc, char **argv) { int clone; start(proc1_gf_1); start(proc1_gf_2); for (;;) { // main loop for (clone = 0; clone < next_clone_id; clone++) { run_a_little(clone); } } exit(0); return(0); }