mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 11:00:32 +00:00
remoteTime based on first snapshot.timestamp += deltaTime instead of sequence ushort
This commit is contained in:
parent
a91604d109
commit
b9a6ef38bb
@ -25,15 +25,26 @@ public abstract class OumuamuaBase : NetworkBehaviour
|
||||
float lastClientSendTime;
|
||||
float lastServerSendTime;
|
||||
|
||||
// "When we send snapshot data in packets, we include at the top a 16 bit
|
||||
// sequence number. This sequence number starts at zero and increases
|
||||
// with each packet sent. We use this sequence number on receive to
|
||||
// determine if the snapshot in a packet is newer or older than the most
|
||||
// recent snapshot received. If it’s older then it’s thrown away."
|
||||
ushort serverSendSequence;
|
||||
ushort clientSendSequence;
|
||||
ushort serverReceivedSequence;
|
||||
ushort clientReceivedSequence;
|
||||
// keep track of last received timestamps and throw out any snapshots
|
||||
// older than that (according to the article).
|
||||
// TODO seems like any snapshot that still fits in the buffer before we
|
||||
// process it seems worth keeping?
|
||||
// TODO consider double for precision over days
|
||||
float serverLastReceivedTimestamp;
|
||||
float clientLastReceivedTimestamp;
|
||||
|
||||
// snapshot timestamps are _remote_ time
|
||||
// we need to interpolate and calculate buffer lifetimes based on it.
|
||||
// -> we don't know remote's current time
|
||||
// -> NetworkTime.time fluctuates too much, that's no good
|
||||
// -> we _could_ calculate an offset when the first snapshot arrives,
|
||||
// but if there was high latency then we'll always calculate time
|
||||
// with high latency
|
||||
// -> at any given time, we are interpolating from snapshot A to B
|
||||
// => seems like A.timestamp += deltaTime is a good way to do it
|
||||
// => let's store it in two variables:
|
||||
float serverRemoteClientTime;
|
||||
float clientRemoteServerTime;
|
||||
|
||||
// "Experimentally I’ve found that the amount of delay that works best
|
||||
// at 2-5% packet loss is 3X the packet send rate"
|
||||
@ -53,11 +64,11 @@ void CmdClientToServerSync(Snapshot snapshot)
|
||||
if (clientAuthority)
|
||||
{
|
||||
// newer than most recent received snapshot?
|
||||
if (snapshot.sequence > serverReceivedSequence)
|
||||
if (snapshot.timestamp > serverLastReceivedTimestamp)
|
||||
{
|
||||
// add to buffer
|
||||
serverBuffer.Enqueue(snapshot, Time.time);
|
||||
serverReceivedSequence = snapshot.sequence;
|
||||
serverBuffer.Enqueue(snapshot);
|
||||
serverLastReceivedTimestamp = snapshot.timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,11 +81,11 @@ void RpcServerToClientSync(Snapshot snapshot)
|
||||
if (!IsClientWithAuthority)
|
||||
{
|
||||
// newer than most recent received snapshot?
|
||||
if (snapshot.sequence > clientReceivedSequence)
|
||||
if (snapshot.timestamp > clientLastReceivedTimestamp)
|
||||
{
|
||||
// add to buffer
|
||||
clientBuffer.Enqueue(snapshot, Time.time);
|
||||
clientReceivedSequence = snapshot.sequence;
|
||||
clientBuffer.Enqueue(snapshot);
|
||||
clientLastReceivedTimestamp = snapshot.timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,17 +102,47 @@ void ApplySnapshot(Snapshot snapshot)
|
||||
// helper function to apply snapshots.
|
||||
// we use the same one on server and client.
|
||||
// => called every Update() depending on authority.
|
||||
void ApplySnapshots(SnapshotBuffer buffer)
|
||||
void ApplySnapshots(ref float remoteTime, SnapshotBuffer buffer)
|
||||
{
|
||||
Debug.Log($"{name} snapshotbuffer={buffer.Count}");
|
||||
|
||||
|
||||
// we buffer snapshots for 'bufferTime'
|
||||
// for example:
|
||||
// * we buffer for 3 x sendInterval = 300ms
|
||||
// * the idea is to wait long enough so we at least have a few
|
||||
// snapshots to interpolate between
|
||||
// * we process anything older 100ms immediately
|
||||
if (buffer.DequeueIfOldEnough(Time.time, bufferTime, out Snapshot snapshot))
|
||||
//
|
||||
// IMPORTANT: snapshot timestamps are _remote_ time
|
||||
// we need to interpolate and calculate buffer lifetimes based on it.
|
||||
// -> we don't know remote's current time
|
||||
// -> NetworkTime.time fluctuates too much, that's no good
|
||||
// -> we _could_ calculate an offset when the first snapshot arrives,
|
||||
// but if there was high latency then we'll always calculate time
|
||||
// with high latency
|
||||
// -> at any given time, we are interpolating from snapshot A to B
|
||||
// => seems like A.timestamp += deltaTime is a good way to do it
|
||||
|
||||
// if remote time wasn't initialized yet
|
||||
if (remoteTime == 0)
|
||||
{
|
||||
// then set it to first snapshot received (if any)
|
||||
if (buffer.Peek(out Snapshot first))
|
||||
{
|
||||
remoteTime = first.timestamp;
|
||||
Debug.LogWarning("remoteTime initialized to " + first.timestamp);
|
||||
}
|
||||
// otherwise wait for the first one
|
||||
else return;
|
||||
}
|
||||
|
||||
// move remote time along deltaTime
|
||||
// TODO consider double for precision over days
|
||||
// (probably need to speed this up based on buffer size later)
|
||||
remoteTime += Time.deltaTime;
|
||||
|
||||
if (buffer.DequeueIfOldEnough(remoteTime, bufferTime, out Snapshot snapshot))
|
||||
ApplySnapshot(snapshot);
|
||||
}
|
||||
|
||||
@ -114,9 +155,8 @@ void Update()
|
||||
// (client with authority will drop the rpc)
|
||||
if (Time.time >= lastServerSendTime + sendInterval)
|
||||
{
|
||||
++serverSendSequence;
|
||||
Snapshot snapshot = new Snapshot(
|
||||
serverSendSequence,
|
||||
Time.time,
|
||||
targetComponent.localPosition,
|
||||
targetComponent.localRotation,
|
||||
targetComponent.localScale
|
||||
@ -135,7 +175,7 @@ void Update()
|
||||
if (clientAuthority && !isLocalPlayer)
|
||||
{
|
||||
// apply snapshots
|
||||
ApplySnapshots(serverBuffer);
|
||||
ApplySnapshots(ref serverRemoteClientTime, serverBuffer);
|
||||
}
|
||||
}
|
||||
// 'else if' because host mode shouldn't send anything to server.
|
||||
@ -148,9 +188,8 @@ void Update()
|
||||
// send to server each 'sendInterval'
|
||||
if (Time.time >= lastClientSendTime + sendInterval)
|
||||
{
|
||||
++clientSendSequence;
|
||||
Snapshot snapshot = new Snapshot(
|
||||
clientSendSequence,
|
||||
Time.time,
|
||||
targetComponent.localPosition,
|
||||
targetComponent.localRotation,
|
||||
targetComponent.localScale
|
||||
@ -165,25 +204,24 @@ void Update()
|
||||
else
|
||||
{
|
||||
// apply snapshots
|
||||
ApplySnapshots(clientBuffer);
|
||||
ApplySnapshots(ref clientRemoteServerTime, clientBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
void Reset()
|
||||
{
|
||||
// disabled objects aren't updated anymore.
|
||||
// so let's clear the buffers.
|
||||
serverBuffer.Clear();
|
||||
clientBuffer.Clear();
|
||||
|
||||
// and reset remoteTime so it's initialized to first snapshot again
|
||||
clientRemoteServerTime = 0;
|
||||
serverRemoteClientTime = 0;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// just in case we received anything while disabled...
|
||||
// it's outdated now anyway. clear the buffers.
|
||||
serverBuffer.Clear();
|
||||
clientBuffer.Clear();
|
||||
}
|
||||
void OnDisable() => Reset();
|
||||
void OnEnable() => Reset();
|
||||
}
|
||||
}
|
||||
|
@ -7,20 +7,30 @@ namespace Mirror.Experimental
|
||||
{
|
||||
internal struct Snapshot
|
||||
{
|
||||
// "When we send snapshot data in packets, we include at the top a 16 bit
|
||||
// sequence number. This sequence number starts at zero and increases
|
||||
// with each packet sent. We use this sequence number on receive to
|
||||
// determine if the snapshot in a packet is newer or older than the most
|
||||
// recent snapshot received. If it’s older then it’s thrown away."
|
||||
internal ushort sequence;
|
||||
// time or sequence are needed to throw away older snapshots.
|
||||
//
|
||||
// glenn fiedler starts with a 16 bit sequence number.
|
||||
// supposedly this is meant as a simplified example.
|
||||
// in the end we need the remote timestamp for accurate interpolation
|
||||
// and buffering over time.
|
||||
//
|
||||
// note: in theory, IF server sends exactly(!) at the same interval then
|
||||
// the 16 bit ushort timestamp would be enough to calculate the
|
||||
// remote time (sequence * sendInterval). but Unity's update is
|
||||
// not guaranteed to run on the exact intervals / do catchup.
|
||||
// => remote timestamp is better for now
|
||||
// TODO consider double for precision over days
|
||||
//
|
||||
// [REMOTE TIME, NOT LOCAL TIME]
|
||||
internal float timestamp;
|
||||
|
||||
internal Vector3 position;
|
||||
internal Quaternion rotation;
|
||||
internal Vector3 scale;
|
||||
|
||||
internal Snapshot(ushort sequence, Vector3 position, Quaternion rotation, Vector3 scale)
|
||||
internal Snapshot(float timestamp, Vector3 position, Quaternion rotation, Vector3 scale)
|
||||
{
|
||||
this.sequence = sequence;
|
||||
this.timestamp = timestamp;
|
||||
this.position = position;
|
||||
this.rotation = rotation;
|
||||
this.scale = scale;
|
||||
|
@ -2,29 +2,11 @@
|
||||
|
||||
namespace Mirror.Experimental
|
||||
{
|
||||
// need to store snapshots with timestamp.
|
||||
// can't put .timestamp into snapshot because we don't want to sync it.
|
||||
internal struct BufferEntry
|
||||
{
|
||||
internal Snapshot snapshot;
|
||||
internal float timestamp;
|
||||
|
||||
internal BufferEntry(Snapshot snapshot, float timestamp)
|
||||
{
|
||||
this.snapshot = snapshot;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
internal class SnapshotBuffer
|
||||
{
|
||||
Queue<BufferEntry> queue = new Queue<BufferEntry>();
|
||||
Queue<Snapshot> queue = new Queue<Snapshot>();
|
||||
|
||||
internal void Enqueue(Snapshot snapshot, float timestamp)
|
||||
{
|
||||
BufferEntry entry = new BufferEntry(snapshot, timestamp);
|
||||
queue.Enqueue(entry);
|
||||
}
|
||||
internal void Enqueue(Snapshot snapshot) => queue.Enqueue(snapshot);
|
||||
|
||||
// dequeue the first snapshot if it's older enough.
|
||||
// for example, currentTime = 100, bufferInterval = 0.3
|
||||
@ -37,11 +19,9 @@ internal bool DequeueIfOldEnough(float currentTime, float bufferInterval, out Sn
|
||||
float thresholdTime = currentTime - bufferInterval;
|
||||
|
||||
// peek and compare time
|
||||
BufferEntry entry = queue.Peek();
|
||||
if (entry.timestamp <= thresholdTime)
|
||||
if (queue.Peek().timestamp <= thresholdTime)
|
||||
{
|
||||
snapshot = entry.snapshot;
|
||||
queue.Dequeue();
|
||||
snapshot = queue.Dequeue();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -49,6 +29,18 @@ internal bool DequeueIfOldEnough(float currentTime, float bufferInterval, out Sn
|
||||
return false;
|
||||
}
|
||||
|
||||
// peek
|
||||
internal bool Peek(out Snapshot snapshot)
|
||||
{
|
||||
if (queue.Count > 0)
|
||||
{
|
||||
snapshot = queue.Peek();
|
||||
return true;
|
||||
}
|
||||
snapshot = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// count queue size independent of time
|
||||
internal int Count => queue.Count;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user