-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
170 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package us.ihmc.commons.thread; | ||
|
||
import us.ihmc.commons.Conversions; | ||
|
||
/** | ||
* Throttler is used to allow things to happen at a slower rate | ||
* than the parent update thread. It features a run method, | ||
* which returns whether or not enough time has passed to run something. | ||
* Alternatively, it features a waitAndRun method, which allows the | ||
* user to sleep until it is ready for the next thing. | ||
* <p> | ||
* This class is useful as an alternative to creating additional | ||
* threads when appropriate. It does not create any threads so | ||
* there are no concurrency issues. | ||
* <p> | ||
* Throttler's {@link #run} methods work best when polled at much higher frequencies | ||
* than the requested throttled frequency. We have put a mechanism in place | ||
* to handle poll frequencies near or lower than requested, but the result | ||
* will be jittery and inaccurate. In those cases, it is best to spin | ||
* up a separate thread. See us.ihmc.tools.thread.RestartableThrottledThread. | ||
* <p> | ||
* Throttler's {@link #waitAndRun} methods have a different nature. They | ||
* obviously cannot be called faster than the requested frequency. They | ||
* are designed to handle waiting the extra time after variable amounts | ||
* of computation in order to run that computation at a steady rate. | ||
* <p> | ||
* Example: | ||
* | ||
* <pre> | ||
* Throttler throttler = new Throttler().setFrequency(5.0); | ||
* | ||
* while (true) | ||
* { | ||
* if (throttler.run()) | ||
* { | ||
* // do stuff | ||
* } | ||
* } | ||
* </pre> | ||
*/ | ||
public class Throttler | ||
{ | ||
private double resetTime = Double.NaN; | ||
private double optionallySetPeriod = Double.NaN; | ||
private transient double currentTime; | ||
private transient double overtime; | ||
|
||
/** | ||
* Set the period. | ||
* <p> | ||
* Syntactic sugar to be clear about what a constant passed in would be. | ||
* For example, as a field: | ||
* | ||
* <pre> | ||
* private final Throttler throttler = new Throttler().setPeriod(5.0); | ||
* </pre> | ||
*/ | ||
public Throttler setPeriod(double period) | ||
{ | ||
optionallySetPeriod = period; | ||
return this; | ||
} | ||
|
||
/** | ||
* Set the frequency. | ||
* <p> | ||
* Syntactic sugar to be clear about what a constant passed in would be. | ||
* For example, as a field: | ||
* | ||
* <pre> | ||
* private final Throttler throttler = new Throttler().setFrequency(5.0); | ||
* </pre> | ||
*/ | ||
public Throttler setFrequency(double frequency) | ||
{ | ||
optionallySetPeriod = Conversions.hertzToSeconds(frequency); | ||
return this; | ||
} | ||
|
||
/** | ||
* For use if the user set the period with the {@link #setPeriod} method. | ||
* @return Whether or not enough time has passed to run your thing again. | ||
* It is recommended to call this at several times the desired throttled rate. | ||
* Calling this more often is directly proportional to the resulting accuracy. | ||
*/ | ||
public boolean run() | ||
{ | ||
return run(optionallySetPeriod); | ||
} | ||
|
||
/** | ||
* @param period for passing in dynamically calculated periods | ||
* @return Whether or not enough time has passed to run your thing again. | ||
* It is recommended to call this at several times the desired throttled rate. | ||
* Calling this more often is directly proportional to the resulting accuracy. | ||
*/ | ||
public boolean run(double period) | ||
{ | ||
currentTime = Conversions.nanosecondsToSeconds(System.nanoTime()); | ||
|
||
if (Double.isNaN(resetTime)) // First run | ||
{ | ||
resetTime = currentTime; | ||
return true; | ||
} | ||
else | ||
{ | ||
calculateOvertime(period); | ||
|
||
boolean periodHasElapsed = overtime >= 0.0; | ||
|
||
if (periodHasElapsed) | ||
{ | ||
// We subtract the overtime to achieve a more accurate rate | ||
resetTime = currentTime - overtime; | ||
} | ||
|
||
return periodHasElapsed; | ||
} | ||
} | ||
|
||
/** | ||
* Sleeps until enough time has passed to run your thing again. | ||
* <p> | ||
* For use if the user set the period with the {@link #setPeriod} method. | ||
*/ | ||
public void waitAndRun() | ||
{ | ||
waitAndRun(optionallySetPeriod); | ||
} | ||
|
||
/** | ||
* Sleeps until enough time has passed to run your thing again. | ||
* | ||
* @param period for passing in dynamically calculated periods | ||
*/ | ||
public void waitAndRun(double period) | ||
{ | ||
currentTime = Conversions.nanosecondsToSeconds(System.nanoTime()); | ||
|
||
if (Double.isNaN(resetTime)) // First run | ||
{ | ||
resetTime = currentTime; | ||
} | ||
else | ||
{ | ||
calculateOvertime(period); | ||
|
||
if (overtime < 0.0) | ||
{ | ||
// Guarantees to sleep at least this amount (i.e. will sleep too long) | ||
currentTime += ThreadTools.parkAtLeast(-overtime); | ||
|
||
calculateOvertime(period); | ||
} | ||
|
||
// We subtract the overtime to achieve a more accurate rate | ||
resetTime = currentTime - overtime; | ||
} | ||
} | ||
|
||
private void calculateOvertime(double period) | ||
{ | ||
double elapsedTime = currentTime - resetTime; | ||
|
||
// Limit the overtime to half the period, to prevent building up | ||
// when the run methods are called too infrequently. | ||
overtime = Math.min(elapsedTime - period, 0.5 * period); | ||
} | ||
} |