;* ======================================================================== *;
;*  The routines and data in this file (taskq.asm) are dedicated to the     *;
;*  public domain via the Creative Commons CC0 v1.0 license by its author,  *;
;*  Joseph Zbiciak.                                                         *;
;*                                                                          *;
;*          https://creativecommons.org/publicdomain/zero/1.0/              *;
;* ======================================================================== *;

;;==========================================================================;;
;; Joe Zbiciak's Task Routines:  The Task Queue.                            ;;
;; http://spatula-city.org/~im14u2c/intv/                                   ;;
;;==========================================================================;;

;; ======================================================================== ;;
;;  GLOBAL VARIABLES USED BY THESE ROUTINES                                 ;;
;;                                                                          ;;
;;  Note that some of these routines may use one or more global variables.  ;;
;;  If you use these routines, you will need to allocate the appropriate    ;;
;;  space in either 16-bit or 8-bit memory as appropriate.  Each global     ;;
;;  variable is listed with the routines which use it and the required      ;;
;;  memory width.                                                           ;;
;;                                                                          ;;
;;  Example declarations for these routines are shown below, commented out. ;;
;;  You should uncomment these and add them to your program to make use of  ;;
;;  the routine that needs them.  Make sure to assign these variables to    ;;
;;  locations that aren't used for anything else.                           ;;
;;                                                                          ;;
;;  NOTES                                                                   ;;
;;   -- TSKQHD and TSKQTL must be initialized to 0.                         ;;
;;                                                                          ;;
;;   -- The symbol OVRFLO isn't required.  If it's not available, then      ;;
;;      the code won't count overflows.                                     ;;
;;                                                                          ;;
;;   -- The TSKQ must have room for TSKQM + 1 words.  That is, if TSKQM     ;;
;;      is 15, then TSKQ must have room for 16 words.                       ;;
;;                                                                          ;;
;;   -- The TSKDQ must have room for 2*(TSKQM + 1) bytes.  That is, if      ;;
;;      is 15, then TSKDQ must have room for 32 bytes.                      ;;
;;                                                                          ;;
;;  MAGIC BUILD FLAGS                                                       ;;
;;   -- RUNQ_NO_ECSCABLE should be set if you need to disable ECScable      ;;
;;      support.  This is necessary, for instance, if you are scanning      ;;
;;      for hand controllers on the ECS.                                    ;;
;; ======================================================================== ;;

                        ; Used by     Req'd Width   Description
                        ;-----------------------------------------------------
;OVRFLO EQU     $130    ; RUNQ/etc.   8-bit         Number of queue overflows
;TSKQHD EQU     $132    ; RUNQ/etc.   8-bit         Task queue head           
;TSKQTL EQU     $133    ; RUNQ/etc.   8-bit         Task queue tail           
;TSKQ   EQU     $300    ; RUNQ/etc.   16-bit        Task queue                
;TSKDQ  EQU     $140    ; RUNQ/etc.   8-bit         Task data queue           
;TSKQM  EQU     $F      ; RUNQ/etc    EQUATE        Task queue length mask    
;; The following are needed if supporting timer-based tasks
;MAXTSK EQU     4       ; RUNQ/etc.   EQUATE        Maximum # of timer tasks
;TSKACT EQU     $134    ; STOPALL     8-bit         # of active timer tasks
;TSKTBL EQU     $310    ; STOPALL     16-bit        Timer-task table


;; ======================================================================== ;;
;;  RUNQ -- Main loop for event-driven programs.                            ;;
;;                                                                          ;;
;;  The main loop runs asynchronously from the MAINISR, running tasks as    ;;
;;  they get scheduled.  There is also a background task that runs when     ;;
;;  nothing else is in the queue.                                           ;;
;;                                                                          ;;
;;   -- Grab task from task queue, if any                                   ;;
;;       -- run that task                                                   ;;
;;       -- look for next task                                              ;;
;;   -- run background task once if no tasks available                      ;;
;;                                                                          ;;
;;  CONTROLLER SCANNING SUPPORT                                             ;;
;;                                                                          ;;
;;  If the symbol SCANHAND is defined *before* INCLUDEing this file, the    ;;
;;  background task will scan hand controllers.  The background task is a   ;;
;;  good choice for scanning the hand controllers as it is reasonably       ;;
;;  periodic, and hand-controller scanning can be fairly expensive.         ;;
;;                                                                          ;;
;;  If your system is fairly busy, you may wish to scan hand controllers    ;;
;;  in a different manner, say by calling SCANHAND from an ISR.  To         ;;
;;  prevent RUNQ from calling SCANHAND, set the symbol RUNQ_NO_SCANHAND.    ;;
;;  This may be desirable if you are going scan the hand controllers        ;;
;;  elsewhere.                                                              ;;
;;                                                                          ;;
;;  RANDOM NUMBER CYCLING SUPPORT                                           ;;
;;                                                                          ;;
;;  If the symbol RAND is defined *before* INCLUDEing this file, the        ;;
;;  background task will also update the random number generator state.     ;;
;;  This helps ensure the random numbers are bit more random since the      ;;
;;  game loading varies according to what's going on.                       ;;
;;                                                                          ;;
;;  ECSCABLE POLLING SUPPORT                                                ;;
;;                                                                          ;;
;;  By default, the background task will look for the ECScable Monitor      ;;
;;  and poll for ECScable activity.  You can disable building in this       ;;
;;  support by defining the symbol RUNQ_NO_ECSCABLE.                        ;;
;; ======================================================================== ;;
RUNQ    PROC

        PSHR    R5              ; Save return address, since any
                                ; task is allowed to exit the main
                                ; game loop, say, when switching
                                ; between game phases.

