Listing for the Task Dispatcher in August DDJ

/********************************************************

  DISPATCH - A Task Dispatcher.

 A program to manage multiple tasks via a ready queue
 and multiple delays via a delay queue. Runs one task at
 a time. Tasks coming off the delay queue are put on the
 ready queue. Delay queue is ticked from timer interrupt.


 Ron Kreymborg
 11-Apr-99
 
********************************************************/

#define TOTALTASKS   10    // set for your application

void InitMulti(void);
int QueTask(void (*pt)());
int QueDelay(void (*pt)(), int delay);
void DecrementDelay(void);
void Dispatch(void);
void ReRunMe(int delay);

#define BusyBit       0x01   // task is busy
#define ReadyBit      0x02   // task is ready
#define DelayBit      0x04   // task is delayed

static void DoQueTask(void);
static void DoQueDelay(void);
static void RunTask(void);
static void GetNewTask(void);
static void DoDelay(int delay);

static struct {
   int     status;        // task status
   int      delay;         // delay in ticks
   void     (*pntr)();     // pointer to task
   int     next;          // linked list
   } task[TOTALTASKS+1];

static int RunningTask;
static int TaskHead, TailTask;
static int Status, NewTask;
static int *DelayHead;
static int NewDelay;
static void (*Tpntr)();


//**********************************************************
// Initialise the data structure. Must be called before
// first use.

void InitMulti(void) {
   int i;

   for (i=0; i<=TOTALTASKS; i++) {
      task[i].status = 0;
      task[i].pntr = 0;
      task[i].next = 0;
      }
   DelayHead = &task[0].next;
}


//**********************************************************
// Put a task on the tail of the ready list. Call with
// a pointer to the task as argument.

int QueTask(void (*pntr)()) {

   Tpntr = pntr;        // set task pointer
   GetNewTask();        // set NewTask 
   DoQueTask();         // put new task on queue
   return Status;       // return status
}


//**********************************************************
// Put a task in the delay queue such that it will come
// off after  clock ticks. Call with a pointer
// to the task as argument.

int QueDelay(void (*pntr)(), int delay) {

   Tpntr = pntr;        // set task pointer
   DoDelay(delay);      // set NewDelay
   GetNewTask();        // set NewTask
   if (NewDelay)
      DoQueDelay();     // put task on delay queue
   else
      DoQueTask();      // if zero just put on ready queue
   return Status;       // return status
}


//**********************************************************
// Run the task at the head of the ready queue, or just
// return.

void Dispatch(void) {

   if (TaskHead) {
      RunningTask = TaskHead;
      TaskHead = task[RunningTask].next;  // head is now next
      if (TaskHead == 0)                  // ensure tail is correct
         TailTask = 0;
      RunTask();                          // run the task
      }
}


//**********************************************************
// Call just before the running task returns to ensure the
// task is re-run or, if  is non-zero, when a task
// must be re-run after a delay.

void ReRunMe(int delay) {

   DoDelay(delay);            // set NewDelay
   NewTask = RunningTask;     // set NewTask
}


//**********************************************************
// Call from tick timer interrupt routine. Assumes interrupts
// are off. Decrements the delay of the task at the head of
// the delay queue. If it goes to zero takes it off the delay
// queue and puts it on the ready queue. Continues to do this
// until a non-zero delay is found.

void DecrementDelay(void) {

   if (*DelayHead) {
      task[*DelayHead].delay--;                 // decrement head delay
      while (*DelayHead && task[*DelayHead].delay == 0) {
         NewTask = *DelayHead;                  // set NewTask
         *DelayHead = task[*DelayHead].next;    // set head to next
         task[NewTask].status &= ~(BusyBit | DelayBit);
         Tpntr = task[NewTask].pntr;            // set task pointer
         DoQueTask();                           // copy to ready list
         }
      }
}



//**********************************************************
// Private functions begin.
//**********************************************************

// Take the task pointer  and insert it in the data
// structure at . Link from the tail of the queue
// to the  position. Ensure the head and tail pointers
// are correct. Set the  to busy and  to null.

static void DoQueTask() {
   
   if (NewTask > TOTALTASKS)
      Status = 0;
   else {
      // Is there a tail task?
      if (TailTask != 0)
         task[TailTask].next = NewTask;   // link tail to new
      else
         TaskHead = NewTask;              // else new is head
      TailTask = NewTask;                 // tail always new
      task[NewTask].next = 0;             // tail link always null
      task[NewTask].delay = 0;            // not really required
      task[NewTask].status |= (BusyBit | ReadyBit);
      task[NewTask].pntr = *Tpntr;        // insert task pointer
      Status = task[NewTask].status;      // set status
      }
      
   NewTask = 0;
}


//**********************************************************
// Take the task pointer  and insert it in the data
// structure at the correct point in the delay queue such
// that the delay for this queue position equals the sum of
// all earlier delays. Ensure the links to and from this
// queue position use the  list position.

static void DoQueDelay() {
   int Pntr0, Pntr1;
   int OldIntVal, IntVal;
   
   if (NewTask > TOTALTASKS)
      Status = 0;
   else {
      IntVal = NewDelay;                     // initial delay
      Pntr0 = *DelayHead;               // start from head
      Pntr1 = 0;

      // While not at end of list and a still positive delay.
      while (Pntr0 && IntVal > 0) {
         OldIntVal = IntVal;                 // save old interval
         IntVal -= task[Pntr0].delay;        // compute next
         // If interval is still positive
         if (IntVal > 0) {
            Pntr1 = Pntr0;                   // step down the queue
            Pntr0 = task[Pntr1].next;
            }
         }

      // We are either at the end of the queue ( == 0) or
      // at the correct place in the queue to insert this task.
      task[NewTask].next = Pntr0;      // link this task in
      task[Pntr1].next = NewTask;
      if (Pntr0 == 0)
         task[NewTask].delay = IntVal;  // we are at the end
      else {
         if (Pntr0 == Pntr1)
            task[Pntr1].next = 0;
         task[NewTask].delay = OldIntVal;    // fix up us
         task[Pntr0].delay = -IntVal;        // and the next entry
         }
      task[NewTask].status |= (BusyBit | DelayBit);
      task[NewTask].pntr = *Tpntr;           // insert pointer
      Status = task[NewTask].status;
      }

    NewTask = 0;
    NewDelay = 0;
}


//**********************************************************
// Set a possibly zero .

static void DoDelay(int delay) {
   if (delay)
      NewDelay = delay;
   else
      NewDelay = 0;
}


//**********************************************************
// Run the task at . When it returns clear the
// status and link. If it has re-scheduled itself, put it on
// the correct queue, otherwise clear the entry completely.

static void RunTask(void) {

   (*task[RunningTask].pntr)();        // run the task

   task[RunningTask].status &= ~(BusyBit | ReadyBit);
   task[RunningTask].next = 0;
   // If the task has re-scheduled itself
   if (NewTask == RunningTask) {
      Tpntr = task[NewTask].pntr;      // set task pointer
      // Put on correct list.
      if (NewDelay)
         DoQueDelay();
      else
         DoQueTask();
      }
   else
     task[RunningTask].pntr = 0;      // ensure task is idle

     RunningTask = 0;
}


//**********************************************************
// Find either the first free entry in the data structure.

static void GetNewTask() {
   void (*ptr)();
   
   NewTask = 1;
   while (NewTask <= TOTALTASKS) {
      ptr = task[NewTask].pntr;
      if (ptr == 0)
         break;
      NewTask++;
      }
}