HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

TimerGetElapsed/Remaining precision

04-29-2008, 12:54 PM#1
Vexorian
If anyone remembers this other thread in which I was testing that circular queue stuff, I said TimerGetElapsed is not precise enough, well that's not the whole story, I made some other tests today.

Collapse JASS:
library onetest initializer init
 globals
    private timer t
 endglobals

    private function disp takes nothing returns nothing
        call BJDebugMsg("rem"+R2S(TimerGetRemaining(t)))
        call BJDebugMsg("ela"+R2S(TimerGetElapsed(t)))
    endfunction
    private function init takes nothing returns nothing
        set t=CreateTimer()
        call TimerStart(t,100,true, null)
        call TimerStart(CreateTimer(),0.04,true, function disp)
    endfunction
endlibrary

The difference between each ela/rem displayed is 0.040.

But if you did:

call TimerStart(t,1000000000,true, null)
The difference goes crazy as I described in the other thread, three numbers in a row are equal then they raise abruptly .

So, I figured that timeout is quite inflated, I tried seconds in a day:

call TimerStart(t,86400,true, null) The difference now is variable between 0.039 and 0.041

So, it looks like blizz does some float stuff here. I think the difference in the last test is not that much, can anyone think of a reasonable upper bound for a game's duration?

Edit: Just tested and the precision for 12 hours is also 0.001, good enough, even so I think I need to use R2SW here...

I'd say a map that breaks after 12 hours is good enough, if you actually play continuously for 12 hours you probably need something to stop you...

Edit 2: Not bad at all, the error for 12 hours is around 0.00001

Edit 3: I had a mistake in my tests for 12 hours, retesting.

edit 4: The error for 12 hours can get as big as 0.003
Edit 5:

Table:
TimeoutError
24 hours 0.007
12 hours 0.003
6 hours 0.0011
3 hours 0.0009
90 minutes 0.00044
30 minutes 0.000083
1 minute0.0000029

Seems linear, so I guess what happens here is that they never store/maintain elapsed, only remaining, and then do a subtraction to get remaining, but then that would require to also store the timeout...
04-29-2008, 02:28 PM#2
rulerofiron99
I think that Blizzard made this bug to cause games to crash after playing for 12 hours, to prevent people from over-playing.

Have you tried running these tests with lots of other laggy triggers around? In the past I noticed that certain lag can cause certain triggers to malfunction or not run the amount of time specified, etc.
04-29-2008, 02:32 PM#3
Vexorian
This has nothing to do with crashing. It will not break the game alone, and it is not really a bug but some implementation specificity.

Anyways, I'll stick to 90 minutes, after the 90 minutes timer expires, I'll iterate through all pending time outs and update their expiration times (subtrack 90 minutes) .

Or should I just use 6 hours? I think an error of 0.001 ought not to cause big issues, need to redo the tests for "AwesomeTimeEvent"...
04-29-2008, 03:56 PM#4
Captain Griffen
Nothing to do with timers.

Try this, and you'll see that the two numbers printed at the same, showing that the issue lies in the inaccuracy of reals rather than anything to do with timers:

Collapse JASS:
library onetest initializer init
 globals
    private timer t
    real total = 86400.
    real x = 0
 endglobals

    private function disp takes nothing returns nothing
        set x = x + 1
        call BJDebugMsg("the " + R2S(total - x*0.04))
        call BJDebugMsg("rem "+R2S(TimerGetRemaining(t)))
        //call BJDebugMsg("ela"+R2S(TimerGetElapsed(t)))
    endfunction
    private function init takes nothing returns nothing
        set t=CreateTimer()
        call TimerStart(t,total,true, null)
        call TimerStart(CreateTimer(),0.04,true, function disp)
    endfunction
endlibrary

Big numbers get more and more inaccuracies as absolute values due to the floating point increasing in scale. Timers themselves seem to be perfectly accurate.
04-29-2008, 04:31 PM#5
Vexorian
Duh.

It's wrong though. This got everything to do with how timers are implemented.

Both TimerGetElapsed and TimerGetRemaining have the issue, and TimerGetElapsed got it since the beginning of the game - its return values are fairly low -, for some reason they made Elapsed depend on the timeout duration. Probably they only store remaining and do Elapsed based on it and then the precision is lost.

