EAStopwatch

Introduction

EAStopwatch provides timing facilities for use in game development. It provides two C++ classes:

Stopwatch A general-purpose timer
LimitStopwatch A special-purpose countdown-style timer

Stopwatch acts much like and classic hand-held stopwatch or like the stopwatch found on sports watches. It has additional flexibility, such as the ability to set an elapsed time to any value.

LimitStopwatch is a stopwatch which simply tells you if a given amount of time has passed. You can accomplish this same functionality by polling a Stopwatch, but LimitStopwatch is more efficient.

Important things to remember

Example usage

EAStopwatch is very easy to use, though curiously its simplicity actually confuses some users familiar with more cumbersome alternatives.

Basic usage:

Stopwatch stopwatch(Stopwatch::kUnitsMilliseconds);

stopwatch.Start();
DoSomethingThatYouWantToMeasure();
printf("Time to do something: %u.\n", stopwatch.GetElapsedTime());
Example of stopwatch reuse (via Restart):
Stopwatch stopwatch(Stopwatch::kUnitsCycles, true); // Note that 'true' tells the timer to auto-start here.

DoSomethingSmall();
printf("Time to do something small: %u.\n", stopwatch.GetElapsedTime());

stopwatch.Restart();
DoSomethingElseSmall();
printf("Time to do something else small: %d.\n", stopwatch.GetElapsedTime());
Example of SetElapsedTime usage.
Stopwatch stopwatch(Stopwatch::kUnitsMilliseconds);

stopwatch.SetElapsedTime(100); // Give the stopwatch a "100 ms head start."
stopwatch.Start();
printf("This should print out 100 (or maybe 101): %u.\n", stopwatch.GetElapsedTime());

Example of LimitStopwatch usage:

LimitStopwatch limitStopwatch(Stopwatch::kUnitsMilliseconds, 1000, true);

while(!limitStopwatch.IsTimeUp())
    printf("waiting\n"); 

Don't do this!

It seems that quite often people unfamiliar with a C++ stopwatch tend to revert away from using the stopwatch as it was designed and try to do timings manually, like shown below. The code below is more complicated than it needs to be and is less precise than it can be, as the high internal resolution of the stopwatch is lost when using it like this.

Stopwatch stopwatch(Stopwatch::kUnitsMilliseconds);

stopwatch.Start();
uint64_t timeStart = stopwatch.GetElapsedTime();
DoSomething();
uint64_t timeElapsed = stopwatch.GetElapsedTime() - time;

Interface

Stopwatch

class Stopwatch
{
public:
    /// Units
    /// Defines common timing units plus a user-definable set of units.
    enum Units
    {
        kUnitsCycles       =    0,  /// Stopwatch clock ticks. 
        kUnitsCPUCycles    =    1,  /// CPU clock ticks (or similar equivalent for the platform).
        kUnitsNanoseconds  =    2,  /// Count in nanoseconds.
        kUnitsMicroseconds =    3,  /// Count in microseconds.
        kUnitsMilliseconds =    4,  /// Count in milliseconds.
        kUnitsSeconds      =    5,  /// Count in seconds.
        kUnitsMinutes      =    6,  /// Count in minutes.
        kUnitsUserDefined  = 1000   /// User defined units (animation frames, video vertical retrace, etc.).
    };

public:
    /// Stopwatch
    /// Constructor for Stopwatch, allows user to specify units. 
    /// If units are kUnitsUserDefined,  you'll need to either subclass 
    /// Stopwatch and implement GetUserDefinedStopwatchCycle or call 
    /// SetUserDefinedUnitsToStopwatchCyclesRatio in order to allow it 
    /// to know how to convert units.
    explicit Stopwatch(int nUnits = kUnitsCycles, bool bStartImmediately = false);

    /// Stopwatch
    /// Copy constructor.
    Stopwatch(const Stopwatch& stopwatch);

    /// ~Stopwatch
    /// Destructor.
    ~Stopwatch();

    /// operator=
    /// Assignment operator.
    Stopwatch& operator=(const Stopwatch& stopwatch);

    /// GetUnits
    /// Gets the current units. Returns one of enum Units or kUnitsUserDefined+.
    int GetUnits() const;

    /// SetUnits
    /// Sets the current units. One of enum Units or kUnitsUserDefined+.
    void SetUnits(int nUnits);

    /// Start
    /// Starts the stopwatch. Continues where it was last stopped. 
    /// Does nothing if the stopwatch is already started.
    void Start();

    /// Stop
    /// Stops the stopwatch it it was running and retaines the elasped time.
    void Stop();

