001         package com.croftsoft.core.util.loop;
002    
003         import com.croftsoft.core.animation.Clock;
004         import com.croftsoft.core.animation.clock.SystemClock;
005         import com.croftsoft.core.lang.NullArgumentException;
006         import com.croftsoft.core.math.MathConstants;
007    
008         /*********************************************************************
009         * Sets the delay by sampling the calling frequency over time.
010         *
011         * @version
012         *   2003-05-22
013         * @since
014         *   2002-03-13
015         * @author
016         *   <a href="https://www.croftsoft.com/">David Wallace Croft</a>
017         *********************************************************************/
018    
019         public final class  SamplerLoopGovernor
020           implements LoopGovernor
021         //////////////////////////////////////////////////////////////////////
022         //////////////////////////////////////////////////////////////////////
023         {
024    
025         public static final long  DEFAULT_SAMPLE_PERIOD_NANOS
026           = 3 * MathConstants.NANOSECONDS_PER_SECOND;
027    
028         public static final long  DEFAULT_AVERAGING_SAMPLES_MAX = 2;
029    
030         //
031    
032         private final long   periodNanos;
033    
034         private final long   samplePeriodNanos;
035    
036         private final long   averagingSamplesMax;
037    
038         private final Clock  clock;
039    
040         //
041    
042         private long  lastTimeNanos;
043    
044         private long  count;
045    
046         private long  totalDelayNanos;
047    
048         private long  delayMillis;
049    
050         private int   delayNanos;
051    
052         private long  averagingSamples;
053    
054         //////////////////////////////////////////////////////////////////////
055         // constructor methods
056         //////////////////////////////////////////////////////////////////////
057    
058         /*********************************************************************
059         * Constructs a LoopGovernor with the specified target frequency.
060         *
061         * @param  frequency
062         *
063         *   The targeted loop frequency in loops per second.
064         *********************************************************************/
065         public  SamplerLoopGovernor (
066           double  frequency,
067           long    samplePeriodNanos,
068           long    averagingSamplesMax,
069           Clock   clock )
070         //////////////////////////////////////////////////////////////////////
071         {
072           if ( frequency <= 0.0 )
073           {
074             throw new IllegalArgumentException ( "frequency <= 0.0" );
075           }
076    
077           periodNanos = ( long )
078             ( MathConstants.NANOSECONDS_PER_SECOND / frequency );
079    
080           totalDelayNanos = periodNanos;
081    
082           delayMillis
083             = totalDelayNanos / MathConstants.NANOSECONDS_PER_MILLISECOND;
084    
085           delayNanos = ( int )
086             ( totalDelayNanos % MathConstants.NANOSECONDS_PER_MILLISECOND );
087    
088           if ( samplePeriodNanos < 2 * periodNanos )
089           {
090             throw new IllegalArgumentException (
091               "samplePeriodNanos < 2 * periodNanos:  " 
092               + samplePeriodNanos + " < " + ( 2 * periodNanos ) );
093           }
094    
095           this.samplePeriodNanos = samplePeriodNanos;
096    
097           if ( averagingSamplesMax < 1 )
098           {
099             throw new IllegalArgumentException ( "averagingSamplesMax < 1" );
100           }
101    
102           this.averagingSamplesMax = averagingSamplesMax;
103    
104           NullArgumentException.check ( this.clock = clock );
105         }
106    
107         public  SamplerLoopGovernor (
108           double  frequency,
109           Clock   clock )
110         //////////////////////////////////////////////////////////////////////
111         {
112           this (
113             frequency,
114             DEFAULT_SAMPLE_PERIOD_NANOS,
115             DEFAULT_AVERAGING_SAMPLES_MAX,
116             clock );
117         }
118    
119         public  SamplerLoopGovernor ( double  frequency )
120         //////////////////////////////////////////////////////////////////////
121         {
122           this ( frequency, SystemClock.INSTANCE );
123         }
124    
125         //////////////////////////////////////////////////////////////////////
126         //////////////////////////////////////////////////////////////////////
127    
128         public void  govern ( )
129           throws InterruptedException
130         //////////////////////////////////////////////////////////////////////
131         {
132           Thread.sleep ( delayMillis, delayNanos );
133    
134           count++;
135    
136           long  currentTimeNanos = clock.currentTimeNanos ( );
137    
138           if ( currentTimeNanos < lastTimeNanos + samplePeriodNanos )
139           {
140             return;
141           }
142    
143           long  measuredSamplePeriodNanos = currentTimeNanos - lastTimeNanos;
144    
145           long  estimatedPeriodNanos = measuredSamplePeriodNanos / count;
146    
147           count = 0;
148    
149           lastTimeNanos = currentTimeNanos;
150             
151           long  estimatedNonDelayTimeNanos
152             = estimatedPeriodNanos - totalDelayNanos;
153    
154           long  newDelayNanos = periodNanos - estimatedNonDelayTimeNanos;
155    
156           if ( newDelayNanos < 0 )
157           {
158             if ( measuredSamplePeriodNanos == currentTimeNanos )
159             {
160               return;
161             }
162    
163             newDelayNanos = 0;
164           }
165    
166           if ( averagingSamples < averagingSamplesMax )
167           {
168             averagingSamples++;
169           }
170    
171           totalDelayNanos
172             += ( newDelayNanos - totalDelayNanos ) / averagingSamples;
173    
174           delayMillis
175             = totalDelayNanos / MathConstants.NANOSECONDS_PER_MILLISECOND;
176    
177           delayNanos = ( int )
178             ( totalDelayNanos % MathConstants.NANOSECONDS_PER_MILLISECOND );
179         }
180    
181         //////////////////////////////////////////////////////////////////////
182         //////////////////////////////////////////////////////////////////////
183         }