every x milliseconds accuracy

For questions about using Classic.

Post » Wed May 06, 2009 1:08 pm

well, for all you out there, the "every x milliseconds" has a huge problem.

if you create an action let's say, every 1000 milliseconds (every second) add 1 to some global variable, you will be surprised how that "accurate to ~10 milliseconds" is going to affect this. well, after exactly 24 hours, the above variable, should count 86400... believe me, it will not. in this case, the ~10 ms after 2000 ms becomes ~20ms, so, after 86400000ms it's like ~864000ms = 864 SECONDS = which is like 15 minutes accuracy!

to try it out, make a directx game, put text object somewere and add mouse and keyboard. add 2 actions:

System: Every 10 milliseconds
> TextSet text to str(float(Text 0 .Text) + 0.010)

MouseKeyboard: On Left mouse button Clicked
> TextSet text to "0"

run the app and some kind of external windows counter (can be date and time properties / dbl click the taskbar clock) and click inside the game window, when the second hand comes to 0. watch as when it reaches 0 again after doing a full cirlce, the text is about 50 or something (should be 60).

is there any way to prevent this from happening?
B
81
S
50
G
10
Posts: 555
Reputation: 13,009

Post » Wed May 06, 2009 2:58 pm

Every ... msec isn't suited for intervals so low they'll execute every frame. Your code is correct, but the thing is, every event can be triggered only once during single loop.

You can make PV that adds timedelta per loop. And then you can check in WHILE whether its value is greater than desired "every ... msec" (but you input values in second units then).

Check this .CAP:
http://www.republika.pl/tymczasownik/ev ... WithPV.cap
B
6
S
3
G
6
Posts: 219
Reputation: 3,013

Post » Wed May 06, 2009 3:17 pm

[quote="BROO":3n168a3z]Every ... msec isn't suited for intervals so low they'll execute every frame. Your code is correct, but the thing is, every event can be triggered only once during single loop.

You can make PV that adds timedelta per loop. And then you can check in WHILE whether its value is greater than desired "every ... msec" (but you input values in second units then).

Check this .CAP:
http://www.republika.pl/tymczasownik/ev ... WithPV.cap[/quote:3n168a3z]

tymczasownik, lol :D
poland ftw!

as I remember from earlier testing, timedelta also has problems with accuracy, but you have a point.
B
81
S
50
G
10
Posts: 555
Reputation: 13,009

Post » Wed May 06, 2009 3:21 pm

Timedelta is basically the time between last and current frame. You know how wildly FPS can fluctuate. ;)
B
62
S
21
G
12
Posts: 1,910
Reputation: 13,155

Post » Wed May 06, 2009 10:49 pm

if you want time elapsed from the beginning you're better off using Timer (system - get time)
To fire off an event every certain amount of miliseconds with long-term accuracy, you could do something like

+compare Timer%MyFiringPeriod less or equal than MyFiringPeriod*0.4
+Fire once while true

Timedelta will always have rounding errors which will accumulate to something hideous.
Even if its precision was to the milisecond, rounding errors would still crop up when you accumulate.

This timer, of course, will wrap after a certain amount of days (which I'm too lazy to calculate). Adjust accordingly.
B
3
S
2
G
4
Posts: 1,445
Reputation: 4,665

Post » Wed May 06, 2009 10:56 pm

Timedelta's theorethical accuracy makes about 0.0001% error coming from using float for it. But if there was some practical tests made, I'd love to hear about them ^^.
B
6
S
3
G
6
Posts: 219
Reputation: 3,013

Post » Wed May 06, 2009 11:10 pm

Sorry, learned this in physics a while ago.

You just *can't* take a value and do calculations on it and expect the error not to grow.

Timedelta is in seconds, thus 0.0001% of timedelta amounts to 0.000001* [s] = 0.0001ms error

that over a day is: 24 hours * 60 minutes * 60 seconds * 1000 miliseconds = 86400000 ms.

the error thus will be bounded by 86400000 * 0.0001ms = 8640ms
That is, off by eight and a half seconds.
Of course, float's rounding error is not fixed and timedelta's error depends on the internal timer resolution, which could be QueryPerformanceCounter(). I read that one has microsecond resolution, which is nice. Still, it's not exact. It cannot be. Nothing is.
B
3
S
2
G
4
Posts: 1,445
Reputation: 4,665

Post » Thu May 07, 2009 6:29 am

Hmm, I didn't think this would be a problem. Can someone put up a quick .cap that demonstrates this? (preferably one I don't have to wait a day to see)
Scirra Founder
B
359
S
214
G
72
Posts: 22,952
Reputation: 178,580

Post » Thu May 07, 2009 8:16 am

I don't think it is a problem actually.
One has to blow up the error almost on purpose. I could do one example anyway, but there would be glaring ways to fix it without having to rewrite anything in construct.
I'd say it's more of a design issue on the game's side. I've worked with SDL and it had a terrible timer resolution, yet I managed to make smooth accurate movement using a few tricks here and there (which are perfectly doable within Construct but somewhat unnecesary).

That said, it would be nice to redefine Timedelta before behaviors touch it. Is that doable? One could rewrite timing at will that way without having to renounce behaviors.
Right now it *can* be done (look at my frameskip example :P) but you have to forget about any behaviors that use Timedelta.

Edit: it would seem I'm contradicting myself :P I'm not. I'm just saying that while rounding errors do accumulate (in everything), one can just avoid accumulating them. Just look at Euler versus Verlet integrators in physics, both come from the same Newton equations, but one of them accumulates rounding error while the other cancels it.
B
3
S
2
G
4
Posts: 1,445
Reputation: 4,665

Post » Thu May 07, 2009 2:10 pm

Since I don't know how was this solved by programmers, experiments will show some stuff.

Here is the simple "add timedelta" thingy:
:arrow: Text('t') adds timedelta meaning it counts seconds
:arrow: Text('t1000') adds timedelta*1000 meaning it counts miliseconds
:arrow: Timer retrieves play time

And all those errors provide difference between Timer and Text('t')*1000 or Timer and Text('t1000'). The [msec] error doesn't divide stuff, the [s] error divide result by 1000.

[url:3k4wn5t5]http://www.republika.pl/tymczasownik/timerCountingError.cap[/url:3k4wn5t5]


:arrow: Timer is based on adding timedelta
:arrow: 1000*timedelta produces numerical errors
:arrow: Numerical errors are random (one time positive, one time negative) and that leads to minimalizing the error
:arrow: The bigger the time is, the bigger numerical error is produced on every tick
:arrow: 0 error for adding nonscaled timedelta doesn't neccesary mean that Timer is compatible with system time (well, actually who'd bother if there was like 0.001msec error during the longest play possible?).

I'm now leaving program on to see results after some time...

Because errors are random, maybe after few hours the error will be positive value ^^.

Maybe there's a better point in checking this incrementing with Profiler's "getTickCount()"? I remember using getTickCount() and it's resolution wasn't really pleasing, is it the same way in Construct's profiler?

Edit:

...positive :-).

Edit2:
B
6
S
3
G
6
Posts: 219
Reputation: 3,013

Next

Return to Help & Support using Construct Classic

Who is online

Users browsing this forum: No registered users and 6 guests