Skip to main content
GameDev.net gamedev.net
🔒 Locked

Time based movement

Started by PsychoPaul Apr 27, 2003 at 9:05 PM 10 replies 1.7k views
Original Post
PsychoPaul
PsychoPaul
I''m not sure if this was discussed before or not but i''ll ask just incase. I''ve looked around (not much but enough i guess) and found two different ways of using time based movement. Some people use GetTickCount() and some use QueryPerformanceTimer() (i hope i have the function names correct). Which one is better or more suggested over the other? Also my second question is, when you use time base movement whats the best or suggested way to implement it in a program. Because i''ve seen people use the result of the timer and multiply it or divide it by another number. How does that work? If there is another thread posted somewhere else please direct me to that post. Thanks in advance. Paul
Paulhttp://members.lycos.co.uk/p79
PixolMaster
PixolMaster
Well, this is how I do it:


  
#include <time.h>

int lastTime = clock(); // Timer Variable

int objectPosition = 0; // Object Position (in miles)

int milesPerSecond = 5; // Speed of object


void Update()
{
// Update time info

// Seconds = seconds passed since last update

float seconds = (((float)(clock() - lastTime)/CLK_TCK));
lastTime = clock();
// Move object

objectPosition += (milesPerSecond*seconds);
}


--- Alex Broadwin
--- Domini Nocti Games
snisarenko
snisarenko
I heard that QueryPerformanceTimer() is the best, but it doesn''t work on all computers. And i''ve never seen actuall source code implementing it. If anyone has the source code on QueryPerformanceTimer(), I would also like to see it

______________________________
My Website: [link]www.antondev.tk[/link]
or directly at http://home.attbi.com/~antonweb/
"I''ve read about...doom being used to manage unix programs(you "kill" the processes)" dede
brucesinner
brucesinner
Good post, Anonymous! (btw, leave your name later...) I have a question concerning it all...and if I want to do a cross-plataform application? Correct me if I''m wrong, but GetTickCount() is a Windows API call, isn''t it? And if, lets say, I want to implement a routine that compiles on Linux too?

"Steel and Fire,Spreading the Holy Word,Dirty Liars,The truth has never been told" - Primal Fear
Hawkeye3
Hawkeye3
So what method do the modern engines of today use? (UT, Quake, ect.)
Enigma
Enigma
Here''s a list of five ways of getting the time under Windows (use GetTimeOfDay under Unix). All have their advantages and disadvantages. Beware of the issues the the AP mentioned - especially with RDTSC on PCs that can throttle the processor. After each timing method I''ve given their performance measured on my system. Results won''t be completely accurate because there is no fully accurate timer to compare against.

Clock:
include: ctime
frequency: CLOCKS_PER_SEC
function: clock()
Timer Profile (Clock):
Frequency: 1000Hz
Resolution: 0.001s
Error: 0.01s
Speed: 3.85524e-08s

GetTickCount:
include: windows.h
frequency: 1000?
function: GetTickCount()
Timer Profile (GetTickCount):
Frequency: 1000Hz
Resolution: 0.001s
Error: 0s
Speed: 3.52e-08s

PerformanceCounter:
include: windows.h
availability check: QueryPerformanceFrequency(LARGE_INTEGER*) returns true
frequency: 1/QueryPerformanceFrequency(LARGE_INTEGER*)
function: QueryPerformanceCounter(LARGE_INTEGER*)
Timer Profile (PerformanceCounter):
Frequency: 1193182Hz
Resolution: 8.38095e-07s
Error: 4.77714e-05s
Speed: 4.20472e-06s

RDTSC (read processor time-stamp counter):
include: none
availablilty check:

  
DWORD timerAvailable = 0x1;

#if defined(__BORLANDC__)
_asm{
; Get CPU ID information
mov eax, 0
cpuid

; check if CPU is GenuineIntel
cmp ebx, ''uneG''
jne not_intel
cmp edx, ''Ieni''
jne not_intel
cmp ecx, ''letn''
jne not_intel

jmp test_RDTSC

not_intel:

; check is CPU is AuthenticAMD
cmp ebx, ''htuA''
jne not_available
cmp edx, ''itne''
jne not_available
cmp ecx, ''DMAc''
jne not_available

test_RDTSC:

; get CPU supported features
mov eax, 1
cpuid

; check if TSC supported
bt edx, 4
jnc not_available

; TSC supported
xor timerAvailable, 1h

not_available:

xor timerAvailable, 1h
}
#elif defined(__GNUC__)
asm(
" /* Get CPU ID information*/\n"
" movl $0, %%eax\n"
" cpuid\n"

" /* check if CPU is GenuineIntel*/\n"
" cmpl $0x756e6547, %%ebx\n"
" jne not_intel\n"
" cmpl $0x49656e69, %%edx\n"
" jne not_intel\n"
" cmpl $0x6c65746e, %%ecx\n"
" jne not_intel\n"

" jmp test_RDTSC\n"

"not_intel:\n"

" /* check is CPU is AuthenticAMD*/\n"
" cmpl $0x68747541, %%ebx\n"
" jne not_available\n"
" cmpl $0x69746e65, %%edx\n"
" jne not_available\n"
" cmpl $0x444d4163, %%ecx\n"
" jne not_available\n"

"test_RDTSC:\n"

" /* get CPU supported features*/\n"
" mov $1, %%eax\n"
" cpuid\n"

" /* check if TSC supported*/\n"
" bt $4, %%edx\n"
" jnc not_available\n"

" /* TSC supported*/
\n"

" xor $0x1, %1\n"

"not_available:\n"

" xor $0x1, %1\n"
:
"=r" (timerAvailable)
:
"r" (timerAvailable)
:
"%eax", "%ebx", "%ecx", "%edx"
);
#else
timerAvailable = 0;
#endif

