Skip to content

Instantly share code, notes, and snippets.

@thomasboyt
Created April 20, 2016 21:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomasboyt/31271ef9b13a46204a317f109d82e4c0 to your computer and use it in GitHub Desktop.
Save thomasboyt/31271ef9b13a46204a317f109d82e4c0 to your computer and use it in GitHub Desktop.

I'm finally getting around to putting "proper" netcode in my golf game, and I'm having a lot of trouble figuring out how to "sync" against a previous state. I've currently got it implemented in the most naive way: the server sends a "sync" message with everyone's positions and velocities to every client every second, and the client just resets all the positions/velocities of the physics bodies to whatever the server sent.

The problem is that the server state is often ahead of the client state, which I think makes sense: the server is sending each player's swing to the clients, so the network lag there causes the client simulation to be slightly behind. So when the sync happens, the balls of all the players "jump" forward a bit to whatever the server simulated.

I looked into implementing a clock, with the idea that the server would send a clock with its positions data the client could compare against. But I'm not sure what this should look like.

My first thought was that:

  • When the player connects to the server, the server sends its current time, and the client does all of its time calculations based on this initial time.
  • Sync messages will include the current server time
  • When the client receives a sync message, it:
    • Looks back to the physics state at that time
    • Check to see if the stored state significantly off from the sync message state
    • If so, reset all of the ball positions and to the synced message and advance the world by the delta between the current time and the sync time to catch up.

However, the problem I ran into with this plan was that the client's time is always going to be behind the server time (since there was some lag in sending the initial time to the client). This means that when the sync message comes in, the server time included in it is often ahead of the client's time, and of course the client then can't "look back" to the state at that time, since it hasn't been calculated yet.

I'm not sure what the right way to solve that is. I suppose that either the sync messages could be stored until that time is reached and then checked, or simply some kind of buffer could be added to ensure the client is always ahead of the server.

I've been looking into some articles on this (like http://gafferongames.com/game-physics/networked-physics/) but all of them seem to assume that the client time is always ahead of the server time, so I feel like I must be implementing the clock wrong, but I'm not sure how.

@fritzy
Copy link

fritzy commented Apr 21, 2016

a) give yourself a buffer and let the server get a head start b) give the client an animation for hitting the ball before it hits -- send notice as soon as ball hits of what the force is going to be at which frame (or time offset) c) the server can be ahead, either the client needs to catch up, or wait for the frame (time) to actually do anything. If things get more than 200ms behind, try to catch up, otherwise, let the server be ahead. If the server is less than 17ms (16.66~), let the server get ahead a bit, and slow down the simulation.

@fritzy
Copy link

fritzy commented Apr 21, 2016

Let the server start the clock. Count frames assuming an attainable framerate for the physics engine (sometimes up to 4x the render engine framerate). Every 2 billion frames, let the frame counter roll over. Physics engine says X happens at Y frame. Wait until that frame to apply force, or go back in time if the frame has already happened and re-simulate to current after adding that force before rendering the next frame. Try to let the server be 1 - 10 physics frames ahead.

@thomasboyt
Copy link
Author

thomasboyt commented Apr 21, 2016

The problem I have with letting the server being ahead and having a buffer is then handling player inputs:

  • Client is at tick=5, server at tick=6
  • Server sends sync message with tick=6 positions, client buffers this mesage until it reaches tick=6
  • Still in tick=5, client sends swing input, starts rendering the swing using predictive physics
  • Client reaches tick=6, syncs, and the ball is reset back to the position it was before the swing
  • Ball isn't rendered at correct position until server sends tick=7 with new position

This wouldn't be so problematic if the sync ticks were frequent (some kind of interpolation could smooth over this), but I'm trying to have long (1 second) times between snapshot syncs to decrease server strain. Since players are sent the swings of other players when they happen, the snapshots shouldn't need to be as frequent as they'd be in e.g. an action game where the snapshots are the only updates of what other players are doing.

Right now, the worst case scenario is: with a snapshot tick period of 1 second, and a lag time of 100ms

  • I receive a sync message with time=100ms at time=0ms
  • I swing at time=50ms
  • At time=100ms, I apply the sync message, causing the ball to revert to its previous position
  • The ball is then stuck there until I apply the sync message I receive at time=1000ms

My immediate thought is to not apply sync to the player's ball if a swing occurred during the "lag time." Not sure what that code looks like yet, though.

@fritzy
Copy link

fritzy commented Apr 21, 2016

The server will have to recalculate from that point as well. The server is saying "this force is applied at X frame" and the clients have to recalculate from that point. The client, when it takes an action says that a force is hit at frame Y, and the server needs to recalculate from that point and relay that information so that the other clients do that as well. Waiting for sync isn't necessary if you're relaying all of the forces and the frames of those forces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment