1
\$\begingroup\$

I'm working on a 3D web game with client-side prediction, etc. I have implemented interpolation for my entities, but it seems a bit laggy sometimes even on localhost. First I will describe the algorithm, and then I will describe the problem.

Interpolation algorithm:

  1. SERVER sends to all players (at 20 ticks per sec):

    1.1) positions and rotations of all players

    1.2) server time at the moment of sending this data (for example serverTime == Date.now() (it is calculated a little differently, but the value is roughly the same as Date.now(); the error is 20 ms), that calculates every server tick)

  2. CLIENT receives all data and:

  3. Saves received serverTime to global variable named localServerTime

  4. Pushes player's positions and rotations to their buffers (f.e. player1 has buffer1: { time: number, position: Vector3, rotation: Vector2 }[], so we push received(from server) player1 position and rotation to the buffer1: buffer1.push({ time: localServerTime, position: receivesPlayer1Pos, rotaiton: receivedPlayer1Rotation })

  5. CLIENT executes local game loop and changes localServerTime: localServerTime += deltaMs

  6. CLIENT interpolates all other players positions and rotations like this:

  7. We will interpolate players with offset == 100: INTERPOLATION_OFFSET_MS = 100, so find current lerp time: currentLerpTime = localServerTime - INTERPOLATION_OFFSET_MS

  8. For all other players:

  9. I will consider player1: get player1's buffer: buffer1 and get state interpolate FROM(stateInterpolateFrom) and state interpolate TO(stateInterpolateTo) ( we will find this two states by currentLerpTime, so stateInterpolateFrom.time < currentLerpTime and stateInterpolateTo.time >= currentLerpTime).

  10. Then we will find alpha(number, that is in range 0-1): alpha = (currentLerpTime - stateInterpolateFrom.time) / (stateInterpolateTo.time - stateInterpolateFrom.time).

  11. Finally, i will interpolate position and rotation: player1.position = lerp(stateInterpolateFrom.position, stateInterpolateTo.position, alpha), same for rotation


My problem here is the following: sometimes I have small lags that arise for some reason. I read that I need to synchronize time; I read a bunch of articles about entity interpolation, and the general principle is described there, but I did not find an algorithm about time synchronization anywhere, so I am asking for help.

I realized that the matter is in the localServerTime, I calculate it incorrectly, but I don’t understand what exactly needs to be done, how to calculate it. If anyone has any relevant articles to put things in place, I would be very happy if you share them.

P.S. On the client I have variable syncedServerTimeMs, when player connecting to the server, server send data: { serverTime: number, serverTick: number} and client calculates syncedServerTimeMs, so I can exactly say the serverTime when I need on it. In the client loop I update syncedServerTimeMs: syncedServerTimeMs += deltaMs, maybe I must use this variable somehow?

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

To get a more accurate server time, you need to know a few basic facts:

  1. You cannot rely on the client and server system time being the same.
  2. You cannot rely on time increments(such as deltaMs), some program time consumption or floating-point precision issues can cause delta time to accumulate errors on a long-time running.
  3. The difference in system time (CPU time) can be relied upon. On any device t1-t0 are accurate.
  4. There is a delay in the communication between the client and the server, and it is affected by various reasons (network delay, protocol parsing, thread communication...) This delay is unstable.You cannot assume the delay is non-existent or stable.

So the solution I suggest is that the client actively asks the server time regularly, calculates the delay through the time of the packet return, and saves the calculated server time and the client time when saving. When the server time needs to be used, the current server time is calculated by the current client time, the saved server time, and the client time when the server time is saved.

Pseudocode:

savedServerTime = 0//saved server time
savedClientTime = 0//client time when the server time is saved

void ServerTimeRequest(){
    SendToServer({ClientTime:Time.now()})
}

void ServerTimeResponse(data){
    rtt = Time.now() - data.ClientTime
    savedServerTime = data.ServerTime + rtt/2
    savedClientTime = Time.now()
}

int GetServerTime(){
    return savedServerTime + (Time.now() - savedClientTime)
}

Related:

Network client-server message exchange and clock synchronization help

Simple Network Time Protocol (SNTP)

\$\endgroup\$
1
  • \$\begingroup\$ I answered you below \$\endgroup\$
    – Pumpudu
    Jan 1 at 12:05
0
\$\begingroup\$

@Mangata, I didn’t care about time synchronization at all, I needed to know what time to use in interpolation, I would handle the synchronization myself. But I appreciate what you wrote, thanks.

I had to write this in the answers, not in the comments, because comment is limited by the number of characters.

in your case the error is +- rtt/2, because time to reach the server and time to reach the client maybe different. F.e. lets imagine time to reach the server(TTRS) = 5 ms, however time to reach client(TTRC) = 150 ms, then all calculations will be wrong.

I use JavaScript on client and Node.js on server. so we can something like this:

CLIENT:  SendToServer(SyncTimeKey)
SERVER: SendToClient({ serverTime: serverTime, dateNow: Date.now() })
CLIENT: 
void ServerTimeResponse(data){
    savedDateNow = Date.now()
    savedServerTime = data.serverTime + savedDateNow - data.dateNow
}
GetServerTime function is the same as yours. 

enter image description here In this case all will be ok, but as i mentioned, question was about other problem)

\$\endgroup\$
7
  • 1
    \$\begingroup\$ Please do not use answers to reply to other answers. If you need to add more information to your question, edit your question. If you need to include more content in a reply than will fit in a comment, we can start a chat room, or you can write the reply elsewhere and link to it. \$\endgroup\$
    – DMGregory
    Jan 1 at 12:27
  • \$\begingroup\$ Hello, it is convenient to regard TTRS and TTRC as the same in this time synchronization algorithm. In most cases, the delay comes from network transmission, and they are roughly equal. Even if the speed of protocol parsing is different due to the different technology stacks used by the client and server, their ratio is roughly stable. All clients rely on the authoritative server's time, and it doesn't matter if the server they calculate has roughly the same lag. This time synchronization method has been extensively verified, and I think there is no problem. :) \$\endgroup\$
    – Mangata
    Jan 3 at 2:53
  • \$\begingroup\$ As for the delay of interpolation, I think it is due to the way of interpolation. The client-side interpolation with prediction needs to predict the content of the data before the server sends the data. stateInterpolateTo should be a state that is inferred based on the existing data. If it is taken from the buffer, then you are not predicting(extrapolation). This(interpolation) will inevitably cause delay, and the degree of delay depends on the frequency of server sending packets and the network delay. \$\endgroup\$
    – Mangata
    Jan 3 at 3:14
  • \$\begingroup\$ @Mangata, I read in a series of Valve articles that they use interpolation, extrapolation is used if there is no data to interpolate (and the prediction is no more than 0.25 seconds). I want the same approach. The main problem with extrapolation is that it is difficult to use in a world where the controls are quite unpredictable (like a shooter). \$\endgroup\$
    – Pumpudu
    Jan 3 at 6:20
  • \$\begingroup\$ @Mangata, As I understand it, I need to use synchronized server time? and rewind it 100 ms back during interpolation. And when receiving packets, take the synchronized server time for the time to push it to the buffer, right? \$\endgroup\$
    – Pumpudu
    Jan 3 at 6:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .