using System; using System.Collections.Generic; using System.Text; using System.Diagnostics; namespace OpenTK { #region InterleavedScheduler public class InterleavedScheduler { // Track actually frequencies private readonly Stopwatch stopWatch = Stopwatch.StartNew(); private readonly int loopsPerSecond; private readonly int updatesPerSecond; private readonly int rendersPerSecond; // Bookkeeping variables private InterleaveTimings timings; // This struct encapsules the sleep sequences of the scheduler. private double previousElapsedSeconds = 0; private int loops = 0; // Used to interpolate frequencies smaller then the loop frequency. private double timeline = 0; private int lastUpdateFrame = 0; private int lastRenderFrame = 0; /// /// This scheduler uses a sequence of sleep times to approximate target update and render frequencies that are /// more precise than the system timer will allow. /// /// Target number of updates per second /// Target number of render frames per second public InterleavedScheduler(int updatesPerSecond, int rendersPerSecond) { if (updatesPerSecond <= 4) throw new ArgumentOutOfRangeException("Updates per second must be greater than 4"); if (rendersPerSecond <= 4) throw new ArgumentOutOfRangeException("Renders per second must be greater than 4"); this.updatesPerSecond = updatesPerSecond; this.rendersPerSecond = rendersPerSecond; // We use the higher frequency for our loop target. this.loopsPerSecond = System.Math.Max(updatesPerSecond, rendersPerSecond); // Seed the alogorithm with something reasonably close. this.timings = new InterleaveTimings(1000 / loopsPerSecond, 30); } /// /// This method must be called once per loop to allow the scheduler to progress. /// /// Set true if an update should be called, otherwise set to false. /// Set true if a frame should be redered, otherwise set to false. /// The number of seconds since the previous call to update. public double Update(out bool shouldUpdate, out bool shouldRender) { loops++; double totalElapsedSeconds = stopWatch.Elapsed.TotalSeconds; double loopElapsedSeconds = totalElapsedSeconds - previousElapsedSeconds; previousElapsedSeconds = totalElapsedSeconds; if (totalElapsedSeconds >= 0.25) { double lps = loops / totalElapsedSeconds; if (lps < loopsPerSecond - 0.25) { timings.Decrement(); } else if (lps > loopsPerSecond + 0.25) { timings.Increment(); } stopWatch.Reset(); stopWatch.Start(); loops = 0; previousElapsedSeconds = 0; } // Timeline keeps our place in each second to know which frame we should be on. timeline = (timeline + loopElapsedSeconds) % 1.0; // If update frequency is the same as loop frequency, no interpolation is needed. if (loopsPerSecond == updatesPerSecond) { shouldUpdate = true; } // Otherwise, interpolate the lower frequency else if (lastUpdateFrame != (int)(timeline * updatesPerSecond)) { shouldUpdate = true; // We only use last update frame when interpolating, so we'll only update it here. lastUpdateFrame = (lastUpdateFrame + 1) % updatesPerSecond; } else { shouldUpdate = false; } // If render frequency is the same as loop frequency, no interpolation is needed. if (loopsPerSecond == rendersPerSecond) { shouldRender = true; } // Otherwise, interpolate the lower frequency else if (lastRenderFrame != (int)(timeline * rendersPerSecond)) { shouldRender = true; // We only use last render frame when interpolating, so we'll only update it here. lastRenderFrame = (lastRenderFrame + 1) % rendersPerSecond; } else { shouldRender = false; } return loopElapsedSeconds; } /// /// Uses the sleep value at the current position in the sequence to call Thread.Sleep(). /// public void Sleep() { System.Threading.Thread.Sleep(timings.NextSleepTime); } /// /// Used to store the parameters needed to represent the desired sleep pattern. /// /// The interleave sleep pattern 9 10 9 10 9 10 9 9 9 /// Translates to the following parameters /// interleaveLength = 9 /// lowValue = 9 /// lowCount = 6 /// highValue = 10 /// highCount = 3 /// private struct InterleaveTimings { /// /// The number of sleep times per cycle. /// private readonly int interleaveLength; private int lowValue; private int lowCount; private int index; public InterleaveTimings(int seedTiming, int interleaveLength) { if (seedTiming <= 0) throw new ArgumentOutOfRangeException("Seed timing must be greater than zero."); if (interleaveLength <= 9) throw new ArgumentOutOfRangeException("Interleave length must be greater than 9"); this.lowValue = seedTiming; this.interleaveLength = interleaveLength; this.lowCount = interleaveLength; this.index = 0; } public void Increment() { if (lowCount <= 0) { lowCount = interleaveLength; lowValue += 1; } lowCount -= 1; Console.WriteLine("Incrementing LowCount={0} LowValue={1}", lowCount, lowValue); } public void Decrement() { if (lowCount >= interleaveLength) { if (lowValue == 0) return; lowCount = 0; lowValue -= 1; } lowCount += 1; Console.WriteLine("Decrementing LowCount={0} LowValue={1}", lowCount, lowValue); } public int NextSleepTime { get { int highCount = interleaveLength - lowCount; int oscilatingSegmentLength = 2 * ((highCount < lowCount) ? highCount : lowCount); int result; if (index < oscilatingSegmentLength) { // Detect odd or even by looking at the right most bit. result = ((index & 0x1) == 0x1) ? lowValue + 1 : lowValue; } else if (highCount > lowCount) { result = lowValue + 1; } else { result = lowValue; } index = (index + 1) % interleaveLength; return result; } } } } #endregion }