geckosio / snapshot-interpolation

A Snapshot Interpolation library for Real-Time Multiplayer Games.
BSD 3-Clause "New" or "Revised" License
270 stars 23 forks source link

Clock desync causes delayed interpolation #11

Closed andrewmunro closed 2 years ago

andrewmunro commented 3 years ago

I have a weird issue that I'm not quite sure how to fix. Occasionally my server gets into a state where all interpolation is really delayed. It interpolates the packets really smoothly, but everything on the client is delayed by a few seconds. The server is not lagging during this time (in fact, I can hear sound effects instantly which are not interpolated).

During this time, I've noticed that the _timeOffset value is really high (normally it's negative, but when it's broken it goes positive)...

Working: image

Broken: image

Refreshing the client has no effect. The only thing that fixes it when this occurs is to reboot the server.

My theory is that if the server is running slow (CPU starved), the clock gets delayed and causes a permanent desync between the server and client and it never recovers.

Is it correct that the timeOffset only gets set once on the first snapshot? It feels like there needs to be some kind of clock sync in place, where the client is using serverTime rather than two separate clocks.

yandeu commented 3 years ago

My theory is that if the server is running slow (CPU starved), the clock gets delayed and causes a permanent desync between the server and client and it never recovers.

Do you think that a slow server could delay the clock? I have no idea :/

Is it correct that the timeOffset only gets set once on the first snapshot?

Yes.

It feels like there needs to be some kind of clock sync in place, where the client is using serverTime rather than two separate clocks.

Hmm. I will think about that.

lucas-jones commented 3 years ago

Maybe there should be a way to update timeOffset incase of latency fluctuation?

That way a simple ping-pong could update this offset

yandeu commented 3 years ago

I guess you could try this:

channel.on('update', snapshot => {

  const timeOffset = SnapshotInterpolation.Now() - snapshot.time

  if (timeOffset_has_changedTooMuch(timeOffset))
    SI._timeOffset = timeOffset

  SI.snapshot.add(snapshot)

})

I guess you should not update SI._timeOffset too often, since the player will notice it.

lucas-jones commented 3 years ago

We'll give this a go!

Maybe if we lerp updating the _timeOffset the player might not notice

lucas-jones commented 3 years ago

Looked to have worked 🎉

Thanks Yandeu!

yandeu commented 3 years ago

Cool!

Do you think we should add an automated check into the library? Maybe auto adjust timeOffset every time it offsets more than 50ms more?

How did you do it? What is your logic inside timeOffset_has_changedTooMuch()?

lucas-jones commented 3 years ago

It would be useful to have this handled in the library. I would like timeOffset to be public (currently hacked in with as any for Typescript)

You're right! 50ms exactly too 😄

Our simple implementation

const timeOffset = SnapshotInterpolation.Now() - snapshot.time;
const timeDifference = Math.abs(this.snapshotInterpolation.timeOffset - timeOffset);

if (timeDifference > 50) {
    console.log(`Client is behind from server ${this.snapshotInterpolation.timeOffset} vs ${timeOffset}. ${timeDifference}ms Difference.`);
    this.snapshotInterpolation.timeOffset = timeOffset;
}
yandeu commented 3 years ago

😄 I think I will add it somewhere here: https://github.com/geckosio/snapshot-interpolation/blob/297a62d17a4669df4c00fa24250f35eb547c04e7/src/snapshot-interpolation.ts#L84