What Time Is It?

[ LiB ]

Time is a tricky subject, because you need different ways of getting the time on every system out there. I'll only cover the methods for Windows and Linux.

Windows

On Windows, you can use the timeGetTime() API function, but this practice is usually frowned upon. While the function theoretically is a millisecond-timer, in actual use, it isn't nearly as accurate. Sometimes the function can be off by dozens of milliseconds , and it just doesn't cut the mustard when you need precision.

Luckily, there is an alternative Windows practicethe performance counter. All x86 CPUs since the original Pentium have had a performance counter timer built into them, that is incredibly precise. This means that you can't use the performance counter on machines older than a Pentium, but seriously, who still has those around?

Here's the catch though: The precision, while extremely accurate, varies based on the system. The performance counter has a value called the frequency , which represents how many times per second the performance counter is updated. A value of 1,000 means that the counter updates 1,000 times a second, and therefore every millisecond.

On my Athlon 1600, for example, this value is 3,579,545. In other words, the performance counter updates 3.5 million times a second, which means that my timer updates once every 279 nanoseconds. That's precise.

So, you can pretty much use the performance counter as a microsecond timer if you want, but I don't have a need for that much precision. I just want a decent millisecond timer.

Getting the Frequency

To use the performance counter functions, the windows.h file needs to be included in your source files. Here is the function definition for the frequency function:

 BOOL QueryPerformanceFrequency( LARGE_INTEGER *lpFrequency ); 

LARGE_INTEGER is a type of data that is close to being an __int64 , but isn't quite the same. This data is actually just a combination of two 32-bit integers to form a struct, which means you can't pass a sint64 pointer into the function; you need to cast it first:

 sint64 freq; QueryPerformanceFrequency( (LARGE_INTEGER*)(&freq) ); 

Theoretically, the function returns 0 if it doesn't succeed, and non-zero if it does; but we know that it can only fail on machines older than Pentiums, so I find it safe to ignore the return value.

The frequency value never changes, so you should retrieve it once, and store it. I'll show you how I do that further into the chapter.

Getting the Time

Getting the current time from the performance counter is similar. (Time in this case means the number of ticks since the computer was started.)

 sint64 t; QueryPerformanceCounter( (LARGE_INTEGER*)(&t) ); 

Now t will hold the number of ticks since the system was started; of course, this value is meaningless to you unless you have a frequency value. So, how do you use this time value? Dividing the time by the frequency should give you the number of seconds that has passed since the system was started:

 t = t / freq; 

But that's only seconds, and I wanted milliseconds. So, instead, I'll convert the frequency value from ticks per second into ticks per millisecond:

 freq = freq / 1000; QueryPerformanceCounter( (LARGE_INTEGER*)(&t) ); t = t / freq; 

And now t holds the number of milliseconds passed since the system was started.

Linux

With Linux, it is somewhat easier to get the time. It has a nice function called gettimeofday, which uses a timeval structure. You've seen this before, when dealing with the select() socket function. It has two fields: one for seconds, and one for microseconds.

These fields are located within the Linux file sys/time.h, so you need to include that file to use the time features:

 #include <sys/time.h> struct timeval t; gettimeofday( &t, 0 ); 

The first parameter of the function is a pointer to the timeval structure, and the second parameter is a pointer to a structure describing the time zone. The second parameter is not required, so I'm not going to bother with it.

The t_sec value of the timeval structure will have the number of seconds since 1970, and the t_usec value will have the number of microseconds past the current second, which means that it will lie somewhere from 0 to 1,000,000.

Milliseconds

Since the two different time retrieval methods get different times (Windows gets the time since the system was started, and Linux gets the time since 1970), you're obviously only going to be able to use these as relative timerscomparing them to see how much time has passed since the timer was last called that is. But don't worrythey're still useful.

So, the base of my entire time library is a millisecond timer, in a function entitled GetTimeMS() , which gets an arbitrary time in milliseconds. As I said before, this value is relative, so the only thing you're guaranteed about a call to this function is that it will accurately return a number that can be subtracted from any previous number, and it can be subtracted to find out the number of milliseconds that have passed.

