3

I am designing a time class that will only perform an action after a predefined time but I still cannot figure out the way to reset the time. Is that anyway to reset the time back to zero or temporarily stop and resume the time?

so, my goal is to reset the time each time the condtion A meets so that it won't screw up delayTime function that it still keep the previous time and end up wrong time calculation.

if ( condition A )
{
    if ( time.delayTime( 5.0f ) )
    {           
        doActionA();
    }
}

TimeClass.h

#ifndef _TIMECLASS_H_
#define _TIMECLASS_H_

#include <windows.h>


class TimeClass
{
public:
    TimeClass();
    ~TimeClass();
    bool delayTime( float second );
    float getSecond();
    void reset();

private:
    float getGameTime();

    float currentTime;
    UINT64 ticks;
    float time;
    float  timeAtGameStart;
    UINT64 ticksPerSecond;
};

#endif

TimeClass.cpp

#include "TimeClass.h"


TimeClass::TimeClass()
{
    // We need to know how often the clock is updated
    if( !QueryPerformanceFrequency((LARGE_INTEGER *)&ticksPerSecond) )
        ticksPerSecond = 1000;

    // If timeAtGameStart is 0 then we get the time since
    // the start of the computer when we call GetGameTime()
    timeAtGameStart = 0;
    timeAtGameStart = getGameTime();
}


float TimeClass::getGameTime()
{
    // This is the number of clock ticks since start
    if( !QueryPerformanceCounter((LARGE_INTEGER *)&ticks) )
        ticks = (UINT64)timeGetTime();

    // Divide by frequency to get the time in seconds
    time = (float)(__int64)ticks/(float)(__int64)ticksPerSecond;

    // Subtract the time at game start to get
    // the time since the game started
    time -= timeAtGameStart;

    return time;
}


bool TimeClass::delayTime( float second )
{
    currentTime = getGameTime();
    static float totalTime = second + getGameTime();

    if ( currentTime >= totalTime )
    {
        totalTime = second + getGameTime();
        return true;
    }
    else
    {
        return false;
    }
}


float TimeClass::getSecond()
{
    currentTime = getGameTime();
    static float totalTime = 1 + getGameTime();

    if ( currentTime >= totalTime )
    {
        totalTime = 1 + getGameTime();
        return currentTime;
    }
    else
    {
        return currentTime;
    }
}


void TimeClass::reset()
{
    timeAtGameStart = 0;
    timeAtGameStart = getGameTime();
}


TimeClass::~TimeClass()
{
}
Ivan Aksamentov - Drop
  • 12,860
  • 3
  • 34
  • 61
user
  • 291
  • 2
  • 7
  • 18
  • There's a standard `` header and you're using a [reserved identifier](http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier). – chris Oct 04 '13 at 11:37
  • you could add pause/play functions. Pause keeps a paused_time and play adds (play press time - paused_time) to start time – nurettin Oct 04 '13 at 12:20
  • Could you tell me how to design pause and play time functions? I am still not familiar with Windows API. – user Oct 04 '13 at 12:27

2 Answers2

2

Standard Library

As already stated simply use the std::chrono::high_resolution_clock. If you use the Visual C++ Compiler there is an actual bug report on the resolution of the high_resolution_clock and it's NOT the same as the QueryPerformanceCounter.

Example

auto t1 = std::chrono::high_resolution_clock::now();
// Do something
auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
double dt = duration.count();

TimeClass

The main problem you're facing right now is that you are using static variables. You are looking for a way to reset the static variable from another function than the delayTime. If you would introduce member variables to get rid of the static variables it would make things a lot easier (Note: static variables within functions are bad practice as they are holding the state => bad for multi-threading). Well what about something like this:

class Event {
    std::chrono::high_resolution_clock::time_point lastEventOccurred;
    double timeBetweenEvents;
public:
    Event(double timeBetweenEvents)
       : timeBetweenEvents(timeBetweenEvents)
       , lastEventOccurred(std::chrono::high_resolution_clock::now()) { }

    bool ready() {
       auto currentTime = std::chrono::high_resolution_clock::now();
       auto elapsedTime = std::chrono::duration_cast<std::chrono::duration<double>>(currentTime - lastEventOccurred).count();

       if(elapsedTime > timeBetweenEvents) {
           reset();
           return true;
       } else
           return false;
    }

    void reset() {
       lastEventOccurred = std::chrono::high_resolution_clock::now();
    }
};

Usage:

Event evtDelay = Event(5.0);

if(condition A) {
    if(evtDelay.ready()) {
        doActionA();
    }
} else if(condition B) {
    evtDelay.reset();
}

Another approach would be to use a capturing state and an update method. As I can see you are using this for a game so the updating could be done in the update method of your game. Assuming the event would have a state like {Active, Inactive} and a member for the elapsed time, every time you call update you add the elapsed time since the last update call if the state is Active. Therefore the ready function would only check if the elapsed time is greater than the defined threshold.

class Event {
    std::chrono::high_resolution_clock::time_point lastUpdateCall;
    double timeBetweenEvents;
    double elapsedTime;
    bool active;
public:
    Event(double timeBetweenEvents)
       : timeBetweenEvents(timeBetweenEvents)
       , lastUpdateCall(std::chrono::high_resolution_clock::now()) 
       , elapsedTime(0.0) 
       , active(true) { }

    bool ready() {
       return elapsedTime >= timeBetweenEvents;
    }

    void reset() {
       elapsedTime = 0.0;
    }

    void setActive(bool state) { 
       active = state; 
    }

