/*
* Copyright (c) 2009, Hamish Morgan. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the University of Sussex nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package locusts.lib;
import java.text.FieldPosition;
import java.text.NumberFormat;
import locusts.server.Game;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>FrameCounter</code> class maintaining a counter and calculating the
* FPS (Frames Per Second). Once instantiated the <code>markFrame()</code>
* method should be called every time a time step occurs. The frame rate and
* counter can then be read by calling <code>getFps()</code> and
* <code>getFrameCount()</code> respectively.
* <p>
* It also writes to the default logger every time the FPS value is updated, at
* log level <code>Level.INFO</code>.
* </p>
*
* @author Hamish Morgan I A Morgan <code><hiam20@sussex.ac.uk></code>
* @version 1.02, 14<sup>th</sup> July 2009
*/
public final class FrameCounter {
/**
* Logger for this class
*/
private static final Logger LOG = LoggerFactory.getLogger(FrameCounter.class);
/**
* Default time between FPS updates.
*/
private static final long DEFAULT_UPDATE_TIME = 1000l;
/**
* Default name to use when printing with <code>toString()</code>.
*/
private static final String DEFAULT_NAME = "Unnamed";
/**
* The <code>NumberFormat</code> object enables the FPS printing with only a
* few decimal places. (Otherwise it is unreadable.)
*/
private static final NumberFormat NUMBER_FORMAT = NumberFormat.
getNumberInstance();
/**
* To get the <code>NumberFormat</code> object to write to a pre-existing
* <code>StringBuffer</code> you have to pass in this pointless parameter.
* Otherwise it will create a new <code>StringBuffer</code> every time.
*/
private static final FieldPosition FIELD_POSITION = new FieldPosition(
NumberFormat.INTEGER_FIELD);
/**
* Name to output when <code>toString</code> is called.
*/
private final String name;
/**
* Time (in milliseconds) between updates to the FPS value.
*/
private final long updateTime;
/**
* Total number of calls to markFrame since last reset.
*/
private int count;
/**
* Counter value when the FPS value was last updated.
*/
private int lastUpdateCount;
/**
* Time (in milliseconds) when FPS value was last updated.
*/
private long lastUpdateTime;
/**
* The last calculated value for the frames-per-second.
*/
private double fps;
/**
* The time in milliseconds that markFrame was called last.
*/
private long lastFrameTime;
/**
* Construct a new instance of the <code>FrameCounter</code> class that will
* use the given name when <code>toString</code> is called and will update
* the FPS values every updateTime milliseconds.
*
* @param name
* What the counter is called for toString
* @param updateTime
* How often the FPS value is updated (in milliseconds)
* @throws IllegalArgumentException
* When the name parameter is null, or when the
* updateTime is less than 1.
*/
public FrameCounter(final String name, final long updateTime) {
if (name == null)
throw new IllegalArgumentException(
"The name parameter must be non-null");
if (updateTime < 1)
throw new IllegalArgumentException(
"The updateTime paramter must be creater than 0.");
this.name = name;
this.updateTime = updateTime;
reset();
}
/**
* Construct a new instance of the <code>FrameCounter</code> class. It will
* be named <code>"Unnamed"</code> and will update the FPS counter ever
* second (1000 milliseconds)
*
* @throws IllegalArgumentException
* When the name parameter is null or when the
* updateTime is less than 1.
*/
public FrameCounter() {
this(DEFAULT_NAME, DEFAULT_UPDATE_TIME);
}
/**
* Construct a new instance of the class that will use the given name when
* toString is called. It will update the fps counter ever second (1000 ms).
*
* @param name
* What the counter is called for toString
* @throws IllegalArgumentException
* When the name parameter is null or when the
* updateTime is less than 1.
*/
public FrameCounter(final String name) {
this(name, DEFAULT_UPDATE_TIME);
}
/**
* Method that sets all object fields to their default initial states. This
* returns the <code>FrameCounter</code> object to the state it was in when
* it was instantiated.
*/
public void reset() {
count = 0;
lastUpdateCount = 0;
lastUpdateTime = System.currentTimeMillis();
lastFrameTime = lastUpdateTime;
fps = Double.POSITIVE_INFINITY;
}
/**
* Record that a discrete time step has occurred and increment the frame
* counter by 1.
*/
public void markFrame() {
count++;
lastFrameTime = System.currentTimeMillis();
}
/**
* Return the time in milliseconds since the last call to
* <code>markFrame</code>.
*
* @return Time the last frame was marked.
*/
public long getLastFrameTime() {
return lastFrameTime;
}
public long getTimeSinceLastFrame() {
return System.currentTimeMillis() - lastFrameTime;
}
public double getTimeSinceLastFrameSeconds() {
return getTimeSinceLastFrame() / 1000D;
}
/**
* Update the FPS value if enough time has passed since the last update,
* otherwise do nothing. In this way the FPS is not calculated all the time
* and represents the average FPS over a period, defined by the
* <code>updateTime</code> parameter to the constructor.
*/
private void doUpdate() {
if (lastFrameTime - lastUpdateTime >= updateTime) {
fps = 1000d * (count - lastUpdateCount) / (lastFrameTime -
lastUpdateTime);
lastUpdateCount = count;
lastUpdateTime = lastFrameTime;
LOG.info(toString());
}
}
/**
* Return the previously calculated FPS value. If not enough time has passed
* since the object was reset (or constructed) then this will have the value
* of <code>Double.POSITIVE_INFINITY</code>
*
* @return number of frames per second recorded in the last interval.
*/
public double getFps() {
doUpdate();
return fps;
}
/**
* Return the total number of frames since the last reset or object
* construction. This is the number of calls to the <code>markFrame()</code>
* method.
*
* @return number of frames since last reset.
*/
public int getFrameCount() {
return count;
}
/**
* Return a string representation of the <code>FrameCounter</code> object.
* It will consist of the number of the counter, as supplied in the
* constructor, and last calculated frame rate in FPS (Frames Per Second.)
* The FPS will be displayed using the default number formatter for the
* default locale. It will probably look something like this:
*
* <pre>
* <Name> Frame Rate: 60.123 FPS
* </pre>
*
* @return string representation of the <code>FrameCounter</code> object.
*/
@Override
public String toString() {
final StringBuffer buffer = new StringBuffer(32);
if (name.length() > 0) {
buffer.append(name);
buffer.append(' ');
}
buffer.append("Frame Rate: ");
if (getFps() == Double.POSITIVE_INFINITY)
buffer.append("Calculating");
else {
NUMBER_FORMAT.format(getFps(), buffer, FIELD_POSITION);
buffer.append(" FPS");
}
return buffer.toString();
}
}
|