001         package com.croftsoft.core.util.loop;
002    
003         import com.croftsoft.core.animation.updater.FrameRateUpdater;
004         import com.croftsoft.core.math.MathConstants;
005    
006         /*********************************************************************
007         * Uses windowed averaging to estimate the target loop delay.
008         *
009         * The window size starts off small to grows to some maximum size.
010         * Any pause greater than one second will cause a window size reset.
011         *
012         * @version
013         *   $Date: 2008/04/19 21:27:13 $
014         * @since
015         *   2003-09-29
016         * @author
017         *   <a href="https://www.croftsoft.com/">David Wallace Croft</a>
018         *********************************************************************/
019    
020         public final class  WindowedLoopGovernor
021           implements LoopGovernor
022         //////////////////////////////////////////////////////////////////////
023         //////////////////////////////////////////////////////////////////////
024         {
025    
026         private static final int   DEFAULT_MAX_WINDOW_SIZE = 100;
027    
028         private static final long  DEFAULT_RESET_TIME_NANOS
029           = MathConstants.NANOSECONDS_PER_SECOND;
030    
031         //
032    
033         private final long      periodNanos;
034    
035         private final int       maxWindowSize;
036    
037         private final long      resetTimeNanos;
038    
039         private final long [ ]  nonDelayTimes;
040    
041         //
042    
043         private int   index;
044    
045         private int   windowSize;
046    
047         private long  delayMillis;
048    
049         private int   delayNanos;
050    
051         private long  previousTimeNanos;
052    
053         private long  totalDelayNanos;
054    
055         private long  sumNonDelayTimes;
056    
057         //////////////////////////////////////////////////////////////////////
058         //////////////////////////////////////////////////////////////////////
059    
060         public static void  main ( String [ ]  args )
061           throws Exception
062         //////////////////////////////////////////////////////////////////////
063         {
064           LoopGovernor  loopGovernor = new WindowedLoopGovernor ( 85.0 );
065    
066           FrameRateUpdater  frameRateUpdater = new FrameRateUpdater ( true );
067    
068           for ( int  i = 0; i < 10000; i++ )
069           {
070             loopGovernor.govern ( );
071    
072             frameRateUpdater.update ( null );
073           }
074         }
075    
076         //////////////////////////////////////////////////////////////////////
077         // constructor methods
078         //////////////////////////////////////////////////////////////////////
079    
080         /*********************************************************************
081         * Main constructor.
082         *********************************************************************/
083         public  WindowedLoopGovernor (
084           long   periodNanos,
085           int    maxWindowSize,
086           long   resetTimeNanos )
087         //////////////////////////////////////////////////////////////////////
088         {
089           if ( periodNanos < 1 )
090           {
091             throw new IllegalArgumentException ( "periodNanos < 1" );
092           }
093    
094           this.periodNanos = periodNanos;
095    
096           if ( maxWindowSize < 1 )
097           {
098             throw new IllegalArgumentException ( "maxWindowSize < 1" );
099           }
100    
101           this.maxWindowSize = maxWindowSize;
102    
103           if ( resetTimeNanos < 1 )
104           {
105             throw new IllegalArgumentException ( "resetTimeNanos < 1" );
106           }
107    
108           this.resetTimeNanos = resetTimeNanos;
109    
110           nonDelayTimes = new long [ maxWindowSize ];
111    
112           delayMillis
113             = periodNanos / MathConstants.NANOSECONDS_PER_MILLISECOND;
114    
115           delayNanos = ( int )
116             ( periodNanos % MathConstants.NANOSECONDS_PER_MILLISECOND );
117    
118           totalDelayNanos = periodNanos;
119         }
120    
121         /*********************************************************************
122         * Convenience constructor.
123         *
124         * @param  frequency
125         *
126         *   The targeted loop frequency in loops per second.
127         *********************************************************************/
128         public  WindowedLoopGovernor ( double  frequency )
129         //////////////////////////////////////////////////////////////////////
130         {
131           this (
132             ( long ) ( MathConstants.NANOSECONDS_PER_SECOND / frequency ),
133             DEFAULT_MAX_WINDOW_SIZE,
134             DEFAULT_RESET_TIME_NANOS );
135         }
136    
137         //////////////////////////////////////////////////////////////////////
138         //////////////////////////////////////////////////////////////////////
139    
140         public void  govern ( )
141           throws InterruptedException
142         //////////////////////////////////////////////////////////////////////
143         {
144           long  currentTimeNanos = System.nanoTime ( );
145    
146           long  nonDelayTime
147             = currentTimeNanos - previousTimeNanos - totalDelayNanos;
148    
149           previousTimeNanos = currentTimeNanos;
150    
151           long  oldNonDelayTime = nonDelayTimes [ index ];
152    
153           nonDelayTimes [ index ] = nonDelayTime;
154    
155           sumNonDelayTimes += nonDelayTime;
156    
157           index = ( index + 1 ) % maxWindowSize;
158    
159           if ( nonDelayTime > resetTimeNanos )
160           {
161             windowSize = 0;
162    
163             sumNonDelayTimes = 0;
164    
165             Thread.sleep ( delayMillis, delayNanos );
166    
167             return;
168           }
169    
170           if ( windowSize == maxWindowSize )
171           {
172             sumNonDelayTimes -= oldNonDelayTime;
173           }
174           else
175           {
176             windowSize++;
177           }
178    
179           long  averageNonDelayTime = sumNonDelayTimes / windowSize;
180    
181           totalDelayNanos = periodNanos - averageNonDelayTime;
182    
183           if ( totalDelayNanos < 0 )
184           {
185             totalDelayNanos = 0;
186           }
187    
188           delayMillis
189             = totalDelayNanos / MathConstants.NANOSECONDS_PER_MILLISECOND;
190    
191           delayNanos = ( int )
192             ( totalDelayNanos % MathConstants.NANOSECONDS_PER_MILLISECOND );
193    
194           Thread.sleep ( delayMillis, delayNanos );
195         }
196    
197         //////////////////////////////////////////////////////////////////////
198         //////////////////////////////////////////////////////////////////////
199         }