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
}