@@loop:
        MOVR    PC,     R5      ; Set our return address to @@loop
        DECR    R5              ; This is 2 decles vs. 4 for SDBD/MVII

        ; Run next scheduled task from queue
        DIS                     ; Shut off interrupts
        MVI     TSKQTL, R1      ; Get the tail of the queue
        CMP     TSKQHD, R1      ; Are there tasks in the queue?
        BEQ     @@bktsk         ; No:  Do background task

        INCR    R1              ; Pop task from queue by
        ANDI    #TSKQM, R1      ; ... moving the queue tail
        MVO     R1,     TSKQTL  ; Store new task-queue tail
        MOVR    R1,     R4
        ADDR    R4,     R4
        ADDI    #TSKDQ, R4
        SDBD
        MVI@    R4,     R2      ; Load instance data for task
        ADDI    #TSKQ,  R1      ; Point to task queue
        MVI@    R1,     R0      ; Get function pointer from queue

        ; Dispatch to user's task.  Make sure interrupts are enabled again
        EIS                     ; Done with critical section
        JR      R0              ; Jump to subroutine
        ; Note that this implicitly loops back to @@oloop here

@@bktsk:
        EIS                     ; Done with critical section

        ;; ---------------------------------------------------------------- ;;
        ;;  If the random number generator exists, call it to advance the   ;;
        ;;  random state.                                                   ;;
        ;; ---------------------------------------------------------------- ;;
    IF  DEFINED RAND
        MVII    #$F,    R0
        CALL    RAND            ; Update random number state
    ENDI    

        ;; ---------------------------------------------------------------- ;;
        ;;  If the hand-controller scanner exists, call it to scan the      ;;
        ;;  controllers.  That is, unless we've been told not to.           ;;
        ;; ---------------------------------------------------------------- ;;
    IF  (DEFINED SCANHAND) = 1 AND (DEFINED RUNQ_NO_SCANHAND) = 0
        CALL    SCANHAND        ; Scan the hand controllers.
    ENDI

        ;; ---------------------------------------------------------------- ;;
        ;;  If we're allowed to, look for the ECScable Monitor and poll it. ;;
        ;; ---------------------------------------------------------------- ;;
    IF  (DEFINED RUNQ_DO_ECSCABLE) = 1

    IF  (DEFINED EC_LOC) = 0
EC_LOC  EQU     $CF00
    ENDI
    IF  (DEFINED EC_MAG) = 0
EC_MAG  EQU     $69
    ENDI
    IF  (DEFINED EC_POLL) = 0
EC_POLL EQU     $CF01
    ENDI

        MVI     EC_LOC, R0
        CMPI    #EC_MAG,R0
        BNEQ    @@loop
        CALL    EC_POLL

    ENDI

        B       @@loop          ; Run around the inner loop
        ENDP


;; ======================================================================== ;;
;;  QTASK                                                                   ;;
;;      Add a task to the task queue.                                       ;;
;;      NOTE: CALL THIS WITH INTERRUPTS OFF!!!!  (eg. use JSRD).            ;;
;;      Interrupts will be enabled before return.                           ;;
;;                                                                          ;;
;;  INPUTS:                                                                 ;;
;;      R0 -- Task function pointer                                         ;;
;;      R1 -- Task instance data                                            ;;
;;      R5 -- Return address.                                               ;;
;;                                                                          ;;
;;  OUTPUTS:                                                                ;;
;;      R0 -- intact.                                                       ;;
;;      R1 -- swapped                                                       ;;
;;      R2, R4 -- trashed.                                                  ;;
;; ======================================================================== ;;
QTASK   PROC
        MVI     TSKQHD, R2      ; Get head of task queue
        INCR    R2              ; Move to next queue slot
        ANDI    #TSKQM, R2      ; Stay in circular buffer
        CMP     TSKQTL, R2      ; Are we overflowing the queue?
        BEQ     @@overflow      ; Yes ... don't overflow it!
        MVO     R2,     TSKQHD  ; Store new task queue head

        MOVR    R2,     R4      ; Generate instance data pointer
        ADDR    R2,     R4      ; (remember mult by two for data ptr)
        ADDI    #TSKQ,  R2      ; Point into task queue
        MVO@    R0,     R2      ; Store function pointer to task q