    void update() {
       auto currentTime = std::chrono::high_resolution_clock::now();

       if(active) {
          auto dt = std::chrono::duration_cast<std::chrono::duration<double>>(currentTime - lastUpdateCall).count();
          elapsedTime += dt;
       }

       lastUpdateCall = currentTime;
    }
};
RazorX
  • 294
  • 2
  • 5
  • do u have the windows API solution? Thanks. – user Oct 04 '13 at 13:37
  • What part do you want to replace by the Windows API? Most of it is purely logical and API independent. Only the time measurement could be done by using QueryPerformanceCounter directly as you already did. Of course you could use the [Event Signaling](http://msdn.microsoft.com/en-us/library/windows/desktop/ms686915(v=vs.85).aspx) but this is mostly designed for using between threads and therefore not as fast as the solution above (Event objects get their notifications through the window message queue). – RazorX Oct 04 '13 at 13:41
  • Worth to say, that `std::chrono::high_resolution_clock` in msvc implementation is not really high resolution clock (it is typedef to `system_clock`). It's precision is too way low for certain usages. When using msvc STL, it is better stick to `boost::chrono` for now (just replace namespace, headers, and add lib). Hope they'll fix it sometimes. – Ivan Aksamentov - Drop Oct 05 '13 at 07:08
1

From your questions it is not quite clear what exactly you want to achieve: clock resetting, pausing/resuming, interval measuring, or timed triggers, or all of this. So I will just describe all of this. =)

Just to make it clear

  • do not use static variables in member functions (methods), it makes your code harder to understand and error-prone. Instead use class member variables (fields)
  • do not store time in (milli)seconds float format. I has too low precision. Even double is not recommended. Instead, store it in API-native format: some kind of CPU ticks: let's wrap it in struct time_point and convert to float only when user asks for it:

    struct time_point
    {
        LARGE_INTEGER value;
    };
    

The functions you'll need from your underlying API are:

  • function to get current time: let's call it

    private: 
         time_point now(); // Here you wrap QueryPerformanceCounter() or `boost::chrono::...::now();` stuff
    
  • functions to reinterpret difference (time duration) between two time_point to usable units (such as nanoseconds long long of (milli)seconds float)

    private:
          float Duration(const time_point& from, const time_point& to); // Here you divide by frequence
    

(also, you can make time_point class and make this functions members)

All other functions can be API-agnostic.

Following this design, you can abstract out underlying API from user and make your app more portable.

Resetting

Is that anyway to reset the time back to zero

But, wait, you've already implemented "reset the time back to zero":

void TimeClass::reset()
{
    //timeAtGameStart = 0;
    timeAtGameStart = getGameTime();
}

(timeAtGameStart = 0; commented as it non needed)

You just saying that start is now(), so difference between start and now() becomes zero.

Stopping and resuming

or temporarily stop and resume the time?

you can achieve "time stop/resume" effect by:

  • adding bool isPaused member varible (don't forget to init it to false in constructor)
  • adding timePaused member varible (of type of your time units: time_point in my case)
  • altering your getGameTime() function: when paused it will return timePaused instead of current time
  • and of course, introducing Pause()/Resume() functions

Here is example code (naming is a little different to yours)

    void Pause()
    {
        if (!isPaused)
        {
            timePaused = clock::now();
            isPaused = true;
        }
    }

    void Resume()
    {
        if (isPaused)
        {
            isPaused = false;
            start -= clock::now() - timePaused;
        }
    }

    float Now() const 
    { 
        if (isPaused)
        {
            return Duration(start, timePaused);
        }
        return Duration(start, clock::now()); // basically it is your getGameTime()
    }

Delta (basic interval measuring)

Sometimes you will want to know how much time passed between some places in your program. For example, to measure FPS in game, you will need to know delta time between frames. To implement this we will need members:

    time_point prevTick;    // time of previous tick
    time_point currTick;    // time of current tick

            void Tick() // user must call it each frame
    { 
        prevTick = currTick;
        currTick = clock::now();
    }

    const float GetDelta() const  // user must call it to know frame time
    { 
        return Duration(prevTick, currTick);
    }

Stopwatch (advanced interval measuring)

Now, we need to have some kind of timed trigger. It is a good idea to separate this triggerfrom timer, so:

  • you can have multiple trigger with different start time and different trigger intervals
  • all of this triggers must be independent from each other
  • global clocks will not be altered, so all triggers remain valid

So let's wrap it to class:

class Stopwatch
{
private:
    float m_Interval;
    float m_Time;
    float m_PrevTime;
public:
    Stopwatch(float interval_ms = 1000.0f) :
        m_Interval(interval_ms),
        m_Time(0.0f),
        m_PrevTime(m_Time)
    {}

    float GetRefreshInterval() const { return m_Interval; }
    void SetRefreshInterval(float interval_ms) { m_Interval = interval_ms; }

    // Update as frequent as possible
    inline void Update(float dt)
    {
        m_Time += dt;
    }

    // Check if time interval reached
    inline bool IsAboutTime()
    {
        if (m_Time >= m_Interval)
        {
            m_PrevTime = m_Time;
            m_Time = 0.0f;
            return true;
        }
        return false;
    }

    // exact value of last interval
    inline float GetLastTime() const
    {
        return m_PrevTime;
    }
};

Usage example:

Stopwatch stopwatch(5000.0f); // will trigger each 5 seconds 
...

stopwatch.Update(timer.GetDelta()); // GetDelta() returns time difference between update frames
if ( condition A )
{
    if ( stopwatch.IsAboutTime() )
    {           
        doActionA();
    }
}

Another possible ways to design stopwatch are:

  • inherit from clock
  • pass current time to IsAboutTime() so no need to Update()

Happy coding! =)

Ivan Aksamentov - Drop
  • 12,860
  • 3
  • 34
  • 61
  • insted of `start -= clock::now() - timePaused;` should be not `start += clock::now() - timePaused;` in Resume function? – Tom Apr 05 '15 at 20:19