/** * Copyright 2012 Jason Sorensen (sorensenj@smert.net) * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package net.smert.frameworkgl; import net.smert.frameworkgl.utils.TimeSpan; /** * * @author Jason Sorensen <sorensenj@smert.net> */ public class Timer { private final static int FRAME_SLOTS = 100; public final static long ONE_NANO_SECOND = 1000000000L; private boolean isGameTick; private boolean isRenderTick; private double lostGameTicks; private double lostRenderTicks; private double remainingGameTicks; private double remainingRenderTicks; private float delta; private float totalTime; private int frameCounter; private int gameTicksPerSecond; private int renderTicksPerSecond; private long currentElapsedTimeDifference; private final FrameAve frameAve; private final TimeSpan elapsedTimeSpan; private final TimeSpan totalTimeSpan; public Timer() { frameAve = new FrameAve(FRAME_SLOTS); elapsedTimeSpan = new TimeSpan(); totalTimeSpan = new TimeSpan(); reset(); } public double getLostGameTicks() { return lostGameTicks; } public double getLostRenderTicks() { return lostRenderTicks; } public double getRemainingGameTicks() { return remainingGameTicks; } public double getRemainingRenderTicks() { return remainingRenderTicks; } public float getDelta() { return delta; } public float getTotalTime() { return totalTime; } public int getFrameCounter() { return frameCounter; } public int getGameTicksPerSecond() { return gameTicksPerSecond; } public int getRenderTicksPerSecond() { return renderTicksPerSecond; } public long getCurrentElapsedTimeDifference() { return currentElapsedTimeDifference; } public boolean isGameTick() { return isGameTick; } public boolean isRenderTick() { return isRenderTick; } public final void reset() { isGameTick = false; isRenderTick = false; lostGameTicks = 0; lostRenderTicks = 0; remainingGameTicks = 0; remainingRenderTicks = 0; delta = 0; totalTime = 0; frameCounter = 0; gameTicksPerSecond = 0; renderTicksPerSecond = 0; currentElapsedTimeDifference = 0; frameAve.init(ONE_NANO_SECOND / 60L); elapsedTimeSpan.reset(); totalTimeSpan.reset(); } public void update() { // Update the elapsed time since the last frame and increase the counter elapsedTimeSpan.update(); frameCounter++; // The number of nanoseconds since the last update. Save the difference for computing the average. currentElapsedTimeDifference = elapsedTimeSpan.diffLastUpdateToLastTime(); delta = (float) elapsedTimeSpan.diffLastUpdateToLastTimeDouble(); frameAve.add(currentElapsedTimeDifference); // Get the total time since we started totalTime = (float) totalTimeSpan.diffNowToLastUpdateDouble(); // Get the number of game ticks per second and render ticks based on the frame rate average gameTicksPerSecond = Fw.config.gameTicksPerSecond; renderTicksPerSecond = (int) (ONE_NANO_SECOND / frameAve.avg()); assert (gameTicksPerSecond > 0); assert (renderTicksPerSecond > 0); // We want the total time for each type of tick in the number of nanoseconds per tick double gameTick = ONE_NANO_SECOND / gameTicksPerSecond; double renderTick = ONE_NANO_SECOND / renderTicksPerSecond; // The current time difference in nanoseconds is used to compute a percent of a tick that // has elapsed since the last frame. remainingGameTicks += currentElapsedTimeDifference / gameTick; remainingRenderTicks += currentElapsedTimeDifference / renderTick; // Check to see if we have enough remaining time to take a tick isGameTick = (remainingGameTicks >= 1D); isRenderTick = (remainingRenderTicks >= 1D); // We are only allowed to take 1 tick per frame if (isGameTick) { remainingGameTicks -= 1D; } if (isRenderTick) { remainingRenderTicks -= 1D; } // If we have any more than two ticks remaining then it is lost. If things are going well the tiny fraction // that will be added to the remaining ticks next frame shouldn't keep us from losing time. Render ticks are // going to be much more sensitive to this. while (remainingGameTicks > 2D) { lostGameTicks += 1D; remainingGameTicks -= 1D; } while (remainingRenderTicks > 2D) { lostRenderTicks += 1D; remainingRenderTicks -= 1D; } } @Override public String toString() { return "(isGameTick= " + isGameTick + " isRenderTick= " + isRenderTick + " lostGameTicks= " + lostGameTicks + " lostRenderTicks= " + lostRenderTicks + " remainingGameTicks= " + remainingGameTicks + " remainingRenderTicks= " + remainingRenderTicks + " delta= " + delta + " totalTime= " + totalTime + " frameCounter= " + frameCounter + " gameTicksPerSecond= " + gameTicksPerSecond + " renderTicksPerSecond= " + renderTicksPerSecond + " currentElapsedTimeDifference= " + currentElapsedTimeDifference + ")"; } private static class FrameAve { private final long[] slots; private int offset; public FrameAve(int count) { this.slots = new long[count]; this.offset = 0; } public void add(long value) { this.slots[this.offset] = value; this.offset = (this.offset + 1) % this.slots.length; } public long avg() { long sum = 0L; for (int i = 0; i < this.slots.length; i++) { sum += this.slots[i]; } return sum / this.slots.length; } public void init(long value) { for (int i = 0; i < this.slots.length; i++) { this.slots[i] = value; } } } }