270 likes | 540 Views
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
E N D
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 tv_sec tv_usec it_interval tv_sec tv_usec it_value 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’ Program-loop Signal-handler 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? application User space tty_driver c_lflag Kernel space input handling c_iflag c_cc output handling c_oflag SOFTWARE struct tty { c_iflag; c_oflag; c_cflag; c_lflag; c_line; c_cc[ ]; }; terminal_driver c_cflag HARDWARE TeleTYpe display device
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; // 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 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’ main_loop on_input on_alarm Handles redrawing whenever Vertical Retrace Is active Handles User’s keystrokes (SIGIO) Handles Timer’s expirations (SIGALRM) 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 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)?