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 itimervalitval, 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
tv_sec tv_usec
struct timeval
tv_sec tv_usec
tv_sec tv_usec
it_interval
it_value
struct itimerval
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 variablesball_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’
The code in this signal-handler takes care of
all movementswhenever it’s time for them
to occur
The code in thisloop handlesall the actual re-drawing
in sync with thevertical retrace
Signal-handler Program-loop
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?
TeleTYpe display deviceHARDWARE
SOFTWARE
application
tty_driverc_lflag
input handlingc_iflagc_cc
output handlingc_oflag
terminal_driverc_cflag
User space
Kernel space
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 echotty.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; // no echoing• tty.c_lflag &= ~ICANON; // 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 settingstty.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’
HandlesUser’s
keystrokes(SIGIO)
HandlesTimer’s
expirations(SIGALRM)
on_input on_alarm
Handlesredrawingwhenever
Vertical RetraceIs active
main_loop
The Signal-HandlersWaiting and Drawing
Program-loop revised again
done = 0;do {
oldx = xloc, oldy = yloc; // remember thesedraw_ball(); // use current locationvsync(); // await next retracehide_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 paddle-movements (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)?