Quote:
Timers themselves seem to be perfectly accurate.
"Perfectly accurate" is such an overstatement. I never said Timers weren't precise, I am just saying, TimerGetElapsed is unnecessarily imprecise.
04-29-2008, 04:34 PM#6
Captain Griffen
I compared time remaining with the starting time minus the number of itinerations times by 0.04, and get exactly the same answer each time - implying that the discrepency is nothing to do with timers (since the the(oretical) value isn't affected by timers at all, except for counting natural numbers).
04-29-2008, 04:40 PM#7
Vexorian
Collapse JASS:
library onetest initializer init
 globals
    private timer t
    real total = 1000000000. 
    real x = 0
 endglobals

    private function disp takes nothing returns nothing
        set x = x + 1
        call BJDebugMsg("the " + R2S(x*0.04))
        call BJDebugMsg("ela"+R2S(TimerGetElapsed(t)))
    endfunction
    private function init takes nothing returns nothing
        set t=CreateTimer()
        call TimerStart(t,total,true, null)
        call TimerStart(CreateTimer(),0.04,true, function disp)
    endfunction
endlibrary

Compare that.
04-29-2008, 05:02 PM#8
Captain Griffen
Hmm...yea, that's screwed up. In fact in more ways than one; I don't think timers can go beyond 1231380. The problem persists below there too.

Nice find.
04-29-2008, 05:05 PM#9
Toadcop
lol.
Captain Griffen +1
cause it's the problem of real values and not timers.

it's not the first time what large reals are inaccurate...

try for example
call echo(R2S(i2r(r2i(565664534.3445))))

// i2r r2i are return bug conversions...
and try

call echo(R2S(565664534.3445))

or try to do some math operation etc.
04-29-2008, 05:11 PM#10
Vexorian
Quote:
Originally Posted by Captain Griffen
Hmm...yea, that's screwed up. In fact in more ways than one; I don't think timers can go beyond 1231380. The problem persists below there too.

Nice find.
Yeah 1000000000 is quite a ridiculous value, but I left it there so the thing is more noticeable, With 12 hours the thing is still there only that requires R2SW to make sense, may I ask why you said "1231380" ?

I just wish blizzard would have implemented a game elapsed time native.

toad: the joke's on you.
04-29-2008, 05:18 PM#11
Captain Griffen
Quote:
Originally Posted by Vexorian
Yeah 1000000000 is quite a ridiculous value, but I left it there so the thing is more noticeable, With 12 hours the thing is still there only that requires R2SW to make sense, may I ask why you said "1231380" ?

Printing the time elapsed plus the time remaining gave a fairly constant 123180 (0.25 or something off, presumably due to timer weirdness/real inaccuracy).

Quote:
I just wish blizzard would have implemented a game elapsed time native.

Indeed. Guess you can get past it with repeating timers, but meh.
04-29-2008, 10:16 PM#12
grim001
If you want it to work forever with high accuracy, you could set the timeout much lower (1 min or less) and calculate the time elapsed just as griffin did.
04-29-2008, 10:36 PM#13
Vexorian
that also seems to hit performance somehow. I tried it in the other thread.
07-29-2008, 01:01 PM#14
ToukoAozaki
As inferred from the results, it seems that the result is precise enough when given a reasonable timeout. Have you tested with 3600 (number of seconds in an hour)? I think that would give a pretty precise result without much performance loss.
07-29-2008, 03:27 PM#15
Vexorian
I stopped the whole idea of queue based timer attachment because... err, can't explain without code.

Collapse JASS:

library sandbox initializer init // requires UnitStruct

globals
    private integer A=0
    private integer B=0
    private constant integer CYCLE = 10
    private constant real    TIME1 =0.02
    private constant real    TIME21=0.01999
    private constant real    TIME22=0.00001
    private constant real    TIME3 = 0.2
endglobals

private function Bu takes nothing returns nothing
   set B=B+1
   if(ModuloInteger(B,CYCLE)==0) then
       //call BJDebugMsg("B: "+I2S(B))
   endif
endfunction

globals
    private code AiFunc
endglobals

private function Au takes nothing returns nothing
   set A=A+1
   if(ModuloInteger(A,CYCLE)==0) then
       //call BJDebugMsg("A: "+I2S(A))
   endif
   call TimerStart(GetExpiredTimer(), TIME21, false, AiFunc)
endfunction

private function Ai takes nothing returns nothing
    call TimerStart(GetExpiredTimer(), TIME22, false, function Au)
endfunction

private function C takes nothing returns nothing
   call BJDebugMsg(I2S(A)+" ; "+I2S(B))
endfunction

private function init takes nothing returns nothing
    set AiFunc= function Ai
    call TimerStart(CreateTimer(), TIME1, true, function Bu)
    call TimerStart(CreateTimer(), TIME21, false, function Ai)
    call TimerStart(CreateTimer(), TIME3, true, function C)
endfunction
endlibrary