[comp.lang.ada] Handling asynchronous keyboard input

mooremj@EGLIN-VAX.ARPA ("MARTIN J. MOORE") (04/11/88)

> Date: 8 Apr 88 16:51:29 GMT
> From: decvax!mandrill!asheem@ucbvax.Berkeley.EDU  (Asheem Chandna)
> Subject: Handling Interactive Input from the Keyboard Buffer
> 
>    I have a large simulation program with many tasks in it. During every time
> loop, I want to do a check to see if the user has typed anything from the 
> keyboard. If he (or she) has, then I want to pull up the user menu. If he
> hasn't, then I just want the program to continue WITHOUT waiting for user
> input. Since, I'd like to do this every time loop, I'm also looking for a low
> overhead way to do it (for e.g., I'd rather not have a delay based time-out on
> every loop). So, basically, I'm looking for an elegant way to have the user 
> interrupt a program run. 

About a year ago, I had the same problem: how to allow asynchronous keyboard 
input without blocking the whole program.  My solution (using VAX Ada)
was as follows:

The heart of the solution is a package called ASYNC_KB_INPUT.  When
elaborated, this package starts a task to read the keyboard.  This task
consists of a loop which calls DEC's TASK_QIOW procedure.  TASK_QIOW is a
useful substitute for VAX/VMS's $QIOW system service in a task environment. 
A vanilla $QIOW is generally unacceptable because it puts the process into
hibernation, blocking the entire program.  TASK_QIOW, however, is implemented
using AST's, so it blocks only the calling task. The keyboard task calls
TASK_QIOW with a character count of 1, so the task waits until a character is
typed, while the rest of the program goes about its business.  (By the way,
there is also an entry in the keyboard task to perform a clean termination
of the task.)

When the keyboard task reads a character (i.e., after the TASK_QIOW call 
completes) the character is placed in a circular queue which is hidden 
inside the ASYNC_KB_INPUT package.  The package exports two functions:

	function INPUT_AVAILABLE return BOOLEAN;
	function GET_CHARACTER return CHARACTER;

INPUT_AVAILABLE simply tells whether there are any new characters in the
queue.  GET_CHARACTER removes the oldest character from the queue and 
returns it (or returns ASCII.NUL if the queue is empty.)  These two functions 
are called from an executive task to periodically check for keyboard input.
The executive task uses the receive characters to drive a finite state machine
which parses the input and takes the appropriate actions.

There are some things to be careful of when using this solution.  First, when 
using this method of keyboard input, you cannot use the standard GET; the 
keyboard task must be shut down in order to use GET.  (We got around this by
implementing the keyboard task as a task type, so we could shut down the
task and start another one at a later time.)  Second, only one keyboard task
must be active at a time, or REAL trouble will result.  Third, you have to be
sure to handle the queue-full case correctly, i.e., don't put characters in
the queue when it is full.  Also, be sure to make the queue big enough so that
filling the queue is very unlikely, or characters may be lost. 

This solution served me well.  The non-portable portion is limited to the 
TASK_QIOW call, which is not visible to the outside world; other
implementations could substitute the appropriate code for their systems. 

				Martin Moore
				mooremj@eglin-vax.arpa
------