View Full Version : Discussion: Time base for TBEM
ErosOlmi
09-10-2008, 11:04
Yes it is working fine. If you have set default initial start time at GetTickCount it will work and can be considered a solution for common situations.
Possibly send me again sources, I would like to study it with more time during the week-end.
At the moment I have no a precise idea of what could be better.
Maybe a possible situation is not relay on GetTickCount alone but determine a sort of initial time when module is loaded by each script and than use that time as TIME ZERO. All the rest will be based on that ZERO time. But to be honest I'm just guessing a possible solution without any test.
Better to talk about that in TBEM dedicated forum.
Anyhow, for the purpose of this and other scripts, new version solve the problem.
Ciao
Eros
Win 98 (Gettickcount) is only accurate to 0.05 (The number may be 1.234, but the next number will be 1.274.) (That is 20FPS, if you hit every cycle, and don't pass one. Windows skips calls for gettickcount if you call it too often. Just sends back old buffered data, before it can give you back the real time.)
Win XP is 0.016 per tick (That is 60FPS)
On both, the timer rolls-over after a few days... producing a negative value as the output.
(-Neg - -Neg = -Neg) as opposed to (+Pos - +Pos = +Pos). (New - Old = Diff)
When negative... Old = -4, New = -3, -2, -1, 0, 1, 2, 3...
(-3 - -4 = -1) as opposed to (+3 - +2 = +1)
Use the HighResolutionTimer, it is more accurate to the time, and takes less time for a call.
TimeGetTime is actually better for all computers. It rolls-over but is faster.
The best method would be to do this...
TimeGetTime... and HighResolutionTimer...
Wait for about one minute... Get them again...
Now you can compare real "Time" to the frequency-timer... and adjust instantly. (Keep using HRT for all next calls.)
But less critical things would use TimeGetTime, and adjust for the predictable negative value, as GetTickCount.
Michael Hartlef
09-10-2008, 11:52
Here is a test that I made for how long 500.000 calls of a time function take. I think it isn't something that is important in our case.
' Empty ThinBASIC CONSOLE file template
uses "console"
dim t,t2,t3, st,et,dt as dword
dim i as long
dim length as long value 500000
Declare Function timeGetTime Lib "winmm.dll" Alias "timeGetTime" () As DWord
Declare Function GetTickCount2 Lib "kernel32.dll" Alias "GetTickCount" () As DWord
'Check how long Gettickcount via thinBasic call takes
st = Gettickcount
for i = 1 to length
dt = gettickcount
next
et = gettickcount
t = et - st
'Now check how long Gettickcount via API call takes
st = Gettickcount
for i = 1 to length
dt = gettickcount2
next
et = gettickcount
t2 = et - st
'Now check how long timeGettime takes
st = Gettickcount
for i = 1 to length
dt = timeGetTime
next
et = Gettickcount
t3 = et - st
console_writeline("GetTickCount(TB): " + t + " GetTickCount(API): " + t2 + " timeGetTime: " + t3)
console_waitkey
Michael Hartlef
09-10-2008, 11:53
The API call for GetTickCount is slightly FASTER then timeGetTime.
ErosOlmi
09-10-2008, 12:20
Michael,
I think your latest version already solved all standard situations like games or other thinBasic scripts I can think about. So thanks for it.
The only possible open problem is when TBEM is used on "long run" scripts on servers or other machines.
You can easily solve the problem stating in help file that this module is not developed for server side scripts or for scripts running for more than xx days.
When you will have more time or worth to dedicate time to it, maybe you will find a better solution but for the moment it is a perfect and very valuable module.
In the meantime if my brain will think about a possible implementation I will ask here.
Thanks a lot
Eros
For long scripts, where time is to extend beyond a set period... (Timer expires)... the time calibration to real system time, should be used as the base... Updated at intervals beyond a minute. (Precision timers are only good for short times. Once time exceded a minute, value reset back to 0, and a time-stamp value is used for a compare.)
val = TimeStamp
val = PrecisionStart
Loop
TimerCompare (If PrecisionStart>PrecisionLimit THEN UseTimeStamp ELSE UsePrecisionTime)
End Loop
When they have exceded a minute, or hour... there is no need to know that 8574345.12345678 seconds passed. Knowing that 8574345 seconds passed is all that is needed.
Only each element that requires precision, should have that value used. (Those should not take longer than an hour.)
Here is the OVERHEAD values... of each time event.
Summary... Being FAST and not-precise, is pure OVERHEAD.
Are we there yet? Are we there yet? Are we there yet? Are we there yet? Where are we going? (I told you 15 times already, we are still in the driveway! Now buckle-up!)
' Empty ThinBASIC CONSOLE file template
Uses "UI"
HiResTimer_Init
GLOBAL xHRT, xHRT_Old, xHRT_New AS QUAD
GLOBAL xt1,xt2,xt3,xt4, t4 AS QUAD
GLOBAL c1,c2,c3,c4, u1,u2,u3,u4 AS DOUBLE
GLOBAL t1,t2,t3, st,et,dt AS DWORD
GLOBAL i as long
GLOBAL length as long value 1000000
Declare Function timeGetTime Lib "winmm.dll" Alias "timeGetTime" () As DWord
Declare Function GetTickCount2 Lib "kernel32.dll" Alias "GetTickCount" () As DWord
FUNCTION TBMAIN()
LOCAL hDlg AS DWORD
DIALOG New 0, "FINISHED",-1,-1, 160, 120, _
%WS_POPUP Or %WS_VISIBLE Or _
%WS_CLIPCHILDREN Or %WS_CAPTION OR _
%WS_SYSMENU Or %WS_MINIMIZEBOX, 0 To hDlg
DIALOG SHOW MODAL hDlg, CALL dlgProc
END FUNCTION
' -- Callback for dialog
CALLBACK FUNCTION dlgProc()
' -- Test for messages
SELECT CASE CBMSG
CASE %WM_INITDIALOG
' -- ##### Getting a unique change value (How precise it is)
st = Gettickcount
DO UNTIL gettickcount <> st
xHRT = gettickcount
LOOP
et = gettickcount
t1 = et - st
st = Gettickcount2
DO UNTIL gettickcount2 <> st
xHRT = gettickcount2
LOOP
et = gettickcount2
t2 = et - st
st = timeGetTime
DO UNTIL timeGetTime <> st
xHRT = timeGetTime
LOOP
et = timeGetTime
t3 = et - st
xHRT_Old = HiResTimer_Get
DO UNTIL HiResTimer_Get <> xHRT
xHRT = HiResTimer_Get
LOOP
xHRT_New = HiResTimer_Get
t4 = xHRT_New - xHRT_Old
MSGBOX 0, "How ACURATE each call is." & CRLF _
& CRLF & "GetTickCount(TB): " & FORMAT$(t1/1000, "#0.000000") _
& CRLF & "GetTickCount(API): " & FORMAT$(t2/1000, "#0.000000") _
& CRLF & "TimeGetTime(API): " & FORMAT$(t3/1000, "#0.000000") _
& CRLF & "HRT CALL: " & FORMAT$(t4/1000000, "#0.000000")
' -- ##### Based on testing... A dead call is one where no change has taken place...
'Determine OVERHEAD by how many useless calls are sent, returning values without change...
u1 = (t1/1000)
u2 = (t2/1000)
u3 = (t3/1000)
u4 = (t4/1000000)
' -- ##### Testing time to execute a CALL
'Check how long Gettickcount via thinBasic call takes
xHRT = 0 ' Ensures value is with data, and all are called with same VAR delay
xHRT_Old = HiResTimer_Get
st = Gettickcount
for i = 1 to length
xHRT = gettickcount
next
xHRT_New = HiResTimer_Get
et = gettickcount
t1 = et - st
xt1 = xHRT_New - xHRT_Old
'Now check how long Gettickcount via API call takes
xHRT_Old = HiResTimer_Get
st = Gettickcount2
for i = 1 to length
xHRT = gettickcount2
next
xHRT_New = HiResTimer_Get
et = gettickcount2
t2 = et - st
xt2 = xHRT_New - xHRT_Old
'Now check how long timeGettime takes
xHRT_Old = HiResTimer_Get
st = timeGetTime
for i = 1 to length
xHRT = timeGetTime
next
xHRT_New = HiResTimer_Get
et = timeGetTime
t3 = et - st
xt3 = xHRT_New - xHRT_Old
' HRT
xHRT_Old = HiResTimer_Get
for i = 1 to length
xHRT = HiResTimer_Get
next
xHRT_New = HiResTimer_Get
xt4 = xHRT_New - xHRT_Old
MSGBOX 0, "How FAST 1,000,000 calls are made." & CRLF & "(Second value is HRT true value.)" & CRLF _
& CRLF & "GetTickCount(TB): " & FORMAT$(t1/1000, "#0.000000") & " : " & FORMAT$(xt1/1000000, "#0.000000") _
& CRLF & "GetTickCount(API): " & FORMAT$(t2/1000, "#0.000000") & " : " & FORMAT$(xt2/1000000, "#0.000000") _
& CRLF & "TimeGetTime(API): " & FORMAT$(t3/1000, "#0.000000") & " : " & FORMAT$(xt3/1000000, "#0.000000") _
& CRLF & "HRT CALL: " & FORMAT$(xt4/1000000, "#0.000000")
c1 = (1/(xt1/1000000)*1000000)
c2 = (1/(xt2/1000000)*1000000)
c3 = (1/(xt3/1000000)*1000000)
c4 = (1/(xt4/1000000)*1000000)
MSGBOX 0, "How MANY calls made in a second." & CRLF _
& CRLF & "GetTickCount(TB): " & FORMAT$(c1, "#0") _
& CRLF & "GetTickCount(API): " & FORMAT$(c2, "#0") _
& CRLF & "TimeGetTime(API): " & FORMAT$(c3, "#0") _
& CRLF & "HRT CALL: " & FORMAT$(c4, "#0")
' Calculate OVERHEAD of calling X calls per second...
DIM Count1, Count2 AS LONG
xHRT_Old = HiResTimer_Get
xHRT_New = (xHRT_Old + 1000000)
st = Gettickcount
DO UNTIL HiResTimer_Get > xHRT_New
Count1 += 1
dt = Gettickcount
IF st <> dt THEN
Count2 += 1
st = dt
END IF
LOOP
xHRT_New = HiResTimer_Get
xt4 = xHRT_New - xHRT_Old
MSGBOX 0, "Actual wasted Calls (GetTickCount-TB)..." & CRLF & "Seconds: " & FORMAT$(xt4/1000000, "#0.000000") & ", Total: " & Count1 & ", Changed: " & Count2 & " " _
& CRLF & "Percent of good calls: %" & FORMAT$((100/Count1)*Count2, "#0.000000")
Count1 = 0
Count2 = 0
xHRT_Old = HiResTimer_Get
xHRT_New = (xHRT_Old + 1000000)
xt1 = HiResTimer_Get
DO UNTIL HiResTimer_Get > xHRT_New
Count1 += 1
xt2 = HiResTimer_Get
IF xt1 <> xt2 THEN
Count2 += 1
xt1 = xt2
END IF
LOOP
xHRT_New = HiResTimer_Get
xt4 = xHRT_New - xHRT_Old
MSGBOX 0, "Actual wasted Calls (HighResTimer-TB)..." & CRLF & "Seconds: " & FORMAT$(xt4/1000000, "#0.000000") & ", Total: " & Count1 & ", Changed: " & Count2 & " " _
& CRLF & "Percent of good calls: %" & FORMAT$((100/Count1)*Count2, "#0.000000")
MSGBOX 0, "Result... Being a FAST reply, with NO CHANGE, is HIGH OVERHEAD." & CRLF & "You are calculating nothing fast, slowing down the whole PC."
CASE %WM_CLOSE
' -- Put code to be executed before dialog end here
END SELECT
END FUNCTION
The relation to games... as a timer...
The highest resolution would be 0.016 seconds, or roughly 60FPS (60 call events per second.) Actual is less...
For instance, if you start at 0.000000001 second...
The next time event "Could" be 0.000000001 second away (0.000000002) or it Could be 0.016000001 (0.016000002)...
You have no idea what the actual "Start" time is, since you can only see changes of 0.016... When you execute a single call, you also have to add that delay. (That is what you see between the API and the TB variation. API "is" faster, but it "is" even faster for the core, than trying to pass an API call from the code, to the core, to the DLL, and get a reply.)
EG, if used as a frame-release trigger... (Holding back until any change is detected.) The fastest speed you would ever see is 60FPS. (But that is if you notice every change, as it happens. What happens in reality... the actual change is delayed, because you are sending thousands of calls, and the one with the change is delayed by the overhead.)
If used as an animation trigger... (Activating an event at an interval.) If you use the START, you will only be behind by about 0.016... If used as a continuance... You will miss events, and sense double events. (Time passed = none, or 1,1,2,1,1,0,2,1,1,2... as times overlap, and don't match with the refresh rate. Creates a jittery motion or sound.)
If the program on the other side, which called the routine, is waiting for an event... that would create delays in the program they are running. (One that will drive them mad, trying to figure out why they cant get over 60 FPS, and all lower FPS jumps around for no apparent reason.)
You COULD...
Create two internal runs... (One or the other would be used.)
If I say an event should happen every 0.001 s, you use the HighTimer.
If I say an event should happen every 0.1 s, you use the GetTick.
If I say an event should happen every 1.0 s or more, you use WindowsTime.
For every event type, there could be two styles of return...
ReleaseStyle (Uses one start value, tracks time from start as trigger.)
SequenceStyle (Resets like a game-timer at every call.)
For those who like them, you could also create a buffered version.
When I ask for an event-check, you return how many events occured since last time I asked. (If I am busy, and don't want to miss events that can't be handled at a designated "Fire", where they would be out of sequence, and also interfere with code that needs priority processing.) This would be like a "Sleep" command to you, asking you for a pause and to buffer events while paused from you.
Release style would initialize with the start value, and trigger every X from that start value. (Best for FPS games. Values will always be x/60 from start time. Though not all 60 may be there, the "Timing" will always match. 4:00:00.0001, 4:00:00.0002, {missed} 4:00:00.0005, 4:00:00.0006. This is what video depends on... time. Four events happen, but I see that I am missing 2... and that 0.0002 is added {in time}, to the event that follows.)
Sequence style would reset, and tell you passed time, good if you buffer moves, and don't depend on time-events to be linear and smooth. (Values from call to call would not always be 1/60th of a second, though you ask them to be. 4:00:00.0001, 4:00:00.0167, 4:00:00.0532, 4:00:00.1180... resulting in passed time lost, but four events. You can't even begin to estimate if you lost time less than 1/60 of a second.)
This is also the same problem I am having with the animations using game-time as a time-passed control. The slower the frame-rate, the more inaccurate it becomes. In the end, the "Time * x" should equal "Timer + Timer + Timer * x", but the timer looses precision, misses calls, and X is never added into that time-segment, thus, the animation falls short and out of sequence. Seeming to get slower and slower.
Use the event to drive a clock... show that "Driven" time, side by side with real time, every 30 seconds just save both values. Record the diference, and you can watch it drift on any trigger that is not timed from "Start", and is not "HighResolution".
You can also run the timer-code through an "Extended loop"... to see some of the issues...
' Empty GUI script created on 10-09-2008 22:53:18 by (ThinAIR)
USES "TBGL"
Declare Function TimeGetTime Lib "winmm.dll" Alias "timeGetTime" () As DWORD
Declare Function GetTickCount2 Lib "kernel32.dll" Alias "GetTickCount" () As DWORD
DIM cDiff(200), c, cLength AS LONG
DIM a, b, c1, c2, c3, cOld_Diff, cNew_Diff AS DWORD
cLength = 1000000
' -- Will not work on HighResTimer... all values change, so it is pointless checking and recording the diff.
'a = TBGL_GetFrameRate
a = GetTickCount
'a = GetTickCount2
'a = TimeGetTime
FOR c = 1 TO cLength
'b = TBGL_GetFrameRate
b = GetTickCount
'b = GetTickCount2
'b = TimeGetTime
IF a <> b THEN
c2 += 1
cNew_Diff = b - a
cDiff(c2) = cNew_Diff
IF cOld_Diff <> cNew_Diff THEN
c3 += 1
cOld_Diff = cNew_Diff
END IF
ELSE
c1 += 1
END IF
a = b
NEXT
MSGBOX 0, "Other_Timer test results" & CRLF & _
"Total calls: " & cLength & CRLF & _
"No changes: " & c1 & CRLF & _
"Total changes: " & c2 & CRLF & _
"Diff err: " & c3
MSGBOX 0, "Each value change: " & JOIN$(cDiff, ", ")
' -- This shows some interesting things...
DIM zDiff(300), zLength AS LONG
DIM x, y, z, z1, z2, z3, zOld_Diff, zNew_Diff AS LONG
zLength = 300
x = TBGL_GetFrameRate
FOR z = 1 TO zLength
y = TBGL_GetFrameRate
zDiff(z) = Y
IF x <> y THEN
z2 += 1
IF zOld_Diff <> Y THEN
z3 += 1
zOld_Diff = Y
END IF
ELSE
z1 += 1
END IF
x = y
NEXT
MSGBOX 0, "TBGL_Timer test results" & CRLF & _
"Total calls: " & zLength & CRLF & _
"No changes: " & z1 & CRLF & _
"Total changes: " & z2 & CRLF & _
"Diff err: " & z3
MSGBOX 0, "Each value change: " & JOIN$(zDiff, ", ")
You will see getTickCount reports 0.016, 0.015, 0.016, 0.016, 0.015, even on a dead loop. If you save the values.
I threw-in the game-timer event for TBGL, just to see what it output... Not sure how to react to those numbers, but the divisor is real small, so it is all good... (Had to remove the subtracton, since that has been done by the call. Also had to remove the recording of "Not Same", since each returned value that is not 1 is unique. It returns 1 if time extends beyond 1 second or 1 frame per second.)
Michael Hartlef
10-10-2008, 07:09
Eros,
which is the API definition behind HiResTimer_Get?
Thanks Jason for the samples.
ErosOlmi
10-10-2008, 07:57
QueryPerformanceFrequency (http://msdn.microsoft.com/en-us/library/ms644905.aspx)
QueryPerformanceCounter (http://msdn.microsoft.com/en-us/library/ms644904(VS.85).aspx)
Some usage
http://www.powerbasic.com/support/pbforums/showthread.php?t=37162&highlight=QueryPerformanceFrequency
Michael Hartlef
10-10-2008, 08:26
Thanks!
Just be sure to compare the output of the TICK and FREQUENCY to the actual passed time value of the clock. That will only be an issue with unpatched dual-core processors, and dual/quad processors, which use two separate clocks. The end result will be drifting or pure half/quarter or double speed. (Depending on the arrangement.)
But the issue should be nearly immediate, after the first call of the API, and a quick 2 to 5 second check.
Where can I find the help-file for this module?
Even with GetTickCount driving it... it is a valuable tool.
Here is another MSDN page to show some of the issues above.
http://msdn.microsoft.com/en-us/magazine/cc163996.aspx
Focus is on the drift/accuracy related to system state changes, and also dual-core.
Michael Hartlef
10-10-2008, 20:06
Just look into the help folder. It's just a loose lisitng of the commands and their syntax. No samples.
Sorry, I keep forgetting there is helpfiles on my computer... I was using the online help files... The link was dead.
Found it... TY