syncInterval support

This commit is contained in:
mischa 2024-09-03 22:18:05 +02:00
parent 9688422d52
commit 9a9c8a6fdf
6 changed files with 44 additions and 35 deletions

View File

@ -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;
/// <summary>True if this object is on the server and has been spawned.</summary>
// 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)
/// <summary>Clears all the dirty bits that were set by SetSyncVarDirtyBit() (formally SetDirtyBits)</summary>
// 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;

View File

@ -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);
}
}

View File

@ -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)
{

View File

@ -104,8 +104,7 @@ 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)
// sync interval
EditorGUILayout.PropertyField(serializedObject.FindProperty("syncInterval"));
// apply

View File

@ -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);
}

View File

@ -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");