coding gotcha - getRealTime() can become negative w/ system uptime
by Orion Elenzil · in Torque Game Engine · 02/02/2009 (11:38 am) · 7 replies
i wrote a feature a while ago in which the client issues a certain HTTP request based on user action, but limits the frequency with which the request can be fired. eg, "whenever the user wants, except not more than once every twenty seconds". after being deployed for some time, this feature suddenly stopped working. after some investigation, i realized that the reason was because i was using getRealTime() in script, which "Returns the current real time in milliseconds. Real time is platform defined; typically time since the computer booted".
this has a few issues:
1) getRealTime() is casting getRealMilliseconds() from U32 to S32,
but sometimes it can get so large that the sign bit is used, and the number becomes negative.
2) on windows, Platform::getRealMilliseconds uses GetTickCount, which looks like it will wrap after about 50 days.
3) even without running in to negative or wrapping, when numbers get large, torquescript starts to represent them in scientific notation, which for integral values generally means you lose the ability to do meaningful math on them unless you use some large-number math utils such as Peter Simard's mAddS32 routines (which i can't find on the GG site now but they're out there somewhere)
what should you do instead ?
use getSimTime(), which returns the number of milliseconds since application startup. note if you plan of having very long server uptime (eg > 25 days), even this will be a problem, and you might want to write a method along the lines of "getSimSeconds()", which would return the seconds since application startup, and should be good for at least a year of uptime.
this has a few issues:
1) getRealTime() is casting getRealMilliseconds() from U32 to S32,
but sometimes it can get so large that the sign bit is used, and the number becomes negative.
2) on windows, Platform::getRealMilliseconds uses GetTickCount, which looks like it will wrap after about 50 days.
3) even without running in to negative or wrapping, when numbers get large, torquescript starts to represent them in scientific notation, which for integral values generally means you lose the ability to do meaningful math on them unless you use some large-number math utils such as Peter Simard's mAddS32 routines (which i can't find on the GG site now but they're out there somewhere)
what should you do instead ?
use getSimTime(), which returns the number of milliseconds since application startup. note if you plan of having very long server uptime (eg > 25 days), even this will be a problem, and you might want to write a method along the lines of "getSimSeconds()", which would return the seconds since application startup, and should be good for at least a year of uptime.
About the author
#2
10/09/2010 (7:46 pm)
Thank you, just ran into this problem and now I know why.
#3
10/09/2010 (10:35 pm)
The best way to deal with wrapping issues like this is to avoid direct comparisons between two timestamps (i.e. if(getSimTime() > $myTime)). Instead, compare the time against the difference of the two timestamps, i.e. if((getSimTime() - $myTime) > <Some Time Span>).
#4
in that situation, the difference before the wrap will be correct, but the difference after the wrap will be wrong.
10/09/2010 (10:41 pm)
that won't help you in this situation, because getSimTime() can wrap-around to negative numbers or 0.in that situation, the difference before the wrap will be correct, but the difference after the wrap will be wrong.
#5
$myTime = 2147483647 (max value for an S32)
getSimTime() then wraps around to -2147483648.
The original direct comparison would then be: if(-2147483648 > 2147483647) which would obviously return false (for the next ~48 days!).
The difference comparison would then be: if((S32)(-2147483648 - 2147483647) > <Some Time>). Because the comparison result is casted to a signed 32bit int, the result would actually be 1, correctly indicating a time 'difference' of 1ms.
The one caveat here which might be a deal breaker is the way in which TScript handles large integers. It automatically wraps them to floats, complete with scientific notation which is going to throw a wrench in things.
Edit: To handle the conversion you could probably use your "torquescript math routines for large integers" resource, Orion.
Fun problem for sure.
10/09/2010 (10:58 pm)
It shouldn't matter. Let's consider the following:$myTime = 2147483647 (max value for an S32)
getSimTime() then wraps around to -2147483648.
The original direct comparison would then be: if(-2147483648 > 2147483647) which would obviously return false (for the next ~48 days!).
The difference comparison would then be: if((S32)(-2147483648 - 2147483647) > <Some Time>). Because the comparison result is casted to a signed 32bit int, the result would actually be 1, correctly indicating a time 'difference' of 1ms.
The one caveat here which might be a deal breaker is the way in which TScript handles large integers. It automatically wraps them to floats, complete with scientific notation which is going to throw a wrench in things.
Edit: To handle the conversion you could probably use your "torquescript math routines for large integers" resource, Orion.
Fun problem for sure.
#6
when getSimTime() wraps to from 0b0111 to 0b1000 the math works out fine. neat!
(simplifying to 4 bits)
but you still have a problem when it wraps from 0b1111 to 0b0000.
10/10/2010 (12:29 am)
whoa cool - you're right,when getSimTime() wraps to from 0b0111 to 0b1000 the math works out fine. neat!
(simplifying to 4 bits)
but you still have a problem when it wraps from 0b1111 to 0b0000.
#7
10/10/2010 (7:30 am)
Oops, little correction: you'd have to cast the comparison result to a U32 instead of an S32. getSimTime() correctly returns an unsigned 32bit integer for getTickCount(). That means a value range between (0, 4294967295). Not sure how that affects the above issue, though.
Associate Orion Elenzil
Real Life Plus
Platform::getRealMilliseconds() boils down to gettimeofday() which is milliseconds since the epoch (jan 1, 1970),
which in 32 bits will also wrap every 49.7 days and go negative in about 25 days.