Home arrow Java arrow Page 6 - Swing Animation
JAVA

Swing Animation


Have you ever been interested in creating a game using the Swing-based animation library? This article covers some important information to help you understand the backbone of this library. It is excerpted from the book Advanced Java Game Programming, written by David Wallace Croft (Apress, 2004; ISBN 1590591232).

Author Info:
By: Apress Publishing
Rating: 5 stars5 stars5 stars5 stars5 stars / 19
March 29, 2005
TABLE OF CONTENTS:
  1. · Swing Animation
  2. · RepaintCollector
  3. · SimpleRepaintCollector
  4. · CoalescingRepaintCollector
  5. · LoopGovernor
  6. · WindowedLoopGovernor
  7. · AnimatedComponent
  8. · Static method check()

print this article
SEARCH DEVARTICLES

Swing Animation - WindowedLoopGovernor
(Page 6 of 8 )

Class WindowedLoopGovernor from package com.croftsoft.core.util.loop avoids these problems by using a technique known as windowed averaging. Rather than estimate the target loop delay over a fixed sample period, the target delay is continuously reestimated for each frame by averaging over the measurements for the most recent frames. The number of frames over which the averaging occurs is the window. The window is said to slide as time passes such that only the most recent measurements are included in the calculation; older measurements are discarded.

If there is a sudden change in the average paint and update time, the delay will begin to adapt with the very next frame. This is an improvement over SamplerLoopGovernor where adaptation would not begin until the end of the current sample period.

Like SamplerLoopGovernor, WindowedLoopGovernor estimates the delay by using averaging. In SamplerLoopGovernor, the average frame rate is calculated by dividing the number of frames by the sample time. In WindowedLoopGovernor, the average update and paint time per frame is calculated by dividing the sum of the update and paint times by the number of frames in the sampling window.

  package com.croftsoft.core.util.loop;
  [...]
  public final class WindowedLoopGovernor
    implements LoopGovernor
  //////////////////////////////////////////////////
  /////////////////////////////////////////////////
  {
  private static final int DEFAULT_MAX_WINDOW_SIZE = 100;
  private static final long DEFAULT_RESET_TIME_NANOS
    = MathConstants.NANOSECONDS_PER_SECOND;

WindowedLoopGovernor uses one extra trick: a variable window size. Initially, the window size is one frame and then grows to some maximum, by default 100 frames. This allows the adaptation to begin immediately without having to wait until the initial 100 measurements have been made.

If there is some oddball measurement of the update and paint time that falls out of range, greater than one full second by default, the measurements are discarded by resetting the window size to zero. This prevents the average from being influenced by pauses in the game loop.

  private final long         periodNanos;
  private final int          maxWindowSize;
  private final long         resetTimeNanos;
  private final long [ ]     nonDelayTimes;

The final variable periodNanos is the reciprocal of the desired loop frequency in nanoseconds. maxWindowSize is the maximum number of frames to be used for averaging the measurements. resetTimeNanos is the threshold in nanoseconds for discarding a measurement and resetting the window size. The nonDelayTimes array holds the most recent measurements of the update and paint times for each frame, up to maxWindowSize measurements.

  private int    index;
  private int    windowSize;
  private long   delayMillis;
  private int    delayNanos;
  private long   previousTimeNanos;
  private long   totalDelayNanos;
  private long   sumNonDelayTimes;

The non-final instance variable index points to the next position to be used to store a measurement in the nonDelayTimes array. The current windowSize ranges from zero to maxWindowSize. The totalDelayNanos array is the delay used for the current loop and is the sum of the delayMillis and the delayNanos arrays. previousTimeNanos stores the time the previous measurement was made in nanoseconds. The sumNonDelayTimes array is the sum of all of the measurements in the array nonDelayTimes that are part of the current window.

  [...]
  public  WindowedLoopGovernor (
    long  periodNanos,
    int   maxWindowSize,
    long  resetTimeNanos )
  //////////////////////////////////////////////////
  {
   if ( periodNanos < 1 )
   {
    throw new IllegalArgumentException ( "periodNanos <
    1" );
    }
    this.periodNanos = periodNanos;
  if ( maxWindowSize < 1 )
  {
   throw new IllegalArgumentException ( "maxWindowSize <
   1" );
  }
  this.maxWindowSize = maxWindowSize;
  if ( resetTimeNanos < 1 )
  {
   throw new IllegalArgumentException ( "resetTimeNanos < 
   1" );
  }
  this.resetTimeNanos = resetTimeNanos;
  nonDelayTimes = new long [ maxWindowSize ];
  delayMillis
    = periodNanos / 
    MathConstants.NANOSECONDS_PER_MILLISECOND;
  delayNanos = ( int )
    ( periodNanos %
    MathConstants.NANOSECONDS_PER_MILLISECOND );
  totalDelayNanos = periodNanos;
 }

The main constructor initializes the delay variables to the desired loop period, periodNanos, which is given in nanoseconds.

 public WindowedLoopGovernor ( double frequency )
 ///////////////////////////////////////////////
 {
   this (
   ( long ) ( MathConstants.NANOSECONDS_PER_SECOND /
   frequency ),
   DEFAULT_MAX_WINDOW_SIZE,
   DEFAULT_RESET_TIME_NANOS );
 }

The convenience constructor calculates periodNanos as the reciprocal of the desired loop frequency given in loops per second. Reasonable frequency values for animation range from 24.0 to 85.0. The convenience constructor also provides default values for the maxWindowSize and the resetTimeNanos arguments.

 public void govern ( )
   throws InterruptedException
 /////////////////////////////////////////////////////
 {
  long  currentTimeNanos = System.currentTimeMillis ( )
   * MathConstants.NANOSECONDS_PER_MILLISECOND;
  long nonDelayTime
   = currentTimeNanos - previousTimeNanos -  
  totalDelayNanos;
  previousTimeNanos = currentTimeNanos;

The time elapsed since the previous frame is measured and the previous delay time is subtracted to get the nonDelayTime. As an animation loop is divided into the three phases of update, paint, and delay, the nonDelayTime is assumed to measure the time it took for the update and paint phases to complete in the most recent frame.

Note that with a low-resolution system clock, the currentTimeNanos and previousTimeNanos may be the same even though some time has passed. In this case, the calculated nonDelayTime may be negative. By averaging the negative values with the positive values over time, a reasonable value will emerge.

  long oldNonDelayTime = nonDelayTimes [ index ];
  nonDelayTimes [ index ] = nonDelayTime;
  sumNonDelayTimes += nonDelayTime;

The new nonDelayTime measurement is stored in the nonDelayTimes array and added to the sumNonDelayTimes. The oldNonDelayTime that was previously stored at that index position is preserved temporarily in case it needs to be subtracted from the sumNonDelayTimes.

  index = ( index + 1 ) % maxWindowSize;


The index is incremented, resetting to zero if it reaches the 
maxWindowSize.

  if ( nonDelayTime > resetTimeNanos )
  {
   windowSize = 0;
   sumNonDelayTimes = 0;
   Thread.sleep ( delayMillis, delayNanos );
   return;
  }

If the measured nonDelayTime is greater than the resetTimeNanos, by default one second, it is assumed that game loop must have been paused or abnormally stalled. In this case, the measurements in the nonDelayTimes are discarded by resetting the windowSize and sumNonDelayTimes to zero. The previously calculated delay value is used and the method returns immediately.

Note that this block of code will always be triggered the first time the govern() method is called. In this case, the initial value for previousTimeNanos will be zero, as the method was never called before. Subtracting the previousTimeNanos value of zero from the currentTimeNanos to get the elapsed time since the previous frame will generate an invalid value. This will result in an out of range nonDelayTime which is intercepted by this if statement.

  if ( windowSize == maxWindowSize )
  {
   sumNonDelayTimes -= oldNonDelayTime;
  }
  else
  {
   windowSize++;
  }

If the windowSize has grown such that it equals the length of the nonDelayTimes array, maxWindowSize, new measurements will overwrite old measurements in the array. Before the old measurements are discarded, however, they must be subtracted from the sumNonDelayTimes. If the windowSize is less than the maxWindowSize, it is incremented and the oldNonDelayTime is not used, as it was not previously included in the sum.

 long  averageNonDelayTime = sumNonDelayTimes / windowSize;
 totalDelayNanos = periodNanos - averageNonDelayTime;
 if ( totalDelayNanos < 0 )
 {
  totalDelayNanos = 0;
 }
 delayMillis
  = totalDelayNanos /
  MathConstants.NANOSECONDS_PER_MILLISECOND;
 delayNanos = ( int )  ( totalDelayNanos  
 %  MathConstants.NANOSECONDS_PER_MILLISECOND );
 Thread.sleep ( delayMillis, delayNanos );
}

The averageNonDelayTime is calculated by dividing the sum of the measured nonDelayTimes by the current windowSize.The target delay time, totalDelayNanos, is estimated by subtracting the averageNonDelayTime from the total loop time, periodNanos. The thread will then be delayed by the totalDelayNanos, as decomposed into a delayMillis argument plus a delayNanos argument required by the sleep() method.

From my observations, WindowedLoopGovernor does an excellent job of achieving high-resolution frame-rate synchronization using a low-resolution system clock. It can be fooled, however, if the update and paint times vary wildly from one frame to the next. In this odd case, you may have to resort to including native code in your game to get high-resolution clock timings on certain operating systems. Normally, however, the variance of your update and paint times will be reasonably low and you can avoid stepping outside the default security sandbox.

This article is excerpted from Advanced Java Game Programming by David Wallace Croft (Apress, 2004; ISBN 1590591232). Check it out at your favorite bookstore today. Buy this book now.


blog comments powered by Disqus
JAVA ARTICLES

- Java Too Insecure, Says Microsoft Researcher
- Google Beats Oracle in Java Ruling
- Deploying Multiple Java Applets as One
- Deploying Java Applets
- Understanding Deployment Frameworks
- Database Programming in Java Using JDBC
- Extension Interfaces and SAX
- Entities, Handlers and SAX
- Advanced SAX
- Conversions and Java Print Streams
- Formatters and Java Print Streams
- Java Print Streams
- Wildcards, Arrays, and Generics in Java
- Wildcards and Generic Methods in Java
- Finishing the Project: Java Web Development ...

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials