Sign in to follow this  
Followers 0
Telanor

Network input handling

124 posts in this topic

[quote]in the test I just did, the client moved for 5468.7882ms while the server only moved for 5438.1314ms[/quote]

Why are you measuring milliseconds? You should be measuring number of steps.

Also, the inputs (on and off) should be time stamped with step numbers when they take effect. That way, they will run exactly the same number of steps on client and server.
1

Share this post


Link to post
Share on other sites
What do you mean steps? Actual character animation steps? Or "press W, move forward 1 foot" steps? The character movement as it's set up right now simply applies a velocity to the player for the duration the key is held. The character isn't moved in discreet amounts. Should it be?
1

Share this post


Link to post
Share on other sites
well, I guess it was mentioned earlier that it is a good idea to do the simulation/game state updates with a fixed time step.

What you are doing right now is probably something like this (written as a pure function):

newState = update( oldState, inputs, deltaTime)

whereas with a fixed time step you would only have

newState = fixedUpdate( oldState, inputs)

and the duration of the update is kind of hard coded.

edit: if you do the fixed update, you only need the same set of inputs every time you do the update, for two simulations to be consistent. And since this is a discrete system time is represented by an integer counting the number of updates. Edited by Inferiarum
1

Share this post


Link to post
Share on other sites
Well I've done as much as I can think of to get it to a proper fixed timestep and the issue hasn't been fixed at all. All of the updating, both on the client and the server, is now run from an event that fires from the physics engine whenever it runs a timestep. The physics internally accumulates the time and runs timesteps at fixed intervals. I still haven't added the time stamping of input states, but I don't think that that would fix this issue, since I'm running both locally, it's not like there's any lag involved.
1

Share this post


Link to post
Share on other sites
[quote]I still haven't added the time stamping of input states, but I don't think that that would fix this issue, since I'm running both locally, it's not like there's any lag involved.[/quote]

You should add the timestamping, and assert that it is correct. When it isn't, you can then start debugging why.
1

Share this post


Link to post
Share on other sites
Are you polling the input at the same tick rate as the server, or applying it for the same duration as a poll tick interval?
1

Share this post


Link to post
Share on other sites
Yes, everything is run at the same tick rate and, at a minimum, applied for the duration of a tick
1

Share this post


Link to post
Share on other sites
As it is right now, they shouldn't be able to, assuming I haven't screwed up somewhere. It should all be applied for a whole number of ticks. Should it not be like that? Edited by Telanor
1

Share this post


Link to post
Share on other sites
You can go either way, depending on the resolution of your physics integrator, but applying consistent whole numbers of ticks is generally safest if your simulation rate is high enough that this doesn't cause jittery interactions.

If you are applying, say, 3 ticks on the client and 4 on the server, you've found your bug. If both agree that an action is applied for 3 ticks but the actual number of milliseconds applied differs, you have a bug in your timestep fixation code.
2

Share this post


Link to post
Share on other sites
Yep, that's exactly what's happening. The server seems to apply the input for exactly 1 less tick than the client. [s] I don't understand why it's happening though...[/s]

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
1

Share this post


Link to post
Share on other sites
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:

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

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

Share this post


Link to post
Share on other sites
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.)
1

Share this post


Link to post
Share on other sites
[quote name='Inferiarum' timestamp='1355401201' post='5010171']
I guess you should not divide by two
[/quote]

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.

[quote name='hplus0603' timestamp='1355418075' post='5010271']
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.
[/quote]

Why would you think that?

[quote name='hplus0603' timestamp='1355418075' post='5010271']
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.)
[/quote]

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?
0

Share this post


Link to post
Share on other sites
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.
0

Share this post


Link to post
Share on other sites
[quote]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?[/quote]

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.

[quote]let the ticks process as fast as possible[/quote]

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.
1

Share this post


Link to post
Share on other sites
[quote name='Telanor' timestamp='1355436792' post='5010357']
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.
[/quote]

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.

[quote name='hplus0603' timestamp='1355418075' post='5010271']
It looks like you are still working with time, rather than ticks, as your baseline for timing.
[/quote]

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
1

Share this post


Link to post
Share on other sites
[quote]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[/quote]

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

[code]targetTick = (currentTime() + serverOffset) * ticksPerSecond;[/code]

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:

[code]targetTick = currentTime() * ticksPerSecond + serverOffset;[/code]

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
fix tags
1

Share this post


Link to post
Share on other sites
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.
0

Share this post


Link to post
Share on other sites
[quote]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.[/quote]

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.
1

Share this post


Link to post
Share on other sites
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
1

Share this post


Link to post
Share on other sites
[quote]the corrections just keep piling on top forever.[/quote]

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.
1

Share this post


Link to post
Share on other sites
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
1

Share this post


Link to post
Share on other sites
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
1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0