Here's the Windows portion of the code:

 sint64 GetTimeMS() {     #ifdef WIN32         sint64 t;         QueryPerformanceCounter( (LARGE_INTEGER*)(&t) );         return t / g_win32counter.m_frequency; 

I'll get to the g_win32counter.m_frequency part in a little bit; for now, it's safe to assume that it holds the performance counter frequency.

NOTE

The reason I split up the assignment of the tv_sec field and multiplied it by 1,000 is subtle. Since tv_sec is a signed 32-bit integer, multiplying anything above 2.14 million by 1,000 causes an overflow; 2.14 million seconds is less than a year. That means that any time after 1971 multiplied by 1,000 causes a 32-bit overflow. Therefore, the time is copied over into the 64- bit value and then multiplied by 1,000, thus ensuring that the num bers won't overflow.

And here is the Linux portion:

 #else         struct timeval t;         sint64 s;         gettimeofday( &t, 0 );         s = t.tv_sec;         s *= 1000;         s += (t.tv_usec / 1000);         return s;     #endif } 

The s variable will hold the end result of the calculations. Once the time is retrieved into t , the number of seconds is extracted and put into s and then multiplied by 1000 (remember, 1 second = 1,000 milliseconds).

Finally, the number of microseconds is divided by 1,000 (there are 1,000 microseconds in a millisecond), added to the sum, and the sum is returned. Ta-da! You now have a millisecond timersort of. There's one more thing you need to take care of.

What's the Frequency, Kenneth?

The easiest way to initialize the performance counter frequency is to make a function that initializes a global value, and remember to call that function whenever you start your program. I've done this before, and it gets tedious ; I'd rather not waste my time trying to remember to initialize a timer, especially if it's only needed on Windows, and nowhere else.

So, I'm going to explain how I exploit a neat feature of C++: Global classes are constructed automatically when a program is first started. So what does this mean? I'm going to create a Windows performance counter frequency class and have its constructor automatically get the frequency value whenever your program is first run.

The class is inside the BasicLibTime.h file:

 #ifdef WIN32     class Win32PerformanceCounter {     public:         Win32PerformanceCounter() {             QueryPerformanceFrequency( (LARGE_INTEGER*)(&m_frequency) );             m_frequency = m_frequency / 1000;         }         sint64 m_frequency;     };     Win32PerformanceCounter g_win32counter; #endif 

Notice that this class exists only within WIN32; Linux has no idea about this file, and neither should users of the library. For all intents and purposes, this class shouldn't exist for anything but the WIN32 branch of the GetTimeMS() function. The class has one variable: m_frequency , which is the frequency of the performance counter divided by 1,000, so it represents ticks per millisecond. On the next -to-last line, a single instance of this class, g_win32counter, is created; you shouldn't try accessing this outside the BasiclLibTime.cpp file, because it literally doesn't exist outside this file. Basically, all you need to know is that this class is automatically constructed when you run your program, so you don't have to worry about initializing anything. I pull basically the same trick with the SocketLib in the next chapter.

Other Times

I included three other relative time functions in the library, each to get seconds, minutes, and hours. I don't think there is any need for a relative day or year function, but feel free to build one yourself. These time functions are called GetTimeS , GetTimeM , and GetTimeH respectively. Each of these functions relies on the result of GetTimeMS and divides the time by the appropriate value. For example:

 sint64 GetTimeS() {     return GetTimeMS() / 1000; } 

And so on.

Timestamps

You'll often want to get a text string representing the current time and date of the system. Luckily, C++ has built-in features. First, we'll need to get the current time as a tm structure, which is an interesting process, to say the least:

 time_t a = time( 0 ); struct tm* b = localtime( &a ); 

First, the current time in seconds is retrieved using the standard C time() function; then a pointer to a tm structure is retrieved from the localtime() function. To work, the function requires a pointer to the current time. There's a similar function called gmtime () , which gets a tm structure using Greenwich Mean Time (GMT); the local time function gets the time according to your computer's time zone.

Now that you have a tm structure, you can use the strftime() function to get a string based on the time structure. This function is similar to the sprintf() C function, except it prints time values instead of variable values. The time values we'll be interested in are %H, %M, %S, %Y, %m, and %d, which represent hours, minutes, seconds, years , months, and days.

First, you'll need a buffer:

 char str[9]; 

And then you'll fill the buffer with the function:

 strftime( str, 9, "%H:%M:%S", b ); 

The first parameter to be filled is the char* buffer, the second parameter is the maximum length of the buffer, the third parameter is a string describing the output format, and the final parameter is a pointer to the tm structure. From the preceding example, you can see that I requested the time in HH:MM:SS format, which can have a maximum of eight characters , the ninth being the NULL character (used to terminate C-strings). For example, 8:00 A.M. should look like this: 08:00:00 .

Here's the TimeStamp() function:

 std::string TimeStamp() {     char str[9];     time_t a = time(0);     struct tm* b = gmtime( &a );     strftime( str, 9, "%H:%M:%S", b );     return str; } 

The TimeStamp() function returns a C++ string . In case you've never used C++ strings, I explain them later in this chapter.

The DateStamp() function is similar, except it returns times in YYYY.MM.DD format. (Yes! It's unorthodox, but as a mathematician and a programmer, the most significant digits always go first! It just makes more sense that way!)

 std::string DateStamp() {     char str[11];     time_t a = time(0);     struct tm* b = gmtime( &a );     strftime( str, 11, "%Y.%m.%d", b );     return str; } 

So the date May 30, 2010 would be represented as 2010.05.30 .

Timers

Up until now, you have only had functions to get relative times, but you don't really have any reliable way of creating a timer that will track the amount of time that has passed since you created or reset the timer. Enter the Timer class. I want to make a class that will start counting time from 0 whenever it is created, or when it is manually reset. This class should also be capable of being given a default time. Say you save a time to disk, close the program, and then run it again; you might want to resume the timer from when it was saved to disk.

To do this, a timer object must have two variables : the system time at which it was initialized or reset, and the official starting time of the timer. Usually, you'll start the timer off at 0, so for now, let's just assume the starting time is 0.

Here's the class with its functions and data:

 class Timer { public:     Timer( sint64 p_timepassed = 0 );     void Reset( sint64 p_timepassed = 0 );     sint64 GetMS();     sint64 GetS();     sint64 GetM();     sint64 GetH();     sint64 GetD();     sint64 GetY(); protected:     sint64 m_inittime;     sint64 m_starttime; }; 

As you can see, there's a constructor (which basically calls Reset() ), a Reset() function, and six functions that get the number of milliseconds, seconds, minutes, hours, days, or years that have passed since the timer was last reset or initialized.

Since the constructor of the timer just calls reset, let's look at the Reset() function:

 void Timer::Reset( sint64 p_timepassed ) {     m_starttime = p_timepassed;     m_inittime = GetTimeMS(); } 

In the class definition, the = 0 in the parameter means that the parameter can be omitted, and if omitted, it is assumed to be 0. For now, let's just assume that the parameter is 0; so the m_starttime value is reset to 0, and the m_inittime value is set to the current time of the system.

Now look at the GetMS() function:

 sint64 Timer::GetMS() {     return (GetTimeMS() - m_inittime) + m_starttime; } 

GetMS() basically subtracts the init time from the current time, adds the starting time (which is zero for this example), and results in exactly the number of milliseconds that has occurred since the timer was last reset.

Now, if you initialized the timer with a value of 10,000 milliseconds:

 timer t( 10000 ); 

the timer will start off with a default value of 10 seconds. From then on, the timer will start counting from 10,000, instead of 0. This is very helpful for saving the value of the timer and then restarting it later, keeping the same amount of time.

For example (assume the comments represent a large amount of processing that you don't see here):

 sint64 x; timer t; // code block 1 x = t.GetMS(); // code block 2 t.reset( x ); // code block 3 x = t.GetMS(); 

After code block 1 has finished executing, x should hold the number of milliseconds required for the processing. Code block 2 is then executed, and after that, the timer is reset to the value recorded before block 2 executed. That means that the timer essentially wasn't counting during the execution of block 2, but it is again counting after block 2. Finally, at the end of block 3, the value of x is again updated, and x should hold the number of milliseconds it took to execute blocks 1 and 3, but not 2.

All of the other time functions within the class are based on the GetMS() function; for example, here is the GetH() function:

 sint64 Timer::GetH() {     return GetMS() / 3600000; } 

There are 3,600,000 milliseconds in an hour (60 min/hr * 60 sec/min * 1000 ms/sec). All the other functions are similar, and there is no need to show them here.

This about concludes my section on time, but I will brush on the topic again in later chapters, to show you efficient ways of implementing a system that automatically executes functions after a specified amount of time. Specifically, I deal with a simple timer system in Chapter 10, "Enemies, Combat, and the Game Loop," and a more complicated queue-based timer system in Chapter 15, "Game Logic."

[ LiB ]


MUD Game Programming
MUD Game Programming (Premier Press Game Development)
ISBN: 1592000908
EAN: 2147483647
Year: 2003
Pages: 147
Authors: Ron Penton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net