src/main/java/engine/Timer.java
package engine;
/**
* The {@code Timer} class provides a utility for tracking elapsed time, frames per second (FPS),
* and time scaling for games or applications. It uses nanosecond precision for timekeeping and
* offers features such as formatted time representation, time-per-frame calculation, and
* slow-motion or speed-up effects via time scaling.
*
* <p>Key features include:
*
* <ul>
* <li>Tracking total elapsed time (scaled and unscaled).
* <li>Calculating frames per second (FPS).
* <li>Formatting time as hours:minutes:seconds.
* <li>Adjustable time scaling for slow-motion or fast-forward effects.
* </ul>
*
* This class is designed to be updated on every frame of an application or game.
*/
public class Timer {
/** Real world time when the timer started. */
private long startTime;
/** The last recorded time in nanoseconds. */
private long lastTime;
/** The time in milliseconds taken for the last frame. */
private float time;
/** Accumulates milliseconds for FPS calculation. */
private long millisecondCounter;
/** The frame count during the last FPS update. */
private int lastFrameCount;
/** The calculated frames per second (FPS). */
private int fps;
/** Total elapsed time in milliseconds. */
private long totalTime;
/** Scaling factor for time (default is 1.0 for real-time). */
private float timeScale;
/** Total number of frames since the Timer started. */
private int frameCount;
/** Constructs a {@code Timer} with a default time scale of 1.0. */
public Timer() {
this.startTime = System.nanoTime();
this.lastTime = System.nanoTime();
this.time = 0;
this.totalTime = 0;
this.timeScale = 1f;
this.frameCount = 0;
}
/**
* Constructs a {@code Timer} with the specified initial time scale.
*
* @param initialTimeScale the initial time scaling factor
*/
public Timer(float initialTimeScale) {
this.timeScale = initialTimeScale;
}
/**
* Returns the current frames per second (FPS).
*
* @return the frames per second
*/
public float getFrameRate() {
return fps;
}
/** Updates the FPS calculation based on the accumulated milliseconds. */
private void updateFPS() {
millisecondCounter += time;
if (millisecondCounter >= 1000) {
millisecondCounter = 0;
fps = frameCount - lastFrameCount;
lastFrameCount = frameCount;
}
}
/**
* Updates the Timer. This method must be called once per frame to ensure accurate time tracking.
*/
public void update() {
long currentTime = System.nanoTime();
time = (currentTime - lastTime) / 1_000_000.0f; // Convert to milliseconds
lastTime = currentTime;
totalTime += time;
frameCount++;
updateFPS();
}
/**
* Resets the {@code Timer} to its initial state, clearing all accumulated time and frame count
* values. This includes resetting the following:
*
* <ul>
* <li>The start time to the current system time.
* <li>The last time recorded to the current system time.
* <li>The total elapsed time to zero.
* <li>The frame count to zero.
* <li>The frames per second (FPS) to zero.
* <li>The millisecond counter to zero.
* <li>The last frame count for FPS calculation to zero.
* </ul>
*
* <p>This method can be used when you need to restart the timer, such as for restarting the game
* / application or resetting the simulation state.
*/
public void reset() {
this.startTime = System.nanoTime();
this.lastTime = startTime;
this.time = 0;
this.totalTime = 0;
this.frameCount = 0;
this.fps = 0;
this.millisecondCounter = 0;
this.lastFrameCount = 0;
}
/**
* Returns the total elapsed time in seconds, scaled by the current time scale.
*
* @return the scaled total elapsed time in seconds
*/
public float getTotalTime() {
return totalTime / 1000.0f * timeScale;
}
/**
* Returns the total elapsed time in seconds, independent of the time scale.
*
* @return the unscaled total elapsed time in seconds
*/
public float getUnscaledTotalTime() {
return totalTime / 1000.0f;
}
/**
* Returns a formatted string representing the scaled total time in the format HH:MM:SS.
*
* @return the formatted scaled total time
*/
public String getFormattedTotalTime() {
return formatTime(getTotalTime());
}
/**
* Returns a formatted string representing the unscaled total time in the format HH:MM:SS.
*
* @return the formatted unscaled total time
*/
public String getUnscaledFormattedTotalTime() {
return formatTime(getUnscaledTotalTime());
}
/**
* Returns the time it took to complete the last frame in seconds, scaled by the current time
* scale.
*
* @return the scaled time per frame in seconds
*/
public float getTimePerFrame() {
return time / 1000.0f * timeScale;
}
/**
* Returns the time it took to complete the last frame in seconds, independent of the time scale.
*
* @return the unscaled time per frame in seconds
*/
public float getUnscaledTimePerFrame() {
return time / 1000.0f;
}
/**
* Returns the current time scaling factor.
*
* @return the time scale
*/
public float getTimeScale() {
return timeScale;
}
/**
* Sets the time scaling factor. A value of 1.0 represents real-time, values less than 1.0 slow
* down time, and values greater than 1.0 speed up time.
*
* @param timeScale the new time scaling factor
*/
public void setTimeScale(float timeScale) {
this.timeScale = timeScale;
}
/**
* Returns the real-time elapsed since the game / application started, measured in seconds.
*
* <p>This method uses `System.nanoTime()` to obtain a high-precision timestamp. The returned
* value is a `float` representing the elapsed time in seconds.
*
* @return The real-time elapsed since the game started, in seconds.
*/
public float getRealtimeSinceStartup() {
return (System.nanoTime() - startTime) / 1_000_000_000.0f;
}
/**
* Returns the real-time elapsed since the game / application started, measured in seconds, as a
* `double` value.
*
* <p>This method uses `System.nanoTime()` to obtain a high-precision timestamp. The returned
* value is a `double` representing the elapsed time in seconds, providing higher precision than
* the `float` version.
*
* @return The real-time elapsed since the game started, in seconds, as a `double`.
*/
public double getRealtimeSinceStartupAsDouble() {
return (System.nanoTime() - startTime) / 1_000_000_000.0;
}
/**
* Returns the total number of frames that have passed since the Timer started.
*
* @return the total frame count
*/
public int getFrameCount() {
return frameCount;
}
/**
* Formats a time value in seconds into a string in the format HH:MM:SS.
*
* @param timeInSeconds the time in seconds to format
* @return the formatted time string
*/
private String formatTime(float timeInSeconds) {
int s = (int) timeInSeconds;
return String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, s % 60);
}
/**
* Returns a string representation of this Timer, showing its current state.
*
* @return a string representation of the Timer
*/
@Override
public String toString() {
return "Timer [millisecondCounter="
+ millisecondCounter
+ ", lastFrameCount="
+ lastFrameCount
+ ", fps="
+ fps
+ ", lastTime="
+ lastTime
+ ", time="
+ time
+ ", totalTime="
+ totalTime
+ ", timeScale="
+ timeScale
+ ", frameCount="
+ frameCount
+ "]";
}
}