    /// Reset
    /// Stops the stopwatch if it was running and clears the elapsed time.
    void Reset();

    /// Restart
    /// Clears the elapsed time and starts the stopwatch if it was not 
    /// already running. Has the same effect as Reset(), Start().
    void Restart();

    /// IsRunning
    /// Returns true if the stopwatch is running.
    bool IsRunning() const;

    /// GetElapsedTime
    /// Gets the elapsed time, which properly takes into account any 
    /// intervening stops and starts. Works properly whether the stopwatch 
    /// is running or not.
    uint64_t GetElapsedTime() const;

    /// SetElapsedTime
    /// Allows you to set the elapsed time. Erases whatever is current. 
    /// Works properly whether the stopwatch is running or not.
    void SetElapsedTime(uint64_t nElapsedTime);

    /// GetElapsedTimeFloat
    /// Float version, which is useful for counting fractions of 
    /// seconds or possibly milliseconds.
    float GetElapsedTimeFloat() const;

    /// SetElapsedTimeFloat
    /// Allows you to set the elapsed time. Erases whatever is current. 
    /// Works properly whether the stopwatch is running or not.
    void SetElapsedTimeFloat(float fElapsedTime);

    /// SetCyclesPerUnit
    /// Allows the user to manually override the frequency of the 
    /// timer. 
    void SetCyclesPerUnit(float fCyclesPerUnit);

    /// GetCyclesPerUnit
    /// Returns the value number of cycles per unit. If the user 
    /// set a manual value via SetCyclesPerUnit, this function returns 
    /// that value.
    float GetCyclesPerUnit() const;

    /// GetStopwatchCycle
    /// Gets the current stopwatch cycle on the current machine.
    /// Note that a stopwatch cyle may or may not be the same thing
    /// as a CPU cycle. We provide the distinction between stopwatch
    /// cycles and CPU cycles in order to accomodate platforms (e.g.
    /// desktop platforms) in which CPU cycle counting is unreliable.
    static uint64_t GetStopwatchCycle();

    /// GetStopwatchFrequency
    /// Note that the stopwatch freqency may or may not be the same thing
    /// as the CPU freqency. We provide the distinction between stopwatch
    /// cycles and CPU cycles in order to accomodate platforms (e.g.
    /// desktop platforms) in which CPU cycle counting is unreliable.
    static uint64_t GetStopwatchFrequency();

    /// GetUnitsPerStopwatchCycle
    /// Returns the number of stopwatch cycles per the given unit.  
    /// If the unit is seconds, the return value would be the frequency of 
    /// the stopwatch timer and thus be the same value as returned by
    /// GetStopwatchFrequency().
    static float GetUnitsPerStopwatchCycle(Units units);

    /// GetCPUCycle
    /// Gets the current CPU-based timer cycle on the current processor 
    /// (if in a multiprocessor system). Note that this doesn't necessarily
    /// get the actual machine CPU clock cycle; rather it returns the 
    /// CPU-based timer cycle. One some platforms the CPU-based timer is
    /// a 1:1 relation to the CPU clock, while on others it is some multiple
    /// of it.
    static uint64_t GetCPUCycle();

    /// GetCPUFrequency
    /// Gets the frequency of the CPU-based timer. Note that this doesn't 
    /// necessarily get the actual machine CPU clock frequency; rather it returns  
    /// the CPU-based timer frequency. One some platforms the CPU-based timer is
    /// a 1:1 relation to the CPU clock, while on others it is some multiple of it.
    static uint64_t GetCPUFrequency(); 

    /// GetUnitsPerCPUCycle
    /// Returns the number of CPU cycles per the given unit.  
    /// If the unit is seconds, the return value would be the frequency 
    /// of the CPU-based timer.
    static float GetUnitsPerCPUCycle(Units units);
};
  

LimitStopwatch

class LimitStopwatch : public Stopwatch
{
public:
    /// LimitStopwatch
    /// Constructor
    LimitStopwatch(int nUnits = kUnitsCycles, uint32_t nLimit = 0, bool bStartImmediately = false);

    /// SetTimeLimit
    /// Sets the time limit and lets you start the stopwatch at the same time.
    void SetTimeLimit(uint32_t nLimit, bool bStartImmediately = false); 

    /// IsTimeUp
    /// Returns true if the limit has been reached. Highly efficient.
    bool IsTimeUp();

    /// GetTimeRemaining
    /// More expensive than IsTimeUp, as it returns a value.
    int64_t GetTimeRemaining();

    /// GetTimeRemainingFloat
    /// More expensive than IsTimeUp, as it returns a value.
    float GetTimeRemainingFloat();
};