Lazy Foo' Productions

SDL Forums external link SDL Tutorials 🐰SDL3 Tutorials🥚 Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Advanced Timers

Advanced Timers screenshot

Last Updated: Oct 15th, 2024

We did a very basic timer in the previous tutorial, now it's time to make one that is a little more practical.
class LTimer
{
    public:
        //Initializes variables
        LTimer();

        //The various clock actions
        void start();
        void stop();
        void pause();
        void unpause();

        //Gets the timer's time
        Uint64 getTicksNS();

        //Checks the status of the timer
        bool isStarted();
        bool isPaused();

    private:
        //The clock time when the timer started
        Uint64 mStartTicks;

        //The ticks stored when the timer was paused
        Uint64 mPausedTicks;

        //The timer status
        bool mPaused;
        bool mStarted;
};
Here is our timer class with more of the functionality you would expect in a timer. It can start, stop, pause, and unpause. It has functions to check whether it's started or paused and the ability to get its time in nanoseconds (more on that later).

We have data members to store the start time of the timer, the time at moment of pausing, and some flags for the timer status.
//LTimer Implementation
LTimer::LTimer():
    mStartTicks{ 0 },
    mPausedTicks{ 0 },

    mPaused{ false },
    mStarted{ false }
{

}
Here we are again initializing variables with constructor initializer lists because our constructor isn't doing anything but assigning some values. Remember, if you code in a way that's nice to the compiler, the compiler will be nice to you with optimizations.
void LTimer::start()
{
    //Start the timer
    mStarted = true;

    //Unpause the timer
    mPaused = false;

    //Get the current clock time
    mStartTicks = SDL_GetTicksNS();
    mPausedTicks = 0;
}

void LTimer::stop()
{
    //Stop the timer
    mStarted = false;

    //Unpause the timer
    mPaused = false;

    //Clear tick variables
    mStartTicks = 0;
    mPausedTicks = 0;
}
As you can see, starting the timer resets the status flags, resets the pause time, and grabs the current application time with SDL_GetTicksNS. SDL_GetTicksNS gets the application time in nanoseconds as opposed to milliseconds. Nanoseconds are a billionth of a second so it's more precise. This also means 1 second is 1,000,000,000, 10 seconds is 10,000,000,000, and a minute is 60,000,000,000.

When we stop the timer, we just reset the variables to where they were when the timer was constructed.
void LTimer::pause()
{
    //If the timer is running and isn't already paused
    if( mStarted && !mPaused )
    {
        //Pause the timer
        mPaused = true;

        //Calculate the paused ticks
        mPausedTicks = SDL_GetTicksNS() - mStartTicks;
        mStartTicks = 0;
    }
}
When we pause the timer, we want to first make sure it's actually running and not already paused. Then we update the status flag, store the time at moment of pausing, and reset the start time so we don't have a stray value.
void LTimer::unpause()
{
    //If the timer is running and paused
    if( mStarted && mPaused )
    {
        //Unpause the timer
        mPaused = false;

        //Reset the starting ticks
        mStartTicks = SDL_GetTicksNS() - mPausedTicks;

        //Reset the paused ticks
        mPausedTicks = 0;
    }
}
Unpausing consists of first checking we're in a valid state to unpause, updating the pause flag, setting the start time to be the current time minus time at pausing. This makes sense because say you paused at 10,000,000,000 after starting at 5,000,000,000 (a 5 second difference), if were to unpause at 30,000,000,000 you would want the new start time to be at 25,000,000,000 to keep that 5 second time difference.

And we also reset the pause time for the sake of not having any stray values.
Uint64 LTimer::getTicksNS()
{
    //The actual timer time
    Uint64 time = 0;

    //If the timer is running
    if( mStarted )
    {
        //If the timer is paused
        if( mPaused )
        {
            //Return the number of ticks when the timer was paused
            time = mPausedTicks;
        }
        else
        {
            //Return the current time minus the start time
            time = SDL_GetTicksNS() - mStartTicks;
        }
    }

    return time;
}
When getting the time, we first check if we even started the timer. If not, we just end up returning 0. If the timer is paused, we return the time at the moment of pausing. If the timer is running, we return the current time minus the start time.
            //Application timer
            LTimer timer;

            //In memory text stream
            std::stringstream timeText;
Before we head into the main loop we declare our timer.
                //Get event data
                while( SDL_PollEvent( &e ) )
                {
                    //If event is quit type
                    if( e.type == SDL_EVENT_QUIT )
                    {
                        //End the main loop
                        quit = true;
                    }
                    //Reset start time on return keypress
                    else if( e.type == SDL_EVENT_KEY_DOWN )
                    {
                        //Start/stop
                        if( e.key.key == SDLK_RETURN )
                        {
                            if( timer.isStarted() )
                            {
                                timer.stop();
                            }
                            else
                            {
                                timer.start();
                            }
                        }
                        //Pause/unpause
                        else if( e.key.key == SDLK_SPACE )
                        {
                            if( timer.isPaused() )
                            {
                                timer.unpause();
                            }
                            else
                            {
                                timer.pause();
                            }
                        }
                    }
                }
In the event loop with start/stop the timer with enter and pause/unpause it with the space bar.
                //Update text
                timeText.str("");
                timeText << "Milliseconds since start time " << ( timer.getTicksNS() / 1000000 ); 
                SDL_Color textColor = { 0x00, 0x00, 0x00, 0xFF };
                gTimeTextTexture.loadFromRenderedText( timeText.str().c_str(), textColor );


                //Fill the background
                SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF,  0xFF );
                SDL_RenderClear( gRenderer );

                //Draw text
                gTimeTextTexture.render( ( kScreenWidth - gTimeTextTexture.getWidth() ) / 2.f,  ( kScreenHeight - gTimeTextTexture.getHeight() ) / 2.f );

                //Update screen
                SDL_RenderPresent( gRenderer );
We want the time in milliseconds, so when we create the string text we divide the nanoseconds by 1,000,000. After we create the text texture, we then render the time text to the screen.