diff --git a/Assets/Mirror/Core/NetworkBehaviour.cs b/Assets/Mirror/Core/NetworkBehaviour.cs
index ca3424bf1..b225d8b70 100644
--- a/Assets/Mirror/Core/NetworkBehaviour.cs
+++ b/Assets/Mirror/Core/NetworkBehaviour.cs
@@ -52,7 +52,12 @@ public abstract class NetworkBehaviour : MonoBehaviour
[Tooltip("Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)")]
[Range(0, 2)]
[HideInInspector] public float syncInterval = 0;
- internal double lastSyncTime;
+
+ // for reliable, we only need the reliable last sync time.
+ // for unreliable, we need the reliable for baselines and unreliable for deltas.
+ // this way we can have syncInterval on unreliable components as well.
+ internal double lastSyncTimeReliable;
+ internal double lastSyncTimeUnreliable;
/// True if this object is on the server and has been spawned.
// This is different from NetworkServer.active, which is true if the
@@ -230,29 +235,38 @@ public void SetSyncVarDirtyBit(ulong dirtyBit)
// true if syncInterval elapsed and any SyncVar or SyncObject is dirty
// OR both bitmasks. != 0 if either was dirty.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool IsDirty() =>
+ public bool IsDirty() => // _RELIABLE
// check bits first. this is basically free.
(syncVarDirtyBits | syncObjectDirtyBits) != 0UL &&
// only check time if bits were dirty. this is more expensive.
- NetworkTime.localTime - lastSyncTime >= syncInterval;
+ NetworkTime.localTime - lastSyncTimeReliable >= syncInterval;
+ // true if syncInterval elapsed and any SyncVar or SyncObject is dirty
+ // OR both bitmasks. != 0 if either was dirty.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- bool IsDirty_BitsOnly() =>
- (syncVarDirtyBits | syncObjectDirtyBits) != 0UL;
+ public bool IsDirtyUnreliable() =>
+ // check bits first. this is basically free.
+ (syncVarDirtyBits | syncObjectDirtyBits) != 0UL &&
+ // only check time if bits were dirty. this is more expensive.
+ // -> for initial we check last reliable sync time.
+ // for delta we check last unreliable sync time.
+ NetworkTime.localTime - lastSyncTimeUnreliable >= syncInterval;
// convenience function to check if a component is dirty for the given
// SyncMethod.
internal bool IsDirtyFor(SyncMethod method)
{
- // reliable: only if dirty bits were set and syncInterval elapsed
+ // reliable: only if dirty bits were set and syncInterval elapsed.
if (method == SyncMethod.Reliable && syncMethod == SyncMethod.Reliable)
{
return IsDirty();
}
- // unreliable: if dirty bits were set (ignored syncInterval for tick aligned SyncVars)
+ // unreliable: only if dirty bits were set and syncInterval elapsed.
+ // note that we chose 'syncInterval' support over 'tick aligned' SyncVars,
+ // because the bandwidth savings are worth it.
else if (method == SyncMethod.Unreliable && syncMethod == SyncMethod.Unreliable)
{
- return IsDirty_BitsOnly();
+ return IsDirtyUnreliable();
}
return false;
@@ -261,9 +275,11 @@ internal bool IsDirtyFor(SyncMethod method)
/// Clears all the dirty bits that were set by SetSyncVarDirtyBit() (formally SetDirtyBits)
// automatically invoked when an update is sent for this object, but can
// be called manually as well.
- public void ClearAllDirtyBits()
+ public void ClearAllDirtyBits(bool clearReliableSyncTime, bool clearUnreliableSyncTime)
{
- lastSyncTime = NetworkTime.localTime;
+ if (clearReliableSyncTime) lastSyncTimeReliable = NetworkTime.localTime;
+ if (clearUnreliableSyncTime) lastSyncTimeUnreliable = NetworkTime.localTime;
+
syncVarDirtyBits = 0L;
syncObjectDirtyBits = 0L;
diff --git a/Assets/Mirror/Core/NetworkIdentity.cs b/Assets/Mirror/Core/NetworkIdentity.cs
index 34215928c..8c57349b7 100644
--- a/Assets/Mirror/Core/NetworkIdentity.cs
+++ b/Assets/Mirror/Core/NetworkIdentity.cs
@@ -1027,7 +1027,7 @@ internal void SerializeServer(bool initialState, SyncMethod method, NetworkWrite
// otherwise if a player joins, we serialize monster,
// and shouldn't clear dirty bits not yet synced to
// other players.
- if (!initialState) comp.ClearAllDirtyBits();
+ if (!initialState) comp.ClearAllDirtyBits(true, false);
}
else if (method == SyncMethod.Unreliable)
{
@@ -1036,11 +1036,9 @@ internal void SerializeServer(bool initialState, SyncMethod method, NetworkWrite
// and shouldn't clear dirty bits not yet synced to
// other players.
//
- // for delta: only clear for full syncs.
- // delta syncs over unreliable may not be delivered,
- // so we can only clear dirty bits for guaranteed to
- // be delivered full syncs.
- if (!initialState && unreliableFullSendIntervalElapsed) comp.ClearAllDirtyBits();
+ // for delta: clear bits depending on if this was a
+ // reliable baseline or a unreliable delta sync.
+ if (!initialState) comp.ClearAllDirtyBits(unreliableFullSendIntervalElapsed, !unreliableFullSendIntervalElapsed);
}
}
}
@@ -1107,16 +1105,14 @@ internal void SerializeClient(SyncMethod method, NetworkWriter writer, bool unre
{
// for reliable: server knows initial. we only send deltas.
// so always clear for deltas.
- comp.ClearAllDirtyBits();
+ comp.ClearAllDirtyBits(true, false);
}
else if (method == SyncMethod.Unreliable)
{
// for unreliable: server knows initial. we only send deltas.
- // but only clear for full syncs.
- // delta syncs over unreliable may not be delivered,
- // so we can only clear dirty bits for guaranteed to
- // be delivered full syncs.
- if (unreliableFullSendIntervalElapsed) comp.ClearAllDirtyBits();
+ // for delta: clear bits depending on if this was a
+ // reliable baseline or a unreliable delta sync.
+ comp.ClearAllDirtyBits(unreliableFullSendIntervalElapsed, !unreliableFullSendIntervalElapsed);
}
}
}
@@ -1284,7 +1280,7 @@ internal void ClearAllComponentsDirtyBits()
{
foreach (NetworkBehaviour comp in NetworkBehaviours)
{
- comp.ClearAllDirtyBits();
+ comp.ClearAllDirtyBits(true, true);
}
}
diff --git a/Assets/Mirror/Core/NetworkServer.cs b/Assets/Mirror/Core/NetworkServer.cs
index 01873285a..4b40f21a1 100644
--- a/Assets/Mirror/Core/NetworkServer.cs
+++ b/Assets/Mirror/Core/NetworkServer.cs
@@ -2021,8 +2021,6 @@ static void BroadcastToConnection(NetworkConnectionToClient connection, bool unr
// 'Unreliable' sync: send Unreliable components over unreliable
// state is 'initial' for reliable baseline, and 'not initial' for unreliable deltas.
- // note that syncInterval is always ignored for unreliable in order to have tick aligned [SyncVars].
- // even if we pass SyncMethod.Reliable, it serializes with initialState=true.
serialization = SerializeForConnection(identity, connection, SyncMethod.Unreliable, unreliableFullSendIntervalElapsed);
if (serialization != null)
{
diff --git a/Assets/Mirror/Editor/NetworkBehaviourInspector.cs b/Assets/Mirror/Editor/NetworkBehaviourInspector.cs
index 90bbd1481..323540661 100644
--- a/Assets/Mirror/Editor/NetworkBehaviourInspector.cs
+++ b/Assets/Mirror/Editor/NetworkBehaviourInspector.cs
@@ -104,9 +104,8 @@ protected void DrawDefaultSyncSettings()
if (syncDirection.enumValueIndex == (int)SyncDirection.ServerToClient)
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncMode"));
- // sync interval: only shown for reliable sync
- if (syncMethod.enumValueIndex == (int)SyncMethod.Reliable)
- EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
+ // sync interval
+ EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
// apply
serializedObject.ApplyModifiedProperties();
diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourDirtyBitsTests.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourDirtyBitsTests.cs
index 86ffb1107..95ae1bbd3 100644
--- a/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourDirtyBitsTests.cs
+++ b/Assets/Mirror/Tests/Editor/NetworkBehaviour/NetworkBehaviourDirtyBitsTests.cs
@@ -88,12 +88,12 @@ public void IsDirty()
// changing a [SyncVar] should set it dirty
++comp.health;
Assert.That(comp.IsDirty(), Is.True);
- comp.ClearAllDirtyBits();
+ comp.ClearAllDirtyBits(true, true);
// changing a SyncCollection should set it dirty
comp.list.Add(42);
Assert.That(comp.IsDirty(), Is.True);
- comp.ClearAllDirtyBits();
+ comp.ClearAllDirtyBits(true, true);
// it should only be dirty after syncInterval elapsed
comp.syncInterval = float.MaxValue;
@@ -115,7 +115,7 @@ public void ClearAllDirtyBitsClearsSyncVarDirtyBits()
Assert.That(emptyBehaviour.IsDirty(), Is.True);
// clear it
- emptyBehaviour.ClearAllDirtyBits();
+ emptyBehaviour.ClearAllDirtyBits(true, true);
Assert.That(emptyBehaviour.IsDirty(), Is.False);
}
@@ -134,7 +134,7 @@ public void ClearAllDirtyBitsClearsSyncObjectsDirtyBits()
Assert.That(comp.IsDirty, Is.True);
// clear bits should clear synclist bits too
- comp.ClearAllDirtyBits();
+ comp.ClearAllDirtyBits(true, true);
Assert.That(comp.IsDirty, Is.False);
}
diff --git a/Assets/Mirror/Tests/Editor/SyncVars/SyncVarAttributeTest.cs b/Assets/Mirror/Tests/Editor/SyncVars/SyncVarAttributeTest.cs
index 77db851f1..5f8a5f639 100644
--- a/Assets/Mirror/Tests/Editor/SyncVars/SyncVarAttributeTest.cs
+++ b/Assets/Mirror/Tests/Editor/SyncVars/SyncVarAttributeTest.cs
@@ -115,7 +115,7 @@ public void TestSettingStruct()
player.guild = myGuild;
Assert.That(player.IsDirty(), "Setting struct should mark object as dirty");
- player.ClearAllDirtyBits();
+ player.ClearAllDirtyBits(true, true);
Assert.That(player.IsDirty(), Is.False, "ClearAllDirtyBits() should clear dirty flag");
// clearing the guild should set dirty bit too
@@ -127,7 +127,7 @@ public void TestSettingStruct()
public void TestSyncIntervalAndClearAllComponents()
{
CreateNetworked(out _, out _, out MockPlayer player);
- player.lastSyncTime = NetworkTime.localTime;
+ player.lastSyncTimeReliable = NetworkTime.localTime;
// synchronize immediately
player.syncInterval = 1f;
@@ -143,7 +143,7 @@ public void TestSyncIntervalAndClearAllComponents()
player.netIdentity.ClearAllComponentsDirtyBits();
// set lastSyncTime far enough back to be ready for syncing
- player.lastSyncTime = NetworkTime.localTime - player.syncInterval;
+ player.lastSyncTimeReliable = NetworkTime.localTime - player.syncInterval;
// should be dirty now
Assert.That(player.IsDirty(), Is.False, "Sync interval met, should still not be dirty");