#OPTION "W V"  -- full ASMs, no vector space
#COMMENT "Transterpreter keyboard process"
#COMMENT "(c) Copyright Christian L. Jacobsen 2006"

-- This is the keyboard handler which needs to be used together with the
-- nonblocking external keyboard channel. It basically needs to be polled :(

-- Poll delay is 1/10th of a second, checking the keyboard 10 times a second
-- should be enough(?)! We are assuming that the underlying runtime gives us
-- times in microseconds.
VAL INT poll.delay IS 10000:
VAL INT NOT.PROCESS.P IS 0:

INLINE PROC deadlock.check(CHAN BYTE out)
  [3]INT queues:
  INT out.val:
  SEQ
    ASM
      -- We get a copy of the queue registers to check if anyone is
      -- running other than us. We check the values later
      LDLP queues  -- Get the address of the queues variable
      SAVEH        -- Save the registers into the queues variable
                   -- FIXME: We have stackk underflow here, dont know
                   -- how to get rid of that warning, other than LDC 0
                   -- at top. It should not matter, but I dont know why
                   -- this warning is appearing.
      -- We also need to test if anyone is waiting for keyboard input from
      -- us. If they are, there will be a workspace in the out channel.
      LD out       -- Load the address of the out channel
      LDNL 0       -- Load the value (ie a wptr or not.proc.p)
      ST out.val   -- Store it into a local variable we can check
    IF
      #PRAGMA DEFINED queues
      (out.val = NOT.PROCESS.P) AND ((queues[0] = NOT.PROCESS.P) AND (queues[2] = NOT.PROCESS.P))
        -- To stop this process, we'll set it waiting on a channel that
        -- will never be read from. This will leave the process off the
        -- queues, and schedule another process, which should result in
        -- a deadlock message.
        CHAN INT tvm.keyboard.chan:
        tvm.keyboard.chan ! 0
      TRUE
        SKIP
:

-- This code does essentially the same thing as the code above. It is quite a
-- bit more compact, at least given that we (at the time of writing) don't
-- have optimal prefixing (the code above has 3 jumps).
--
-- Given that I have written this code now, I am sure I could optimise the
-- above as well.  The whole kbhandler code is 116 bytes using
-- deadlock.check(...) and 88 bytes using deadlock.check.asm(...).
--
-- The code here loads the fptr, bptr and tptr into the 'queues' array. It
-- then loads the fptr, and the tptr into the stack, loads the value of the
-- channel word 'out' into the stack, or's them togeter to see if they are all
-- zero. If this is the case, there is nothing runnable other than us, and we
-- stop(p) ourselves, otherwise we fall through to the rest of the keyboard
-- handling code.
--
-- This code uses STOPP rather than a channel communication (as the above)
-- which probably better (ie if we record any information for debugging
-- purposes during channel comms).
INLINE PROC deadlock.check.asm(CHAN BYTE out)
  [3]INT queues:
  ASM
    -- Load fptr, bptr and tptr into queues
    LDLP queues   -- A = ptr(queues)
    SAVEH         -- stack empty
    -- Load the fptr, and tptr back out of queues onto stack
    LD queues[0]  -- A = valof(queues[0])
    LD queues[2]  -- A = valof(queues[2]); B = valof(queues[0])
    -- Load the value of the channel word for the channel 'out'
    LD out        -- A = ptr(chan-out); B = valof(queues[2]); C = valof(queues[0])
    LDNL 0        -- A = valof(chan-out); B = valof(queues[2]); C = valof(queues[0])
    -- OR the three values we now have on the stack, so we can determine if
    -- they were all 0
    OR            -- A = (valof(chan-out) OR valof(queues[2])); B = valof(queues[0])
    OR            -- A = ( (valof(chan-out) OR valof(queues[2])) OR valof(queues[0]) )
    -- CJ jumps on the wrong value of Areg, so we flip the value using EQC 0
    EQC 0         -- A = 1 if there is nothing to run, 0 if something to run
    -- Jump to the end if there are runnable things, otherwise STOPP the
    -- process, which causes us to enter the scheduler, which will print a
    -- message if there really are no other things to run.
    CJ :end       -- If there are runnable procs, continue
    STOPP         -- Stop the process, rescedule (better than chan comms)
    :end          -- Drop through to kbhandler code
:


-- The actual keyboard handler, please note that it takes INTegers in, so that
-- we can reliably detect when the external keyboard channel has no input for
-- us, without using any of the possible values available in a byte.
PROC chan.kbhandler(CHAN INT in?, CHAN BYTE out!)
  INT t:
  TIMER tim:
  WHILE TRUE
    INT ch:
    SEQ
      -- We want to to send as much data as there is available in the
      -- input buffer, this should make I/O from pipes perform better, 
      -- but should not affect keyboard interactive input.
      in ? ch             -- Poll the external keyboard channel
      WHILE ch <> (-1)    -- Check if there was actual key data on it
        SEQ
          out ! (BYTE ch) -- The user program will get scheduled now
          in ? ch         -- Check if there is more data
      -- Check to see if there is anything else running that us? If not
      -- we'll stop running too...
      -- If we dont do this, the user has to press a key before we can
      -- detect deadlock.
      deadlock.check.asm(out)
      -- Wait for a while, if there were no (more) key events 
      tim ? t
      tim ? AFTER t PLUS poll.delay
:

-- This is a special externals decleration, which the slinker will
-- automatically turn this into an FFICALL -1.
#PRAGMA EXTERNAL "PROC C.tvmspecial.0.readchar(RESULT INT ch) = 0"
#PRAGMA EXTERNAL "PROC C.tvmspecial.1.printscreen(VAL BYTE ch) = 0"

-- The actual keyboard handler, please note that it takes INTegers in, so that
-- we can reliably detect when the external keyboard channel has no input for
-- us, without using any of the possible values available in a byte.
PROC ffi.kbhandler(CHAN BYTE out!)
  INT t:
  TIMER tim:
  WHILE TRUE
    INT ch:
    SEQ
      -- We want to to send as much data as there is available in the
      -- input buffer, this should make I/O from pipes perform better, 
      -- but should not affect keyboard interactive input.
      C.tvmspecial.0.readchar(ch)
      WHILE ch <> (-1)    -- Check if there was actual key data on it
        SEQ
          out ! (BYTE ch) -- The user program will get scheduled now
          C.tvmspecial.0.readchar(ch)  -- Check if there is more data
      -- Check to see if there is anything else running that us? If not
      -- we'll stop running too...
      -- If we dont do this, the user has to press a key before we can
      -- detect deadlock.
      -- NOTE: This wont work for the multiterpreter.
      deadlock.check.asm(out)
      -- Wait for a while, if there were no (more) key events 
      tim ? t
      tim ? AFTER t PLUS poll.delay
:
