Java
  Home arrow Java arrow Page 6 - Swing Animation
Dev Articles Forums 
ADO.NET  
Apache  
ASP  
ASP.NET  
C#  
C++  
ColdFusion  
COM/COM+  
Delphi-Kylix  
Design Usability  
Development Cycles  
DHTML  
Embedded Tools  
Flash  
Graphic Design  
HTML  
IIS  
Interviews  
Java  
JavaScript  
MySQL  
Oracle  
Photoshop  
PHP  
Reviews  
Ruby-on-Rails  
SQL  
SQL Server  
Style Sheets  
VB.Net  
Visual Basic  
Web Authoring  
Web Services  
Web Standards  
XML  
Mobile Linux 
App Generation ROI 
IBM® developerWorks 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us Get Paid 
Request Media Kit
Contact Us 
Site Map 
Privacy Policy 
Support 
 USERNAME
 
 PASSWORD
 
 
  >>> SIGN UP!  
  Lost Password? 
JAVA

Swing Animation
By: Apress Publishing
  • Search For More Articles!
  • Disclaimer
  • Author Terms
  • Rating: 5 stars5 stars5 stars5 stars5 stars / 8
    2005-03-29

    Table of Contents:
  • Swing Animation
  • RepaintCollector
  • SimpleRepaintCollector
  • CoalescingRepaintCollector
  • LoopGovernor
  • WindowedLoopGovernor
  • AnimatedComponent
  • Static method check()

  • Rate this Article: Poor Best 
      ADD THIS ARTICLE TO:
      Del.ici.ous Digg
      Blink Simpy
      Google Spurl
      Y! MyWeb Furl
    Email Me Similar Content When Posted
    Add Developer Shed Article Feed To Your Site
    Email Article To Friend
    Print Version Of Article
    PDF Version Of Article
     
     
    ADVERTISEMENT


    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.

    More Java Articles
    More By Apress Publishing


     

    JAVA ARTICLES

    - 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 ...
    - Generics and Limitations in Java
    - Getting Started with Java Web Development in...







    © 2003-2009 by Developer Shed. All rights reserved. DS Cluster 2 Hosted by Hostway
    For more Enterprise Application Development news, visit eWeek