Linux game programming An introduction to the use of input notifications

advertisement
Linux game programming
An introduction to the use of
interval timers and asynchronous
input notifications
Our prior animation demo
• We achieved the illusion of “smooth” and
“flicker-free” animation, by synchronizing
drawing-operations with Vertical Retrace
• But more is needed in a game that’s “fun”
• Some deficiencies in our ‘animate1’ demo:
– Ball movements tied to Vertical Retrace
– The viewer lacked any real-time control
• How can we overcome these limitations?
Decoupling ‘move’ from ‘draw’
• Our program-loop had used logic like this:
do {
vsync();
// delay until start of the next retrace
hide_ball();
// “erase” the ball
move_ball();
// adjust its location
show_ball();
// “redraw” the ball
--count;
// decrement a counter
}
while ( count > 0 );
• So ball movement is delayed by vertical retraces
Linux provides ‘interval timers’
•
•
•
•
•
•
•
•
#include <sys/time.h>
struct itimerval itval, itold;
itval.it_value.tv_sec = 2;
itval.it_value.tv_usec = 0;
itval.it_interval.tv_sec = 0;
itval.it_interval.tv_usec = 10000;
setitimer( ITIMER_REAL, &itval, &itold );
(See the ‘man’ page for additional details)
structs timeval and itimerval
struct timeval
tv_sec
tv_usec
struct itimerval
it_interval
tv_sec
tv_usec
it_value
tv_sec
tv_usec
It_itimerval = next delay value, it_timeval = current delay value
SIGALRM
• When timer “expires” our application gets
notified, by being sent a signal from Linux
• Normally an application gets terminated if
the SIGALRM signal is delivered to it
• But we can alter that default behavior, by
installing a ‘signal-handler’ that we design
• We can ‘move-the-ball’ when SIGALRM is
received, regardless of Vertical Retrace
Our signal-handler
void on_alarm( int signum )
{
// modify these global variables
ball_xcoordinate += xincrement;
ball_ycoordinate += yincrement;
}
// The ‘signal()’ function “installs” our handler
signal( SIGALRM, on_alarm);
Main program-loop “revised”
• We can now omit ball-movement in main loop:
do {
vsync();
// delay until start of the next retrace
hide_ball();
// “erase” the old ball
oldx = newx; oldy = newy; // remember new position
show_ball();
// “redraw” the new ball
--count;
// decrement a counter
}
while ( count > 0 );
• Ball-movement is managed by ‘signal-handler’
‘draw’ versus ‘move’
Signal-handler
Program-loop
The code in this
signal-handler
takes care of
all movements
whenever it’s
time for them
to occur
The code in this
loop handles
all the actual
re-drawing
in sync with the
vertical retrace
The Operating System takes care of switching the CPU between
these two separate threads-of-control
Giving the user control
• Linux supports “asynchronous” terminal i/o
• We can reprogram the terminal console so
a SIGIO signal will be sent to our program
whenever the user decides to press a key
• And we can install a ‘signal-handler’ of our
own design that executes if SIGIO arrives
• This will allow a user to “control” our game
The ‘tty’ interface
•
•
•
•
‘tty’ is an acronyn for ‘TeleTYpe’ terminal
Such devices have a keyboard and screen
Behavior emulates technology from 1950s
Usually a tty operates in ‘canonical’ mode:
– Each user-keystroke is ‘echoed’ to screen
– Some editing is allowed (e.g., backspace)
– The keyboard-input is internally buffered
– The <ENTER>-key signals an ‘end-of-line’
– Programs receive input one-line-at-a-time
‘tty’ customization
• Sometimes canonical mode isn’t suitable
(an example: animated computer games)
• The terminal’s behavior can be modified!
• UNIX provides a convenient interface:
– #include <termios.h>
– struct termios tty;
– int tcgetattr( int fd, struct termios *tty );
– int tcsetattr( int fd, int flag, struct termios *tty );
How does the ‘tty’ work?
SOFTWARE
application
User space
tty_driver
c_lflag
Kernel space
input handling
c_iflag
c_cc
output handling
c_oflag
terminal_driver
c_cflag
HARDWARE
TeleTYpe display device
struct tty {
c_iflag;
c_oflag;
c_cflag;
c_lflag;
c_line;
c_cc[ ];
};
The ‘c_lflag’ field
•
•
•
•
•
•
This field is just an array of flag bits
Individual bits have symbolic names
Names conform to a POSIX standard
Linux names match other UNIX’s names
Though actual symbol values may differ
Your C/C++ program should use:
#include <termios.h>
for portability to other UNIX environments
ICANON and ECHO
• Normally the ‘c_lflag’ field has these set
• They can be cleared using bitwise logic:
tty.c_lflag &= ~ECHO;
// inhibit echo
tty.c_lflag &= ~ICANON; // no buffering
The ‘c_cc[ ]’ array
•
•
•
•
•
‘struct termios’ objects include an array
The array-indices have symbolic names
Symbol-names are standardized in UNIX
Array entries are ‘tty’ operating parameters
Two useful ones for our purposes are:
tty.c_cc[ VMIN ] and tty.c_cc[ VTIME ]
How to setup ‘raw’ terminal-mode
• Step 1: Use ‘tcgetattr()’ to get a copy of the
current tty’s ‘struct termios’ settings
• Step 2: Make a working copy of that object
• Step 3: Modify its flags and control-codes
• Step 4: Use ‘tcsetattr()’ to install changes
• Step 5: Perform desired ‘raw’ mode input
• Step 6: Use ‘tcsetattr()’ to restore the
terminal to its original default settings
‘raw’ mode needs four changes
• tty.c_cc[ VMIN ] = 1;
– so the ‘read()’ function will return as soon as
at least one new input-character is available
• tty.c_cc[ VTIME ] = 0;
– so there will be no time-delay after each new
key pressed until the ‘read()’ function returns
• tty.c_lflag &= ~ECHO;
• tty.c_lflag &= ~ICANON;
// no echoing
// no buffering
Demo program: ‘rawtty.cpp’
•
•
•
•
•
This program may soon prove useful
It shows the keyboard scancode values
It demonstrates ‘noncanonical’ tty mode
It clears the ISIG bit (in ‘c_lflags’ field)
This prevents <CONTROL>-C from being
used to abort the program: the user must
‘quit’ by hitting the <ESCAPE>-key; so
default terminal-settings will get reinstalled
‘Noncanonical’ terminal i/o
• We’ve now learned how to reprogram the
terminal to allow “raw” keyboard input
#include <termios.h>
struct termios
tty;
tcgetattr( 0, &tty );
// get tty settings
tty.c_lflag &= ~( ICANON | ECHO | ISIG );
tty.c_cc[ VMIN ] = 1; tty.c_cc[ VTIME ] = 0;
tcsetattr( 0, TCSAFLUSH, &tty ); // install
Handling a key-press
• Here’s a ‘simple’ signal-handler that lets a user
decide to terminate our program (by hitting the
<ESCAPE>-key) instead of the program itself
deciding to quit when a counter reaches zero
void on_input( int signum )
{
int
inch = 0;
read( 0, &inch, 4 );
if ( inch == ESCAPE_KEY ) done = 1;
}
Enabling ‘asynchronous’ I/O
• Now we need to install our signal-handler,
specify which program will receive SIGIO,
then enable the delivery of these signals
• signal( SIGIO, on_input );
• fcntl( 0, F_SETOWN, getpid() );
• int flagval = fcntl( 0, F_GETFL, NULL );
• flagval |= O_ASYNC; // turn on flag-bit
• fcntl( 0, F_SETFL, flagval );
‘animate2.cpp’
on_input
Handles
User’s
keystrokes
(SIGIO)
on_alarm
Handles
Timer’s
expirations
(SIGALRM)
main_loop
Handles
redrawing
whenever
Vertical Retrace
Is active
The Signal-Handlers
Waiting and Drawing
Program-loop revised again
done = 0;
do {
oldx = xloc, oldy = yloc; // remember these
draw_ball();
// use current location
vsync();
// await next retrace
hide_ball();
// erase previous ball
}
while ( !done );
// ‘xloc’, ‘yloc’, and ‘done’ get changed by handlers
Enhancment: more user-control
•
•
•
•
•
•
In ‘pong’ game the user moves a ‘paddle’
The paddle can only be moved left or right
Lower wall of the playing-court is removed
Ball will “escape” unless it hits the paddle
Keyboard has left and right “arrow keys”
Our input signal-handler could “move” the
paddle whenever a user hits an arrow-key
In-class exercise #1
• Before you remove the game-court’s lower
wall, see if you can implement the paddlemovements (left or right) in response to a
user’s hitting the left-arrow or right-arrow
• You will need to investigate the numerical
code-sequence that Linux generates when
a user hits one of these arrow-keys
• Our ‘rawtty.cpp’ application may be useful!
In-class exercise #2
• For brevity, our ‘animate2’ demo-program
removed the capability of users controlling
the speed of the ball-movements (with an
argument supplied on the command-line)
• Can you devise a way for users to exert
“run-time” control over the ball’s speed by
pressing keys while the program is running
(for example, the ‘+’ key and the ‘-’ key)?
Download