return timerAvailable;

frequency: no easy method - calculate yourself using another timer
function:

  
int64 time;
int64* addr = &time
#if defined(__BORLANDC__)
__emit__(0x0f);
__emit__(0x31);
_asm{
; read TSC
mov esi, addr
mov [esi], eax
mov [esi + 4], edx
}
#elif defined(__GNUC__)
DWORD tscLowDWORD;
DWORD tscHighDWORD;
asm(
" rdtsc\n"
" mov %%eax, %0\n"
" mov %%edx, %1\n"
:
"=a" (tscLowDWORD),
"=d" (tscHighDWORD)
);
time = (((int64)tscHighDWORD) << 32) | tscLowDWORD;
#endif
return time;

Timer Profile (RDTSC):
Frequency: 933356508Hz
Resolution: 1.0714e-09s
Error: 2.33191e-05s
Speed: 6.78857e-08s

TimeGetTime:
include: windows.h
frequency: no easy method?
function: timeGetTime()
Timer Profile (TimeGetTime):
Frequency: 1020Hz
Resolution: 0.000980392s
Error: 0.0872549s
Speed: 1.39124e-07s

Enigma
PsychoPaul
PsychoPaul
Thanks to everyone for the replys.

Enigma: Very facinating information!! thank you very much!
I''ve made my decision as to which function i will use. since according to your information it has a zero error count it will be GetTickCount()

Oh btw once i get the time difference how should i use that information? should i just grab any arbitrary number and multiply/divide it by the time difference. or if it''s not an arbitrary number how do i figure it out.

here hows this. say i wanted to move an object 0.1 distance every second. would i do something like this:
0.1 distance * time lapsed and add that to my current position
would that be ok?
Paulhttp://members.lycos.co.uk/p79
Enigma
Enigma
GetTickCount doesn't really have zero error, it's just that on the single run I profiled it for it happened to match the timer it was tested against perfectly. The timer profiling code is part of a library I'm writing and is called when the first timer is created. It therefore sacrifices accuracy for execution speed. In reality GetTickCount seems almost identical to clock, but runs fractionally faster.

To use GetTickCount to move an object at 0.1 distance units a second do either:

// speed is your 0.1 distance units a second divided by the frequency of the timer.
//For GetTickCount I believe the frequency is 1000Hz
float speed = 0.1 / frequency;
int initialTime = GetTickCount();
while (true){
//game loop
position = initialPosition + (speed * (GetTickCount() - initialTime));
render();
}

or

// speed is your 0.1 distance units a second divided by the frequency of the timer.
//For GetTickCount I believe the frequency is 1000Hz
float speed = 0.1 / frequency;
int lastTime = GetTickCount();
int thisTime;
while (true){
//game loop
thisTime = GetTickCount();
position = position + (speed * (thisTime - lastTime));
lastTime = thisTime;
render();
}

The second version has potential problems with accumulated errors but is really your only choice if the object is not moving in constant direction.

Enigma

Edit: broke up long comments to avoid changing page width

[edited by - Enigma on April 28, 2003 7:23:49 PM]
PsychoPaul
PsychoPaul
Yup i was more or less thinking along the lines of the second type of movement. But what kind of "potential problems with accumulated errors" may occure? if you don''t mind me asking.
Paulhttp://members.lycos.co.uk/p79
Enigma
Enigma
It''s do with rounding errors on floats. There are really two issues here. A floating point number only has a certain number of significant digits that are stored. So if for example a float could only store four digits (real floats store more than four, four is just an example) then it would be able to store, for example:

0.0001234
0.001234
0.01234
0.1234
1.234
12.34
123.4
1234
12340
123400

e.t.c.

Now say your object starts at position 0 and moves at 100 units per second and your code renders 100 frames per second. Now every frame the object moves 1 unit. Initially this is no problem and the position runs from 0 upwards quite smoothly. After the position reaches 10000 things go wrong. In the next iteration of the code you get position = position (10000) + 1. The floating point unit adds 10000 and 1 with the result 10001. Unfortunately a floating point number can''t store 10001 because it can only store the first 4 digits, so instead it stores 10001 as 10000 and your object just sits there not moving becuase the position never increases beyond 10000.

The other problem occurs when your distance per frame itself cannot be correctly represented by a float. Imagine you object moves at 111.15 units per second and your code renders 100 frames per second. The amount added to the position each frame should be 1.1115, but because the float can only store the first four digits instead it will be 1.112 (or 1.111, not sure whether it will round up or not).

Usually these problems aren''t important because the first will only crop up if your position values get large enough relative to your speed and the second will simply result in objects moving at a fractionally different speed to what is expected.

Enigma
PsychoPaul
PsychoPaul
Ahhh i get it now.. cool thanks for your help Enigma. Time to do some real testing.

Paul
Paulhttp://members.lycos.co.uk/p79

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.