Network input handling

Started by
123 comments, last by hplus0603 11 years, 2 months ago
I think the server and client should run physics at the same number of steps per second. This means that you may need to step physics more than once between each rendered frame to "catch up" if rendering is slow. I don't think using a "tick rate" that is different from physics rate is all that useful on the server; in fact, I think it would just get in the way.
Instead, I would use a high-precision timer (QueryPerformanceCounter on Windows, clock_gettime(CLOCK_REALTIME) or similar on Linux) and calculate how much I need to wait until the next physics tick, and then pass that as the timeout delay to the call to select() (assuming you use select().) If you get data before it's time to run physics, select() unblocks, you decode the data, and go back to select(). Once it's time (which you detect by the time-to-sleep calculation returning 0 or a negative number,) you step physics, then stay in the same loop. Thus, there is no fixed clock rate at the server, other than the rate at which it steps physics.

If you use another API, you may have to do other things to arrange for waking up each time it's time for physics, but no matter what it is, using any deadline other than "the time I know I need to run the next physics tick" is unlikely to be optimal. Also, don't try to "anticipate" any latency in wake-up -- just do the math as if the thread will wake up perfectly, and when it wakes up a little bit late, you're effectively running in "catch-up" mode with less than a full frame to catch up, so everything's still fine.

Also, I would not "consume" input, but instead "set" input. Each packet, send the state of all input to the server. The server sets "the input of the client is X until I hear differently" in its receiving. If you drop a packet, chances are, that input was the same as the previous input, and the server and client don't de-sync because of it. Also, you can stuff multiple frames into a single packet. Say you run at 60 Hz, and send packets at 20 Hz. Then you include the input for the last three frames in each packet you send.
enum Bool { True, False, FileNotFound };
Advertisement
Well I think we are doing all those things. The physics has its own internal stepping. I call update and tell it how much time has passed and then it will accumulate the time and run as many steps as needed. Also, the input isn't being consumed. Like I said, the server combines the states. So if it has W down, D down as its current state and it receives a Space down message, then the state becomes W down, D down, Space down.

Well I think we are doing all those things. The physics has its own internal stepping. I call update and tell it how much time has passed and then it will accumulate the time and run as many steps as needed. Also, the input isn't being consumed. Like I said, the server combines the states. So if it has W down, D down as its current state and it receives a Space down message, then the state becomes W down, D down, Space down.


So you kind of delta encode the packets? I think this is not really that practical for input packets, since you have to deal with lost packets, and it may actually need more data then sending the whole state at each game logic tick. As hplus said, most of the time a small integer is enough to send all key state information as a bitmask.
Well the thing is, I'm not actually sending keys, I'm sending actions. And since mods can add new actions, I can't use a bitmask. Also, we're using lidgren to handle the low level networking details. There's no worry of lost packets.
You can still send a bitmask you just have to have the same information on the server and the client, which bit maps to which action. For example you could write all actions in a text file and just assign IDs successively when reading it. Of course on the client you need additional information about which key maps to which action, but you should have that already.

That is, the client produces the bitmask out of information from the input devices and the mapping from actions to IDs, sends it to the server and the server gets the bitmask from the network interface.

The actual part of the program (e.g. a class which stores the current bitmask internally and also knows the mapping from the actions to the bit IDs ) that presents the input to the game logic is the same for client and server.

edit: also in my opinion resending lost input packets in this scenario does not make any sense because at the time the lost packet arrives at the server it is probably out of date anyway.
I suppose that could work if we set a max supported number of actions. I'm still not sure whats causing the issue with the server simulating the input state for a shorter period of time than the client though.
You could also make the bitmask more flexible to allow for an arbitrary number of actions, you have to know if it is worth the effort.

If you time stamp the input states with the simulation tick they should be used for, there is a one to one mapping between input state and simulation tick, and, as long as the input packets arrive at the server in time, the same input should be used for the same simulation tick. So if this is what you are doing then I am not sure where the issue is.
The states aren't being tagged with the simulation tick. I'm not sure I understand how that's supposed to work either. If I assume both the server and client are perfectly in sync and the client sends out a "move forward" command for tick 150, by the time the server gets it, it will already be past that point. If the client is meant to be behind the server, as some articles have suggested, then it still doesn't make sense, because the server will have already simulated the tick before the client has even issued the command.
If you want to do client side prediction, the client actually runs ahead of the server (for something slightly more than half the round trip time) because like you mentioned you want the input packets to reach the server in time.

If you get a game state update from the server, you simulate up to the current client time with local inputs and if you are the only player both client and server come to the same result for the game state at the same tick (assuming client does a full simulation).

Of course if there is a lot of interaction with other players (like in an multiplayer FPS) you get a problem because you have no information about the inputs of other players. That is why in this type of game the predicted state is only used to determine the camera position, and all other players are rendered based on information in the past.

E.g. if we have client time CT, round trip time RTT then the camera is placed using the predicted state at time CT but all other players are placed using the state at time
CT - RTT - IP, where IP is the interpolation time (https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking).

You could also try to predict the input of the other players and draw everything at time CT (http://www.gamasutra.com/view/news/177508/The_lagfighting_techniques_behind_GGPOs_netcode.php#.UMB7HdPjlh4).
This is possible if actions have something like a charge-up.
How do you place the client ahead of the server? Do you just set the clients time = server time + round trip time when the client joins? What happens if their round trip time fluctuates? Or if they happen to connect in the middle of a lag spike, giving them a 400ms ping which later settles down to 50ms?

Also, that valve article doesn't seem to be feasible in my situation. They're relying on being able to rewind the state of all the players to do calcuations: "This doesn't mean you have to lead your aiming when shooting at other players since the server-side lag compensation knows about client entity interpolation and corrects this error." What do you do when it proves to be too expensive to rewind and resimulate the entire state, and when you have to deal with more then just players in the simulation?

Edit: I'd like to add that I while I haven't tested to see if we can actually afford to rewind and resimulate the entire state, the author of the physics engine advises against it in this thread

This topic is closed to new replies.

Advertisement