• Create Account

Best way of sending updates at fixed intervals.

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

8 replies to this topic

#1fjholmstrom  Members

172
Like
0Likes
Like

Posted 08 January 2013 - 06:15 AM

Assume my game is running at a rate of 60Hz (~16.66 ms) on both the server and client and updates from the server to the client is sent at 20Hz (50ms). Now since the game is running at 60Hz, there are two ways of calculating the interval for the client updates. I can either just do this:

if((currentTime - lastSendTime) >= 0.05) {
SendUpdate();
lastSendTime = currentTime;
}

And just check this every frame for each client. But to me it seems like a cleaner solution would be to specify the client send rate in "every n:th" frame instead, so if I want to send at 20Hz when running at a 60Hz simulation rate I would just do this:

if(frameNumber % 3 == 0) {
SendUpdate();
}

It just feels a lot cleaner, you don't need to keep a separate timer or have to deal with small inaccuracies in the local time of the server, etc. It also becomes nicely synchronized with updates to the game state on the server, etc. The reason I'm asking is because in the few examples of "professional" networking code you can find on the web, I see *everyone* using a the first method of a separate timer for the send time for each client, so I'm thinking I must be missing something here?

#20BZEN  Members

2194
Like
0Likes
Like

Posted 08 January 2013 - 07:02 AM

For sure, if you can guarantee 60Hz, then frame hopping will be better, due to 'aliasing' associated with the first method (sometimes you will get perfect 1/3 updates, sometimes you will get 1 /4 updates doe to floating point drift).

Then you can also have options for 30Hz, 15Hz, 12Hz, 10Hz, 12Hz, ... (1/3, 1/4, 1/5, 1/6, ). A reason why '60' is a nice number to have.

Professionally, the first method is often used, with maybe some rounding error to compensate for round off errors and frame aliasing. Even sometimes the network tick runs in a separate thread.

Se second advantage, is that you can guarantee the frame timestep (so, each network tick will be exactly 20Hz, or considering to be exact), thus removing the need to implicitly indicate timing (you can measure time using the packet sqn, for example).

Sometimes, the problem of aliasing is not always such a big deal tbh. It depends how things work for you.

Edited by papalazaru, 08 January 2013 - 07:03 AM.

Everything is better with Metal.

#3fjholmstrom  Members

172
Like
0Likes
Like

Posted 08 January 2013 - 07:28 AM

papalazaru:

If the simulation runs on a fixed timestep (60Hz) - I would take that as a guarantee of 60Hz, it might jitter a *tiny* bit back and forth but at least the whole simulation will "jitter" instead of individual connections send intervals. And yes, the slight aliasing which will happen with the first method is what hit me when I implemented it - but like you say the aliasing could be countered by simply doing some slight rounding or something equivalent.

One thing which trips me up about using the second method of "frame hopping" (never heard that expression before) is this: What happens when I do get jitter on the server, what if I get HUGE jitter and I need to run 4 frames to "catch up" to real time. Then I will send two packets in quick succession... hm, but then again if that happens the client will just perceive it as network jitter anyway. The difference here I suppose is that timing it manually using (lastSend - currentTime) you would only send *one* packet for those four updates.

#4Ashaman73  Members

13651
Like
0Likes
Like

Posted 08 January 2013 - 09:33 AM

Here's an other method:

// all times are long given in ms
const long INTERVAL = 1000/frequency_in_Hz;
if( (current_time / INTERVAL)>(last_time/INTERVAL))
{
sendUpdate();
last_time = current_time;
}


This ensures that you sends at most X, equally spaced, updates per second, but less if your performances drops for some reasons.

Edited by Ashaman73, 08 January 2013 - 09:33 AM.

Ashaman

#5hplus0603  Moderators

10579
Like
0Likes
Like

Posted 08 January 2013 - 11:02 AM

It is my advice that you should always run physics with fixed time steps. And, thus, if you use fixed time steps. you can also run networking at a fixed number of physics steps.

At the larger scale, if you get a big frame hitch, then yes, you may send to packets in quick succession. And, as you say, this will look to the other end as jitter, which is what it is.

enum Bool { True, False, FileNotFound };

#6wintertime  Members

3826
Like
0Likes
Like

Posted 08 January 2013 - 11:52 AM

Here's an other method:
// all times are long given in ms
const long INTERVAL = 1000/frequency_in_Hz;
if( (current_time / INTERVAL)>(last_time/INTERVAL))
{
sendUpdate();
last_time = current_time;
}


This ensures that you sends at most X, equally spaced, updates per second, but less if your performances drops for some reasons.

Thats got 2 bugs. Should be more like this:

if(current_time>=last_time+INTERVAL)
{
sendUpdate();
last_time += INTERVAL; // to prevent the step being slightly larger
}


#7hplus0603  Moderators

10579
Like
0Likes
Like

Posted 08 January 2013 - 12:35 PM

Thats got 2 bugs.

No, it doesn't, assuming current_time and last_time are integral types. Given that INTERVAL is a long, and he counts in milliseconds, that's a reasonable assumption.

I don't like using milliseconds as integers for timing much, as they are kind-of imprecise, but it actually works alright. The only bug in the code posted above is when the system clock wraps 2 billion milliseconds, which happens a little over three weeks after boot. (And then again nine weeks after boot.)

enum Bool { True, False, FileNotFound };

#8wintertime  Members

3826
Like
0Likes
Like

Posted 09 January 2013 - 12:28 PM

I was assuming current_time and last_time were real numbers like in the thread starting post; that would mean its nearly always updating.

If they are longs its still not right, for example wanting 60 updates per second 1000/60=16.666666... not 16 and 1000/16=62.5 updates per second.

Depending on 2 divisions to round down appropriately seems not the right way to do this as when the starting time is not exactly divisible by the INTERVAL everything is delayed about the size of remainder forever.

Also there are forgotten updates when the time ticks further than an exact multiple of the INTERVAL sometimes and you just set last_time=current_time instead of adding:

for example wanting 100 updates -> INTERVAL==10

current_time==0 -> no update

current_time==9 -> no update

current_time==21 -> 1 update -> oops should have been 2

I guess you are just assuming larger error margins are ok when I was already counting them as wrong.

#9hplus0603  Moderators

10579
Like
0Likes
Like

Posted 09 January 2013 - 10:00 PM

I was assuming current_time and last_time were real numbers like in the thread starting post

But if you read the code, that's clearly not what it's assuming.
If they are longs its still not right

Yes, it does, because it does the division and compares every step.
Also there are forgotten updates when the time ticks further than an exact multiple of the INTERVAL sometimes and you just set last_time=current_time instead of adding

But that's not what the code is doing. It adds for each step, and if time elapsed more than one step, it will step faster until it catches up.

I'm all for pointing out flaws in code snippets, so that other readers can be aware. However, so far, I'm afraid you're batting pretty poorly :-)
enum Bool { True, False, FileNotFound };

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.