• Create Account

Network input handling

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.

124 replies to this topic

#61Telanor  Members

Posted 10 December 2012 - 10:06 PM

Yep, that's exactly what's happening. The server seems to apply the input for exactly 1 less tick than the client. I don't understand why it's happening though...

Edit: Found the issue. The client was doing "if action.isTriggered, send triggered, else if action.isHeld, send held". Problem is, when you pressed a key, on the very first frame, it counted as both triggered and held. Client side, it was receiving both on the same tick, but the server had to wait an extra tick before getting the isHeld message.

Edited by Telanor, 10 December 2012 - 10:46 PM.

#62Telanor  Members

Posted 13 December 2012 - 02:10 AM

I'm having problems determining whether the client predicted the players location correctly or not. I have a circular array storing the player's position for each tick on the client. When the client receives an update from the server, which contains the server tick that it was for, it looks for the position it calculated for that tick in the array. It keeps coming up with the wrong answer though.

Say for example the client receives an update from the server that says "on tick 150, your calculated position was X = 10". So it looks at the array for it's position at tick 150, and it comes up with X = 13. When you look at the position for tick 145 though, it has X = 10. It's not that the client's numbers are wrong, it's just that the client and server don't agree on what tick that movement occurred at.

This is what I'm using to set/correct the client's current tick:

World.CurrentTick = tick + (int)Math.Ceiling(incomingMessage.SenderConnection.AverageRoundtripTime / 2f / Physics.Physics.WorldSpace.TimeStepSettings.TimeStepDuration);


Where tick is the tick from the latest update from the server. AverageRoundtripTime is measured in seconds, as is TimeStepDuration.

Edited by Telanor, 13 December 2012 - 02:14 AM.

#63Inferiarum  Members

Posted 13 December 2012 - 06:20 AM

I guess you should not divide by two

#64hplus0603  Moderators

Posted 13 December 2012 - 11:01 AM

It looks like you are still working with time, rather than ticks, as your baseline for timing.
Things happen on ticks. Ticks have numbers. That's your clock right there.
Only use a loose correlation of client clock to estimated ticks.