; moved addi for STIC interruptibility fix
        ADDI    #TSKDQ, R4      ; Point into task data queue
        MVO@    R1,     R4      ; Store instance data to data q
        SWAP    R1              ; ....  cont'd
        MVO@    R1,     R4      ; ....  cont'd

    IF      (DEFINED OVRFLO) = 0  ; No support for overflow tracking?
@@overflow:
        EIS                     ; Re-enable ints after crit section
        JR      R5              ; Return.

    ELSE    ; separate exit path for overflow case.

        EIS                     ; Re-enable ints after crit section
        JR      R5              ; Return.

@@overflow:
        MVI     OVRFLO, R2
        INCR    R2
        MVO     R2,     OVRFLO

        EIS                     ; Re-enable ints after crit section
        JR      R5              ; Return.

    ENDI   

        ENDP

;; ======================================================================== ;;
;;  SCHEDEXIT                                                               ;;
;;      Flushes the task queue and schedules the exit task.                 ;;
;;                                                                          ;;
;;  INPUTS:                                                                 ;;
;;      R5 -- Return address.                                               ;;
;;                                                                          ;;
;;  OUTPUTS                                                                 ;;
;;      Does not return if scheduled as a task.  Causes RUNQ to return      ;;
;;      to its caller.                                                      ;;
;; ======================================================================== ;;
SCHEDEXIT:      PROC
        PSHR    R5              ; Push return address.
        JSRD    R5,STOPALLTASKS ; Flush the pending task queue
        MVII    #@@exit, R0     ; Schedule the exit task ...
        PULR    R5
        B       QTASK           ; ... and chain the return. (ints still DIS)
@@exit: PULR    PC              ;
        ENDP

;; ======================================================================== ;;
;;  STOPALLTASKS                                                            ;;
;;                                                                          ;;
;;  Stops all tasks by flushing the task queue (sets head/tail to 0).       ;;
;;                                                                          ;;
;;  If MAXTSK is defined (eg. timer-based tasks are being used), this       ;;
;;  code will also set all the timer-based task periods to -1, and will     ;;
;;  set number of active timer tasks (TSKACT) to 0.  It returns the         ;;
;;  previous TSKACT in R1.                                                  ;;
;;                                                                          ;;
;;  NOTE: CALL THIS WITH INTERRUPTS OFF!!!!  (eg. use JSRD)  Interrupts     ;;
;;  are NOT re-enabled by this function.  This function is usually called   ;;
;;  as part of an initialization sequence, or by SCHEDEXIT.                 ;;
;;                                                                          ;;
;;  INPUTS:                                                                 ;;
;;      R5 -- Return address                                                ;;
;;                                                                          ;;
;;  OUTPUTS:                                                                ;;
;;      R0 -- Previous TSKACT (if MAXTSK is defined.)                       ;;
;;      R1 -- Cleared                                                       ;;
;;      All tasks stopped.                                                  ;;
;; ======================================================================== ;;
STOPALLTASKS    PROC

        CLRR    R1
        MVO     R1,     TSKQHD          ; \__ Clear out the task queue
        MVO     R1,     TSKQTL          ; /

    IF DEFINED MAXTSK

        MVI     TSKACT, R0              ; \__ Get the previous # of active
        MVO     R1,     TSKACT          ; /   tasks and set current # to 0.

        DECR    R1

@@ofs   QSET    TSKTBL + 2
@@cnt   QSET    0
        REPEAT  MAXTSK
            IF      @@cnt = 3
                NOP                     ; Interruptible every 4th for STIC
@@cnt           QSET    0
            ELSE
@@cnt           QSET    @@cnt + 1
            ENDI
           
            MVO     R1,     @@ofs       ; Set period for task N to -1
@@ofs       QSET    @@ofs + 4
        ENDR

    ENDI
        JR      R5

        ENDP
;; ======================================================================== ;;
;;  End of File:  taskq.asm                                                 ;;
;; ======================================================================== ;;
