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 }