/*
 * Horrid example of an in-process cooperative multi-tasking scheduler
 * using GNU C label addresses, local stacks and a process table. Also
 * using select() for possible extensions with timers, events and
 * whatnot.
 *
 * Possible enchancements include making initial checks and the goto
 * for each 'program' into some form of macro and adding some form of
 * registration service in the scheduler to wait for I/O and
 * stuff. Left as an exercise to the interested reader.
 *
 * M.C. Widerkrantz, mc at hack.org, http://hack.org/mc/
 * 2004-03-19 (2)
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>

/* Values plucked out of the blue. Change at will. */

#define STACKDEPTH 1024
#define MAXPROC 17

/* Some shorthand macros. Way too bothersome to type. */

#define push(x) proc[pid].stack[proc[pid].top ++] = x
#define pop() *proc[pid].stack[(proc[pid].top --) - 1]
#define top() proc[pid].top

/* A function pointer type. */

typedef void (*funp_t)();

/********** Globals **********/

#define NOPROC -1
int maxpid = NOPROC; 

/* Process table. */

struct process
{
    funp_t funp;
    int top;
    void *stack[STACKDEPTH];
} proc[MAXPROC];


/********** Helper functions **********/

int newprocess(funp_t funp)
{
    if (maxpid < MAXPROC)
    {
        maxpid ++;
        proc[maxpid].funp = funp;
        proc[maxpid].top = 0;
    }
    else
    {
        /* Obviously, a little more graceful form of recovery would
         * be nice... */
        printf("Process table full. Exiting...\n");
        exit(1);
    }

    return maxpid;
}


/**********The 'programs' **********/

void A()
{
    static int pid = NOPROC;
    
    /* Does this function have a running process? */

    if (NOPROC != pid)
    {
        /* Check for stack underflow. */

        if (0 == top())
        {
            return;
        }

        /*
         * This is the 'interesting' goto. Pops stack for label
         * address.
         */

        goto pop();
        
    }
    else
    {
        /* Start new process. */
        pid = newprocess(A);
    }

 start:
    puts("A start");
    push(&&inbetween);
    return;
 inbetween:
    puts("A inbetween");
    push(&&later);
    return;
 later:
    puts("A later");
    return;
}

/*
 * Another program. Could be entirely different, but I'm too lazy to change it much. 
 */
void B()
{
    static int pid = NOPROC;
    
    /* Does this function have a running process? */

    if (NOPROC != pid)
    {
        /* Check for stack underflow. */

        if (0 == top())
        {
            return;
        }

        /* Pop stack for label address and goto it. */

        goto pop();
        
    }
    else
    {
        /* Start new process. */
        pid = newprocess(B);
    }

 start:
    puts("B start");
    push(&&inbetween);
    return;
 inbetween:
    puts("B inbetween");
    push(&&later);
    return;
 later:
    puts("B later");
    return;
}


int main(void)
{
    struct timeval tv;
    int found, i;

    /* 
     * A NULL funp means the process is unallocated, so initalize all
     * processes to be unallocated.
     */

    for (i = 0; i <= MAXPROC; i ++)
    {
        proc[i].funp = NULL;
    }

    /* Call some functions, i.e. start some processes. */
    
    A();
    B();
    
    /* The Scheduler, Idle Loop, Whatever */
    
    while (1)
    {
        /*
         * Fall through once each second. Could be expanded to hilariously
         * complex things.
         */

        tv.tv_sec = 1L;
        tv.tv_usec = 0L;
    
        found = select(1, NULL, NULL, NULL, &tv);
        if (0 < found)
        {
            perror("select");
        }

        /* 
         * Go through process table, check for waiting I/O, give them
         * some share of the CPU, that sort of stuff.  For now, just
         * call the function.
         */

        for (i = 0; i <= MAXPROC; i ++)
        {
            if (NULL != proc[i].funp)
            {
                printf("  serving process %d\n", i);
                proc[i].funp();
            }
        }
    }
    
    exit(0);
}