One good way of knowing how much to adjust your tick clock is to timestamp the packets sent, and the server sends you back a delta for how early/late the packets were, and you adjust your tick clock by some percentage of the reported delta. (Or by all of the delta, if it's sufficiently off, like any amount late, or more than 2 ticks early.)

enum Bool { True, False, FileNotFound };

#65Telanor  Members

Posted 13 December 2012 - 04:13 PM

I guess you should not divide by two

But it makes sense theoretically to do so... It only takes half the RTT for a packet to get from the server to the client, which means the server should have moved forward 4 ticks in that time rather than 8.

It looks like you are still working with time, rather than ticks, as your baseline for timing.
Things happen on ticks. Ticks have numbers. That's your clock right there.
Only use a loose correlation of client clock to estimated ticks.

Why would you think that?

One good way of knowing how much to adjust your tick clock is to timestamp the packets sent, and the server sends you back a delta for how early/late the packets were, and you adjust your tick clock by some percentage of the reported delta. (Or by all of the delta, if it's sufficiently off, like any amount late, or more than 2 ticks early.)

Seems like a reasonable idea, but that would require that I have the client send its input state every tick rather than only sending changes when they happen. Do you think I should do that?

#66riuthamus  Moderators

Posted 13 December 2012 - 05:22 PM

Depending on the code, the two might not be relative. If you assume 1 tick is always 1 second, and at some point it is not, your already setting yourself up for failure every time. Let the tick equal whatever time it takes to process it so that the value is fluid and is not a factor with time. If it takes 5 seconds or 50 seconds, that should not related to the better part of your code.

HPlus is saying put a timestamp in there so that you can use that parameter for the code or for checking ( if needed ) but let the ticks process as fast as possible and dont try to estimate the time it would take, estimate in ticks, whatever speed that may or may not be.

Again this is how i understood what he was saying.

#67hplus0603  Moderators

Posted 13 December 2012 - 08:45 PM

Seems like a reasonable idea, but that would require that I have the client send its input state every tick rather than only sending changes when they happen. Do you think I should do that?

There is no difference, assuming all messages get delivered. If not all messages get delivered, then it is good to send the state of inputs in each packet.

let the ticks process as fast as possible

No, this is not what I'm saying. I'm saying that the global rate of tick progression happens on the server. The server has a clock that maps real time to tick numbers by simple division. Each time the server wakes up, it checks what the current "goal tick number" is, and if the last simulated tick number is less than that, it simulates ticks forward until it's caught up.

On the client, you need to get an estimate of what the server's clock is. The server will send packets saying "this was the state of things at tick X," which means that you know the current tick is at least X. You can then send your input, saying "this happened at tick X+1."
The server will get that input, and it will probably be late (unless you're sitting right next to the server.) The server will then include information saying "you were Y ticks late in the last message, and here is data for tick Z."
You can then adjust your server tick offset to Y (or, at least, bump it up one step) and send the next set of inputs for tick Z+Y.
If your inputs arrive way too early, so the server has to buffer more than one network packet worth of tick-timestamped data, then the server can send "you were early" information so the client can bump down its estimate of how early it needs to send the data.

Corollaries:
- When the server sends tick adjustment data, the client will run physics faster than real time for that adjustment period (or slower/delayed, when adjusting backwards.)
- You need no round-trip estimate, because the server will simply tell you how much earlier (or later) you need to make your estimation. This means that asynchronous round trip times won't be a problem from a server clock estimate point of view.
enum Bool { True, False, FileNotFound };

#68Inferiarum  Members

Posted 14 December 2012 - 03:28 AM

But it makes sense theoretically to do so... It only takes half the RTT for a packet to get from the server to the client, which means the server should have moved forward 4 ticks in that time rather than 8.

The packet takes another RTT/2 to get to the server. You should try it without dividing by 2. Otherwise I think the calculation is fine.

It looks like you are still working with time, rather than ticks, as your baseline for timing.

But is it not preferable to store things like the RTT in time and then, e.g., calculate the number of ticks that have to be extrapolated at the client exactly like he did using values in time and then quantizing to ticks in the end?

edit: ok, i guess you could do it all with ticks if you do not think of them as discrete and allow stuff like RTT = 2.343 ticks, but that does not work if, e.g., you get the RTT already in time from the network library.

edit2: thinking about it, you still have to do the transition from time to ticks at some point on the client if you want to update the client time with the internal clock.

Edited by Inferiarum, 14 December 2012 - 03:38 AM.

#69hplus0603  Moderators

Posted 14 December 2012 - 12:14 PM

you still have to do the transition from time to ticks at some point on the client if you want to update the client time with the internal clock

Yes, but that's the only place you need to do it. Basically, there is one place in your code where you do:

targetTick = (currentTime() + serverOffset) * ticksPerSecond;

And you only do this at the beginning of a step (or, even, when detecting whether steps need to be done.)

And, you could measure serverOffset in ticks, rather than time -- you don't necessarily need better precision than that. Thus, the math could just as easily be:

targetTick = currentTime() * ticksPerSecond + serverOffset;

There's really very little reason to use resolution of fractional ticks in physics or simulation or networking.

For presentation, if you run graphics faster than physics, you may want it, to do presentation extrapolation. But try to keep that from leaking into simulation.

Edited by hplus0603, 14 December 2012 - 12:15 PM.
fix tags

enum Bool { True, False, FileNotFound };

#70Telanor  Members

Posted 14 December 2012 - 09:30 PM

Well I think I got the tick syncing working but now I'm having an awful time trying to get the position correction to work right. The problem is that that client can skip around, moving back a couple ticks, ahead a couple ticks, even skipping some if it spent too much time loading.

My plan was to look at the difference between the position the server calculated and the position the client calculated, and if it was past a certain threshold, adjust the player's current position by the difference in the calculation results. The issue is, when the server says, hey you were at this position for server tick X, how do I find where the client was for that tick? Using a circular array isn't working out because of the skipping around... I was thinking a queue maybe but if the client ends up skipping a tick because it can't keep up, it's still going to cause problems.

#71hplus0603  Moderators

Posted 14 December 2012 - 11:08 PM

The problem is that that client can skip around, moving back a couple ticks, ahead a couple ticks, even skipping some if it spent too much time loading.

Yes! Hence, lag in games. Typically, though, latency will be predictable and not change much, and the jitter you get will be within your acceptable interval where you don't change the estimated delay up or down at all.

Another useful mechanism for display is to update a "I think this entity will be at this point XX milliseconds from now" guess each time you get a packet, and interpolate the position of objects between wherever they are, and the future-predicted position. This will serve to somewhat hide clock jitter, at the expense of introducing some prediction offset rather than always displaying exact locations, after the fact.
enum Bool { True, False, FileNotFound };

#72Telanor  Members

Posted 14 December 2012 - 11:40 PM

Well the thing is... if I try to correct the position, it just results in you flying off into infinity because the corrections just keep piling on top forever. Even if you take network latency out of the picture, the issue would still arise with the current implementation should the client ever miss a tick due to it spending too much time on a previous one. I understand there might be some jitter due to fluctuations in the latency, but I think this is a different issue right now. I'm just not sure how to set this up so the client can check its predictions

#73hplus0603  Moderators

Posted 15 December 2012 - 11:34 AM

the corrections just keep piling on top forever.

I suggest you don't accumulate corrections, but instead just calculate "this is the position I'd like to be at at time X" and locally move towards that position. It might even just be a force impulse if the current physics location is just a bit off, and a large snap/warp only if the server position is significantly off from the local position.
I would not recommend accumulating an error delta over time.
enum Bool { True, False, FileNotFound };

#74Telanor  Members

Posted 15 December 2012 - 04:37 PM

How would you handle this situation then?

S: 3, C: 6 - Client moves from X = 5 to X = 7
S: 4, C: 7 - Client moves to X = 9
S: 5, C: 8 - Client moves to X = 11
S: 6, C: 9 - Server receives move command, new client pos = 7
S: 7, C: 10 - Server receives move command, new client pos = 8
S: 8, C: 11 - Server receives move command, new client pos = 10
S: 9, C: 12 - Client receives position = 7 from server for tick 6. Matches, no action taken
S: 10, C: 13 - Client receives position = 8 from server for tick 7. Warps player back to X = 8
S: 11, C: 14 - Client receives position = 10 from server for tick 8. Warps player to X = 10

Let's just assume the player is moving fast enough that they need to be warped. Normally the suggestion is to record the player input, rewind, correct, and reapply the input. Since I can't rewind, and you suggest not using deltas, how else can you deal with this without having never-ending warping? Even if you're not warping them, how can you 'nudge' the player in the right direction without knowing where they need to be *now*. The server is only telling you where they should have been several ticks ago.

Edited by Telanor, 15 December 2012 - 04:43 PM.

#75hplus0603  Moderators

Posted 15 December 2012 - 06:48 PM

There are a few ways to do that.

First, change simulation engine :-)

If you can't do that, then try not simulating the client ahead, but instead waiting for the server round-trip. You may be able to allow the camera to swivel freely with the mouse, but hide movement behind acceleration latency.

If you can't do that, then, when you receive a correction in position/velocity, apply a delta position/velocity to your physics engine on the same step if the correction is small. Specifically, when you get a correction for "pos=10" when you had "pos=11" then apply an impulse to move the player backwards by 1 unit, rather than moving to the particular position.
If the correction is big, then snap the player and stop the client from giving commands until you've received a server snapshot that matches the client snapshot again. This means that big corrections suck for the player, but... big corrections suck! :-)

Even better behavior may be had by applying a correction based on the extrapolated position. Let's say you get "S=7, X=10, V=1.5" and now "C=12," then you can compare the players position now (say, X=20) to the extrapolated position for now (which is (12-7) * V + X, or 17.5) and apply a delta of (20 - 17.5).

Edited by hplus0603, 15 December 2012 - 07:02 PM.

enum Bool { True, False, FileNotFound };

#76Telanor  Members

Posted 15 December 2012 - 07:59 PM

I doubt any physics engine would be able to handle rewinding in our situation. We have a dynamic world. Rewinding would mean saving and recreating the world itself. It's already expensive enough just creating it the first time. Recreating it several times in a single tick.... would be impossible. Not to mention the memory requirement of storing the world state.

I feel that a delta correction could certainly work in our situation *if* we could reliably get a list of all positions the client has moved through between the time the calculation error occurred and the time it received the proper location from the server. I just can't figure out how to do that given the client's tendency to have the current tick jump around. I guess part of the problem is that I don't know what issues it should be able to handle and what issues are external problems that need to be fixed in order for this system to work correctly.

#77hplus0603  Moderators

Posted 15 December 2012 - 11:42 PM

just can't figure out how to do that given the client's tendency to have the current tick jump around.

If the client's tick jumps around, you do not have sufficient de-jitter buffering. You can take a clock skew as a signal to not let input happen until the clock is re-synchronized. The server telling you to jump ahead 1 tick or back 1 tick can be solved by just stepping twice, or delay one step; any bigger corrections (once initial sync has happened) means you're doing something wrong, or running on an incredibly terrible network infrastructure (at which point: more de-jitter buffering!)
enum Bool { True, False, FileNotFound };

#78Telanor  Members

Posted 16 December 2012 - 03:23 AM

Well, I'd probably guess I'm doing something wrong then. Connecting to the server from the same computer, I get tick offsets of around +/-4 when moving around. Probably due to loading that occurs as you move further out. I'll play around with trying to reduce that. What do you mean by de-jitter buffering though?

#79hplus0603  Moderators

Posted 16 December 2012 - 01:26 PM

What do you mean by de-jitter buffering though?

A de-jitter buffer is a queue of incoming messages. The server then asks the queue "get me all messages for step X" and gets those for processing.
The time adjustment sent to the client is such that messages go into this buffer slightly earlier than they are asked for. How much earlier is a tunable parameter, and the earlier you send messages from the client (i e, the more lag compensation you apply in the queue, not just for the transmission time,) the more jitter your server can take without "running out" of packets for timestamp.
Same thing on the client receiving side.
The draw-back of this buffering is that it adds artificial lag, to compensate for jitter and get a smooth delivery of packets.
If the server or client code cannot keep up with your given step rate, and hitching because of loading or whatnot is common, then more de-jitter buffer is exactly what you need, because the end-to-end system (including processing of steps) is very jittery, and to get a smooth experience, you need to have sufficient buffering to cover that.

enum Bool { True, False, FileNotFound };

#80riuthamus  Moderators

Posted 16 December 2012 - 02:12 PM

What do you mean by de-jitter buffering though?

A de-jitter buffer is a queue of incoming messages. The server then asks the queue "get me all messages for step X" and gets those for processing.
The time adjustment sent to the client is such that messages go into this buffer slightly earlier than they are asked for. How much earlier is a tunable parameter, and the earlier you send messages from the client (i e, the more lag compensation you apply in the queue, not just for the transmission time,) the more jitter your server can take without "running out" of packets for timestamp.
Same thing on the client receiving side.
The draw-back of this buffering is that it adds artificial lag, to compensate for jitter and get a smooth delivery of packets.
If the server or client code cannot keep up with your given step rate, and hitching because of loading or whatnot is common, then more de-jitter buffer is exactly what you need, because the end-to-end system (including processing of steps) is very jittery, and to get a smooth experience, you need to have sufficient buffering to cover that.

This system could work, but wouldnt work well for pvp type of situations...

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.