==============================================================================
  THE TASK QUEUE EVENT-DRIVEN PROGRAMMING MODEL:  taskq.asm
==============================================================================

RUNQ provides an event-driven programming model.  The model is built
around a task queue, and a main event-handling loop.  The purpose
of the event-handling loop is to look for events to get queued, and
to process those events as they come in.  When there's nothing else
to do, the event loop executes an "idle task."  The "idle task" may
subsequently generate new events.

The "idle task" is embedded in RUNQ.  It's exact contents depend on
build flags.  Look at "taskq.asm" for details.  By default, the idle
task will do the following:

 -- Scan the hand controllers looking for new inputs.  This can be
    a source of new events to process.

 -- Update the random number generator.  The goal here is to let
    system loading improve the randomness of the random number
    generator.  (We hope.)

 -- Poll the ECScable (if we're allowed to).


The task queue is fairly simple:  It contains a queue of function
pointers, and a parallel queue of arguments to pass to those functions.
A single 16-bit argument is provided.

This simple queue structure is sufficient for reporting input events,
collision events, timed events and so on.  Events can come from anywhere.
Any routine can call QTASK to queue a new event to be processed.  This
provides a fairly flexible method for queuing up things to do.

Another reason for queuing tasks is to break up large operations into
small chunks, so that many parallel operations can interleave.  The
game 4-Tris does this, for instance.  

In 4-Tris, the score is updated by "rolling" the displayed value towards
the actual score.  Likewise, the well is cleared with an animated
sequence.  We would like each of these actions to occur in parallel.
4-Tris handles this by breaking up both operations into tiny events.
For the score, each event call causes the score to do one update as part
of its "rolling" process.  For the well-clearing, one event call clears
one row of the area being cleared.  Because these streams of events
are being scheduled simultaneously, the two streams can interleave,
giving the illusion of simultaneity.

In general, I'd recommend that you keep event handlers short.  Anything
that takes a "long time" to process should be broken up into small
pieces.  This allows the process to "multitask" with the rest of the
system.

API Reference:

    RUNQ         -- Call to turn on the event handling loop.  Returns only
                    when SCHEDEXIT is called.
                
    QTASK        -- Queues a task to be handled by the event handling loop.
               
    SCHEDEXIT    -- Causes RUNQ to exit.  This is useful when you have a
                    top-level function which transitions game phases between
                    calls to RUNQ.
   
    STOPALLTASKS -- Flushes the tasks queue, and if there are any timer-based
                    tasks, disabled them as well.

==============================================================================
  TIMER-BASED TASKS:  timer.asm
==============================================================================

The routines in timer.asm are intended to allow programs to generate
periodic tasks -- tasks with fixed or nearly-fixed periods. 

The timer routines maintain a set of task structures in memory.  These
structures contain pointers to each of the timer-triggered event
handlers, the period of each task, and the argument to pass to the
task when the task is triggered.

Timer tasks can be either one-shot or repeating tasks.  A one-shot
timer task gets queued once when its timer expires.  That's it.
A repeating task will reinitialize its timer each time its triggered.

The timers are updated by a single function called DOTIMER.  DOTIMER
should be called from an ISR, although it can also be queued as
a task.   On each invocation of DOTIMER, all active task timers are
updated, and events are queued for timers that expire.  

Additional functions are provided for starting and stopping timed tasks.
The variable "TSKACT" gives the total number of active tasks.  DOTIMER
only updates tasks 0 .. TSKACT-1.

API Reference:

    DOTIMER          -- Call periodically to update the task timers

    STARTTASK        -- Initializes a task timer record.  User must still
                        set TSKACT if needed.

    STOPTASK         -- Disabled a task timer.

    RETRIGGERTASK    -- Copies a tasks timer reinitialization field to
                        its period.  This will retrigger a one-shot
                        or restart a period task that had been stopped.

    STOPALLTASKS     -- Flushes the tasks queue and disables all timer-based
                        tasks.

==============================================================================
  SLEEP and SPAWN:  sleep.asm
==============================================================================

SLEEP and SPAWN make use a timer slot to allow a function to "sleep"
for a specified period while other events are handled.  This function is
a somewhat tricky function to use correctly.  Most programs can probably
live without as well, which is why it's broken out to a separate file.

SLEEP and SPAWN are actually the same function.  So from here, when
I refer to SLEEP, I'm referring to both unless noted otherwise.

SLEEP works by saving the caller's registers to a special register-save
area.  It then sets up a task timer record, and returns to the caller's
caller.  That's right, rather than return to its caller, it looks for
a return address on the top of stack, and returns to that.  This is
important:  You *must* have your return address on the top of stack
when you call SLEEP.  You *cannot* have other information stored on
the stack when you call SLEEP.

When the task timer expires, it will kick off a special "resume" task.
This task will restore the registers from the register-save area and
jump to the point just after SLEEP was called.  

SLEEP and SPAWN differ only in the period argument passed to either.
One can specify a one-shot period which results in a SLEEP.  Alternately,
one can specify a repeating period, which results in a SPAWN.  SPAWN
effectively "spawns" a new repeating task a the point where the call to
SPAWN is made.  This can be useful, but tricky to get right.  Look
at 4-Tris for an example of how SPAWN is used.

==============================================================================
  SCANHAND -- Reading the hand controllers
==============================================================================

The RUNQ routine will scan the hand controller for you, unless either
SCANHAND is missing, or you build it with the symbol "RUNQ_NO_SCANHAND" 
set.

The file "scanhand.asm" provides a fairly complete and comprehensive
hand-controller scanning routine.  It may be overkill for your application.
Please read the docs in "scanhand.asm" for details.

You're welcome to provide your own hand-controller scanning routine.
If you want RUNQ to call it from the background task, then name it
SCANHAND and define it before you INCLUDE "taskq.asm".


==============================================================================
  HOW DOES THIS RELATE TO 4-TRIS?
==============================================================================

These routines were developed as part of 4-Tris.  The routines
"MAINISR" and "MAINLOOP have been renamed "DOTIMER" and "RUNQ", 
respectively.

The hand-controller scanning routine in "scanhand.asm" is very different
from the SCANHAND that appeared in 4-Tris.  It uses a different dispatch
table format, requires slightly more RAM, and provides significantly 
different functionality.

