Saturday, October 6, 2012

Lesson 8: Timers

Deprecation Notice:

I've been updating the lessons now that SDL2 is officially released, please visit my new site on Github that has updated lessons and improved code.


In this lesson we'll add onto our small class library of one (the Window class) by creating a simple timer and then use it in a simple program. To do this we'll be making use of the SDL_Timer functions, specifically SDL_GetTicks which returns the milliseconds elapsed since SDL was initialized.

So how would we be able to measure a time if we can only tell how long it's been since SDL was initialized? Well we could mark down the value of SDL_GetTicks when we start the timer as startTicks and then again when we stop it as endTicks. We can then subtract endTicks - startTicks to get the milliseconds elapsed during the measurement. Seems easy enough right?

Let's begin planning out how we'd like our Timer to function before we start putting it together. We'll definitely need functions for starting, stopping and getting the elapsed ticks (measured in milliseconds), and we'll also want to be able to check if the timer has been started. In addition I think it'd be nice if we could pause and unpause the timer, and maybe a function to restart it that would return the elapsed ticks and start the timer over again all in one go. We'll also need variables to track the start and pause points as mentioned above in how to use SDL_GetTicks to determine the elapsed time, along with some values to track the state of the timer.

So let's try something like this:

That looks pretty good, so let's get started implementing these functions in timer.cpp, beginning with the constructor.

When we construct a new timer we want it to be off, ie. not started or paused. We can do this like so:

In our Start function we'll want to tag the timer as started and record the value of SDL_GetTicks when Start was called, so that we can properly return the elapsed time, which is the difference between the current SDL_GetTicks and the value of mStartTicks. In our Stop function we simply want to tag the timer as stopped, by setting mStarted and mPaused to false.

For our Pause and Unpause functions we'll need to do a bit more work. We wouldn't want to pause the timer if it wasn't started or was already paused as both operations don't really make sense. When we pause the timer we'll also need to store the elapsed time so that we can preserve the timer's value so that when we unpause we continue adding on to the measured time instead of resetting it. We can do this with the method discussed above, were we take SDL_GetTicks - mStartTicks as the elapsed time, where mStartTicks is the value of SDL_GetTicks when the timer was started. So Pause should look like this:

So now mPausedTicks stores the elapsed ticks, so we can resume timing when we call Unpause. In Unpause we'll want to mark the timer as not paused and use the value of mPausedTicks to set the value of mStartTicks to have some offset from the value of SDL_GetTicks at the time Unpause is called so that we preserve the value of the timer. We can do this by setting mStartTicks to SDL_GetTicks - mPausedTicks, so Unpause also ends up being quite simple:

Our function Restart, to restart the timer and return the elapsed time turns out to be quit simple. The function Ticks is used to get the elapsed time, then the timer is restarted and the value is returned. This ordering is very important as we need to get the elapsed time before we reset the timer, or else we'll return 0 every time.

Finally we arrive at the most important function of our Timer, the one that actually tells us how much time has elapsed! We'll only want to return a value if the timer has been started, if the timer is paused we'll want to return the value of mPausedTicks (the elapsed time between Start and Pause), if the timer is running we'll want to return return the elapsed time, with SDL_GetTicks - mStartTicks. If it's not started then we'll want to return 0, as no time has been measured.

We finish off our timer with some simple getters to check if the Timer is started or paused:

And there we have it! A useful, simple timer. Now let's try using it in a program to make sure it works. We'll make a very simple program that will display the elapsed time and read some input for starting/stopping/pausing/unpausing the timer. For this program we'll also need the Window class that we wrote in Lesson 7 to provide the various graphics functions we'll need.

I'll only be posting code relevant to the specifics of using the timer in the lesson, but if you have difficulty with some code that isn't posted you can always find the full source and assets for each lesson on Github.

After opening our Window we'll want to create an instance of the Timer class and some SDL_Textures to hold our messages.

Here we also setup the message box positioning to stick a bit below the middle of the window height (recall that y is the y position of the top-left corner) and set its width and height equal to the texture's width and height.

We also want to display the value of the timer's Ticks function, the elapsed time, after the end of the "Ticks Elapsed: " message. There's one small issue though, Ticks returns an int, but we need a string to render a message with. This can be resolved by using a stringstream; we write Ticks to the stream and then pass it to the message creation function as a string, like so:

We then clear the stringstream by filling it with a blank string and set the positioning of the message to be a bit after the text message.

Within our event polling loop we'll want to check for some key presses to tell the timer to start/stop/pause/unpause as desired.

Now before we render everything we've got one last problem to solve. How are we going to update the texture displaying the number of ticks on the screen? We can do something where we simply destroy the old texture and recreate a new one with the updated Ticks, but we wouldn't want to do this if the timer is paused or stopped since there'd be no reason to change the message.

Sounds like we'll just need an if statement, and to copy down the code we used to create the texture initially:

We can put this bit of code in our logic section right after the input polling. Our rendering section will just draw the two SDL_Textures with their Rect's and we finish off the program as always by destroying our textures, calling Window::Quit and returning.

When you run the program you should see something like this:
Where the timer will start at 0 and begin increasing once you start it. Pausing will cause the timer to stop counting, unpausing will resume it from where it left off. Stopping the timer will stop it, and when restarting it will begin again at 0. Note that the value displayed doesn't reset when the timer is stopped but rather when it's resumed.

Lesson 8 Extra Challenge!

Make an additional message display to state whether the timer is stopped/paused
Hint


End of Lesson 8

Thanks for joining me! I'll see you again soon in Lesson 9: Playing sounds with SDL_mixer.