fix mrg grid issue; fix unreliable sending just because baseline changed

This commit is contained in:
miwarnec 2024-10-30 12:13:23 +01:00
parent 07956819c2
commit bc6b1d56f5

View File

@ -68,6 +68,20 @@ public class NetworkTransformHybrid2022 : NetworkBehaviour
[Tooltip("When true, changes are not sent unless greater than sensitivity values below.")] [Tooltip("When true, changes are not sent unless greater than sensitivity values below.")]
public bool onlySyncOnChange = true; public bool onlySyncOnChange = true;
// change detection: we need to do this carefully in order to get it right.
//
// DONT just check changes in UpdateBaseline(). this would introduce MrG's grid issue:
// server start in A1, reliable baseline sent to client
// server moves to A2, unreliabe delta sent to client
// server moves to A1, nothing is sent to client becuase last baseline position == position
// => client wouldn't know we moved back to A1
//
// INSTEAD: every update() check for changes since baseline:
// UpdateDelta() keeps sending only if changed since _baseline_
// UpdateBaseline() resends if there was any change in the period since last baseline.
// => this avoids the A1->A2->A1 grid issue above
bool changedSinceBaseline = false;
// sensitivity is for changed-detection, // sensitivity is for changed-detection,
// this is != precision, which is for quantization and delta compression. // this is != precision, which is for quantization and delta compression.
[Header("Sensitivity"), Tooltip("Sensitivity of changes needed before an updated state is sent over the network")] [Header("Sensitivity"), Tooltip("Sensitivity of changes needed before an updated state is sent over the network")]
@ -460,9 +474,11 @@ protected virtual void OnServerToClientDeltaSync(byte baselineTick, Vector3 posi
} }
// update server /////////////////////////////////////////////////////// // update server ///////////////////////////////////////////////////////
bool baselineDirty = true;
void UpdateServerBaseline(double localTime) void UpdateServerBaseline(double localTime)
{ {
// only sync on change: only resend baseline if changed since last.
if (onlySyncOnChange && !changedSinceBaseline) return;
// send a reliable baseline every 1 Hz // send a reliable baseline every 1 Hz
if (localTime >= lastBaselineTime + baselineInterval) if (localTime >= lastBaselineTime + baselineInterval)
{ {
@ -472,15 +488,6 @@ void UpdateServerBaseline(double localTime)
// TransformSnapshot snapshot = ConstructSnapshot(); // TransformSnapshot snapshot = ConstructSnapshot();
target.GetLocalPositionAndRotation(out Vector3 position, out Quaternion rotation); target.GetLocalPositionAndRotation(out Vector3 position, out Quaternion rotation);
// only send a new reliable baseline if changed since last time
// check if changed (unless that feature is disabled).
// baseline is guaranteed to be delivered over reliable.
// here is the only place where we can check for changes.
if (!onlySyncOnChange || Changed(position, rotation)) //snapshot))
{
// reliable just changed. keep sending deltas until it's unchanged again.
baselineDirty = true;
// save bandwidth by only transmitting what is needed. // save bandwidth by only transmitting what is needed.
// -> ArraySegment with random data is slower since byte[] copying // -> ArraySegment with random data is slower since byte[] copying
// -> Vector3? and Quaternion? nullables takes more bandwidth // -> Vector3? and Quaternion? nullables takes more bandwidth
@ -512,6 +519,9 @@ void UpdateServerBaseline(double localTime)
lastSerializedBaselinePosition = position; lastSerializedBaselinePosition = position;
lastSerializedBaselineRotation = rotation; lastSerializedBaselineRotation = rotation;
// baseline was just sent after a change. reset change detection.
changedSinceBaseline = false;
// perf. & bandwidth optimization: // perf. & bandwidth optimization:
// send a delta right after baseline to avoid potential head of // send a delta right after baseline to avoid potential head of
// line blocking, or skip the delta whenever we sent reliable? // line blocking, or skip the delta whenever we sent reliable?
@ -523,9 +533,6 @@ void UpdateServerBaseline(double localTime)
// in that case, skip next delta by simply resetting last delta sync's time. // in that case, skip next delta by simply resetting last delta sync's time.
if (baselineIsDelta) lastDeltaTime = localTime; if (baselineIsDelta) lastDeltaTime = localTime;
} }
// indicate that we should stop sending deltas now
else baselineDirty = false;
}
} }
void UpdateServerDelta(double localTime) void UpdateServerDelta(double localTime)
@ -561,17 +568,27 @@ void UpdateServerDelta(double localTime)
// here by checking IsClientWithAuthority. // here by checking IsClientWithAuthority.
// TODO send same time that NetworkServer sends time snapshot? // TODO send same time that NetworkServer sends time snapshot?
// only sync on change:
// unreliable isn't guaranteed to be delivered so this depends on reliable baseline.
// if baseline is dirty, send unreliables every sendInterval until baseline is not dirty anymore.
if (onlySyncOnChange && !baselineDirty) return;
if (localTime >= lastDeltaTime + sendInterval) // CUSTOM CHANGE: allow custom sendRate + sendInterval again if (localTime >= lastDeltaTime + sendInterval) // CUSTOM CHANGE: allow custom sendRate + sendInterval again
{ {
// perf: get position/rotation directly. TransformSnapshot is too expensive. // perf: get position/rotation directly. TransformSnapshot is too expensive.
// TransformSnapshot snapshot = ConstructSnapshot(); // TransformSnapshot snapshot = ConstructSnapshot();
target.GetLocalPositionAndRotation(out Vector3 position, out Quaternion rotation); target.GetLocalPositionAndRotation(out Vector3 position, out Quaternion rotation);
// look for changes every unreliable sendInterval!
// every reliable interval isn't enough, this would cause MrG's grid issue:
// server start in A1, reliable baseline sent to client
// server moves to A2, unreliabe delta sent to client
// server moves to A1, nothing is sent to client becuase last baseline position == position
// => client wouldn't know we moved back to A1
// every update works, but it's unnecessary overhead since sends only happen every sendInterval
// every unreliable sendInterval is the perfect place to look for changes.
if (onlySyncOnChange && Changed(position, rotation))
changedSinceBaseline = true;
// only sync on change:
// unreliable isn't guaranteed to be delivered so this depends on reliable baseline.
if (onlySyncOnChange && !changedSinceBaseline) return;
// save bandwidth by only transmitting what is needed. // save bandwidth by only transmitting what is needed.
// -> ArraySegment with random data is slower since byte[] copying // -> ArraySegment with random data is slower since byte[] copying
// -> Vector3? and Quaternion? nullables takes more bandwidth // -> Vector3? and Quaternion? nullables takes more bandwidth
@ -662,6 +679,9 @@ void UpdateServer()
// update client /////////////////////////////////////////////////////// // update client ///////////////////////////////////////////////////////
void UpdateClientBaseline(double localTime) void UpdateClientBaseline(double localTime)
{ {
// only sync on change: only resend baseline if changed since last.
if (onlySyncOnChange && !changedSinceBaseline) return;
// send a reliable baseline every 1 Hz // send a reliable baseline every 1 Hz
if (localTime >= lastBaselineTime + baselineInterval) if (localTime >= lastBaselineTime + baselineInterval)
{ {
@ -669,15 +689,6 @@ void UpdateClientBaseline(double localTime)
// TransformSnapshot snapshot = ConstructSnapshot(); // TransformSnapshot snapshot = ConstructSnapshot();
target.GetLocalPositionAndRotation(out Vector3 position, out Quaternion rotation); target.GetLocalPositionAndRotation(out Vector3 position, out Quaternion rotation);
// only send a new reliable baseline if changed since last time
// check if changed (unless that feature is disabled).
// baseline is guaranteed to be delivered over reliable.
// here is the only place where we can check for changes.
if (!onlySyncOnChange || Changed(position, rotation)) //snapshot))
{
// reliable just changed. keep sending deltas until it's unchanged again.
baselineDirty = true;
// save bandwidth by only transmitting what is needed. // save bandwidth by only transmitting what is needed.
// -> ArraySegment with random data is slower since byte[] copying // -> ArraySegment with random data is slower since byte[] copying
// -> Vector3? and Quaternion? nullables takes more bandwidth // -> Vector3? and Quaternion? nullables takes more bandwidth
@ -709,6 +720,9 @@ void UpdateClientBaseline(double localTime)
lastSerializedBaselinePosition = position; lastSerializedBaselinePosition = position;
lastSerializedBaselineRotation = rotation; lastSerializedBaselineRotation = rotation;
// baseline was just sent after a change. reset change detection.
changedSinceBaseline = false;
// perf. & bandwidth optimization: // perf. & bandwidth optimization:
// send a delta right after baseline to avoid potential head of // send a delta right after baseline to avoid potential head of
// line blocking, or skip the delta whenever we sent reliable? // line blocking, or skip the delta whenever we sent reliable?
@ -720,18 +734,10 @@ void UpdateClientBaseline(double localTime)
// in that case, skip next delta by simply resetting last delta sync's time. // in that case, skip next delta by simply resetting last delta sync's time.
if (baselineIsDelta) lastDeltaTime = localTime; if (baselineIsDelta) lastDeltaTime = localTime;
} }
// indicate that we should stop sending deltas now
else baselineDirty = false;
}
} }
void UpdateClientDelta(double localTime) void UpdateClientDelta(double localTime)
{ {
// only sync on change:
// unreliable isn't guaranteed to be delivered so this depends on reliable baseline.
// if baseline is dirty, send unreliables every sendInterval until baseline is not dirty anymore.
if (onlySyncOnChange && !baselineDirty) return;
// send to server each 'sendInterval' // send to server each 'sendInterval'
// NetworkTime.localTime for double precision until Unity has it too // NetworkTime.localTime for double precision until Unity has it too
// //
@ -758,6 +764,22 @@ void UpdateClientDelta(double localTime)
// TransformSnapshot snapshot = ConstructSnapshot(); // TransformSnapshot snapshot = ConstructSnapshot();
target.GetLocalPositionAndRotation(out Vector3 position, out Quaternion rotation); target.GetLocalPositionAndRotation(out Vector3 position, out Quaternion rotation);
// look for changes every unreliable sendInterval!
//
// every reliable interval isn't enough, this would cause MrG's grid issue:
// client start in A1, reliable baseline sent to server
// client moves to A2, unreliabe delta sent to server
// client moves to A1, nothing is sent to server becuase last baseline position == position
// => server wouldn't know we moved back to A1
// every update works, but it's unnecessary overhead since sends only happen every sendInterval
// every unreliable sendInterval is the perfect place to look for changes.
if (onlySyncOnChange && Changed(position, rotation))
changedSinceBaseline = true;
// only sync on change:
// unreliable isn't guaranteed to be delivered so this depends on reliable baseline.
if (onlySyncOnChange && !changedSinceBaseline) return;
// save bandwidth by only transmitting what is needed. // save bandwidth by only transmitting what is needed.
// -> ArraySegment with random data is slower since byte[] copying // -> ArraySegment with random data is slower since byte[] copying
// -> Vector3? and Quaternion? nullables takes more bandwidth // -> Vector3? and Quaternion? nullables takes more bandwidth
@ -976,7 +998,7 @@ public virtual void Reset()
lastSerializedBaselineTick = 0; lastSerializedBaselineTick = 0;
lastSerializedBaselinePosition = Vector3.zero; lastSerializedBaselinePosition = Vector3.zero;
lastSerializedBaselineRotation = Quaternion.identity; lastSerializedBaselineRotation = Quaternion.identity;
baselineDirty = true; changedSinceBaseline = false;
lastDeserializedBaselineTick = 0; lastDeserializedBaselineTick = 0;
lastDeserializedBaselinePosition = Vector3.zero; lastDeserializedBaselinePosition = Vector3.zero;