Sleeping Better


The animation loop in run( ) depends on a good timer and the accuracy of the sleep( ) call. The previous major section dealt with alternatives to currentTimeMillis( ). In this section, I consider ways of improving the sleep( ) code in run( ), so the required frame rate is consistently achieved.

The SleepAcc class measures sleep accuracy. Example 2-4 calls sleep( ) with increasingly small values and measures the actual sleep time using the Java 3D timer.

Example 2-4. Measuring sleep( ) accuracy
 import java.text.DecimalFormat; import com.sun.j3d.utils.timer.J3DTimer; public class SleepAcc {   private static DecimalFormat df;   public static void main(String args[])   {     df = new DecimalFormat("0.##");  // 2 dp     // test various sleep values     sleepTest(1000);     sleepTest(500);     sleepTest(200);     sleepTest(100);     sleepTest(50);     sleepTest(20);     sleepTest(10);     sleepTest(5);     sleepTest(1);   } // end of main( )   private static void sleepTest(int delay)   {     long timeStart = J3DTimer.getValue( );     try {       Thread.sleep(delay);     }     catch(InterruptedException e) {}     double timeDiff =        ((double)(J3DTimer.getValue( ) - timeStart))/(1000000L);     double err = ((delay - timeDiff)/timeDiff) * 100;     System.out.println("Slept: " + delay + " ms  J3D: " +                           df.format(timeDiff) + " ms  err: " +                           df.format(err) + " %" );   }  // end of sleepTest( ) } // end of SleepAcc class 

The difference between the requested and actual sleep delay is negligible for times of 50 ms or more and gradually increases to a +/-10 to 20 percent error at 1 ms. A typical run is:

     D>java SleepAcc     Slept: 1000 ms  J3D: 999.81 ms  err: 0.02 %     Slept: 500 ms  J3D: 499.54 ms  err: 0.09 %     Slept: 200 ms  J3D: 199.5 ms  err: 0.25 %     Slept: 100 ms  J3D: 99.56 ms  err: 0.44 %     Slept: 50 ms  J3D: 49.59 ms  err: 0.82 %     Slept: 20 ms  J3D: 20.53 ms  err: -2.59 %     Slept: 10 ms  J3D: 10.52 ms  err: -4.91 %     Slept: 5 ms  J3D: 5.42 ms  err: -7.78 %     Slept: 1 ms  J3D: 1.15 ms  err: -13.34 %       :  // more lines until ctrl-C is typed

The reason for this inaccuracy is probably due to the complexity of the operation, involving the suspension of a thread and context switching with other activities. Even after the sleep time has finished, a thread has to wait to be selected for execution by the thread scheduler. How long it has to wait depends on the overall load of the JVM (and OS) at that moment.

sleep( )'s implementation varies between operating systems and different versions of Java, making analysis difficult. Under Windows 98 and J2SE 1.4.2, sleep( ) utilizes a large native function (located in jvm.dll), which employs the Windows kernel sleep( ) function with a reported accuracy of 1 ms.

The conclusion is that I should extend the animation loop to combat sleep( )'s inaccuracies.

Handling Sleep Inaccuracies

This version of run( ) in this section revises the previous one in three main ways:

  • It uses the Java 3D timer.

  • sleep( )'s execution time is measured, and the error (stored in overSleepTime) adjusts the sleeping period in the next iteration.

  • Thread.yield( ) is utilized to give other threads a chance to execute if the animation loop has not slept for a while.

Here's the updated method:

     private static final int NO_DELAYS_PER_YIELD = 16;     /* Number of frames with a delay of 0 ms before the        animation thread yields to other running threads. */     public void run( )     /* Repeatedly update, render, sleep so loop takes close        to period nsecs. Sleep inaccuracies are handled.        The timing calculation use the Java 3D timer.     */     {       long beforeTime, afterTime, timeDiff, sleepTime;       long overSleepTime = 0L;       int noDelays = 0;       beforeTime = J3DTimer.getValue( );       running = true;       while(running) {         gameUpdate( );         gameRender( );         paintScreen( );         afterTime = J3DTimer.getValue( );         timeDiff = afterTime - beforeTime;         sleepTime = (period - timeDiff) - overSleepTime;         if (sleepTime > 0) {   // some time left in this cycle           try {             Thread.sleep(sleepTime/1000000L);  // nano -> ms           }           catch(InterruptedException ex){}           overSleepTime =                 (J3DTimer.getValue( ) - afterTime) - sleepTime;         }         else {    // sleepTime <= 0; frame took longer than the period         overSleepTime = 0L;           if (++noDelays >= NO_DELAYS_PER_YIELD) {             Thread.yield( );   // give another thread a chance to run             noDelays = 0;           }         }         beforeTime = J3DTimer.getValue( );       }       System.exit(0);     } // end of run( )

If the sleep( ) call sleeps for 12 ms instead of the desired 10 ms, then overSleepTime will be assigned 2 ms. On the next iteration of the loop, this value will be deducted from the sleep time, reducing it by 2 ms. In this way, sleep inaccuracies are corrected.

If the game update and rendering steps take longer than the iteration period, then sleepTime will have a negative value and this iteration will not include a sleep stage. This causes the noDelays counter to be incremented, and when it reaches NO_DELAYS_PER_YIELD, yield( ) will be called. This allows other threads to execute if they need to and avoids the use of an arbitrary sleep period in run( ).

The switch to the Java 3D timer is mostly a matter of changing the calls to System.currentTimeMillis( ) to J3DTimer.getValue( ). Time values change from milliseconds to nanoseconds, which motivates the change to long variables. Also, the sleep time must be converted from nanoseconds to milliseconds before calling sleep( ), or I'll be waiting a long time for the game to wake up.

If you prefer to use System.nanoTime( ) from J2SE 5.0, you can globally search and replace, changing every J3DTimer.getValue( ) call to System.nanoTime( ). You don't have to import the Java 3D packages if you choose this approach.



Killer Game Programming in Java
Killer Game Programming in Java
ISBN: 0596007302
EAN: 2147483647
Year: 2006
Pages: 340

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net