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 }