Skip to content

Commit 710a63a

Browse files
fix: NetworkVariable clearing dirty flags after OnNetworkSpawn (#3779)
* fix This includes a fix for the forum reported issue where spawning with ownership in a distributed authority topology where the owner is not the spawn authority, the owner/authority sets a NetworkVariable during OnNetworkSpawn, then the changes made would not be synchronized. * test This adds an integration test to validate the fix for this PR. * update removing an IDE injected namespace that is never usedd. * fix: second part This migrates the NetworkList specific script from NetworkBehaviour.InternalOnNetworkSpawn into NetworkList itself. It also provides (currently) two internal virtual methods, NetworkVariableBase.OnSpawned and NetworkVariableBase.OnPreDespawn, to provide a means for NetworkList to handle cleaning its dirty state up after the instance with write permissions has finished running through the spawn stages (pre-spawn, spawning, post-spawn, gets invoked). * test Adding some debug information to NetworkShowHideTests. Replacing m_ServerNetworkManager in OwnerModifiedTests with GetAuthorityNetworkManager() to become CMB service testing compatible. * test - fix Fixing the race condition test instability when running OwnershipPermissionsTests against CMB service until the order in which a message is received will reflect the order in which it is sent relative to messages received prior to and after the given message. * style further clarifying the change made here. * test Some more instability related fixes.
1 parent 2ea2736 commit 710a63a

File tree

9 files changed

+225
-76
lines changed

9 files changed

+225
-76
lines changed

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -797,17 +797,6 @@ internal void InternalOnNetworkSpawn()
797797
{
798798
Debug.LogException(e);
799799
}
800-
801-
// Initialize again in case the user's OnNetworkSpawn changed something
802-
InitializeVariables();
803-
804-
if (m_NetworkObject.HasAuthority)
805-
{
806-
// Since we just spawned the object and since user code might have modified their NetworkVariable, esp.
807-
// NetworkList, we need to mark the object as free of updates.
808-
// This should happen for all objects on the machine triggering the spawn.
809-
PostNetworkVariableWrite(true);
810-
}
811800
}
812801

813802
internal void NetworkPostSpawn()
@@ -821,6 +810,13 @@ internal void NetworkPostSpawn()
821810
{
822811
Debug.LogException(e);
823812
}
813+
814+
// Let each NetworkVariableBase derived instance know that
815+
// all spawn related methods have been invoked.
816+
for (int i = 0; i < NetworkVariableFields.Count; i++)
817+
{
818+
NetworkVariableFields[i].OnSpawned();
819+
}
824820
}
825821

826822
internal void NetworkSessionSynchronized()
@@ -858,6 +854,13 @@ internal void InternalOnNetworkPreDespawn()
858854
{
859855
Debug.LogException(e);
860856
}
857+
858+
// Let each NetworkVariableBase derived instance know that
859+
// all spawn related methods have been invoked.
860+
for (int i = 0; i < NetworkVariableFields.Count; i++)
861+
{
862+
NetworkVariableFields[i].OnPreDespawn();
863+
}
861864
}
862865

863866
internal void InternalOnNetworkDespawn()

com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ public NetworkList(IEnumerable<T> values = default,
5858
Dispose();
5959
}
6060

61+
internal override void OnSpawned()
62+
{
63+
// If we are dirty and have write permissions by the time the NetworkObject
64+
// is finished spawning (same frame), then go ahead and reset the dirty related
65+
// properties for NetworkList in the event user script has made changes when
66+
// spawning to prevent duplicate entries.
67+
if (IsDirty() && CanSend())
68+
{
69+
UpdateLastSentTime();
70+
ResetDirty();
71+
SetDirty(false);
72+
}
73+
base.OnSpawned();
74+
}
75+
6176
/// <inheritdoc cref="NetworkVariable{T}.ResetDirty"/>
6277
public override void ResetDirty()
6378
{

com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,28 @@ public void Initialize(NetworkBehaviour networkBehaviour)
140140
}
141141
}
142142

143+
/// TODO-API: After further vetting and alignment on these, we might make them part of the public API.
144+
/// Could actually be like an interface that gets automatically registered for these kinds of notifications
145+
/// without having to be a NetworkBehaviour.
146+
#region OnSpawn and OnPreDespawn (ETC)
147+
148+
/// <summary>
149+
/// Invoked after the associated <see cref="NetworkBehaviour.OnNetworkPostSpawn"/> has been invoked.
150+
/// </summary>
151+
internal virtual void OnSpawned()
152+
{
153+
154+
}
155+
156+
/// <summary>
157+
/// Invoked after the associated <see cref="NetworkBehaviour.OnNetworkPreDespawn"/> has been invoked.
158+
/// </summary>
159+
internal virtual void OnPreDespawn()
160+
{
161+
162+
}
163+
#endregion
164+
143165
/// <summary>
144166
/// Deinitialize is invoked when a NetworkObject is despawned.
145167
/// This allows for a recyled NetworkObject (in-scene or pooled)

com.unity.netcode.gameobjects/Tests/Runtime/AttachableBehaviourTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private bool ResetAllStates()
118118
else
119119
{
120120
var attachable = networkManager.SpawnManager.SpawnedObjects[currentAttachableRoot.NetworkObjectId].GetComponentInChildren<TestAttachable>();
121-
attachable.ResetStates();
121+
attachable?.ResetStates();
122122
}
123123

124124
// Target
@@ -129,7 +129,7 @@ private bool ResetAllStates()
129129
else
130130
{
131131
var node = networkManager.SpawnManager.SpawnedObjects[m_TargetInstance.NetworkObjectId].GetComponentInChildren<TestNode>();
132-
node.ResetStates();
132+
node?.ResetStates();
133133
}
134134

135135
// Target B
@@ -140,7 +140,7 @@ private bool ResetAllStates()
140140
else
141141
{
142142
var node = networkManager.SpawnManager.SpawnedObjects[m_TargetInstanceB.NetworkObjectId].GetComponentInChildren<TestNode>();
143-
node.ResetStates();
143+
node?.ResetStates();
144144
}
145145
}
146146
return m_ErrorLog.Length == 0;

com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/OwnershipPermissionsTests.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private bool WaitForOneClientToBeApproved(OwnershipPermissionsTestHelper[] clien
6767
{
6868
approvedClients++;
6969
}
70-
else if (helper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.RequestInProgress)
70+
else if (helper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.RequestInProgress || helper.OwnershipRequestResponseStatus == NetworkObject.OwnershipRequestResponseStatus.Denied)
7171
{
7272
requestInProgressClients++;
7373
}
@@ -279,8 +279,16 @@ public IEnumerator ValidateOwnershipPermissionsTest()
279279
var fourthInstance = fourthClient.SpawnManager.SpawnedObjects[networkObjectId];
280280
var fourthInstanceHelper = fourthInstance.GetComponent<OwnershipPermissionsTestHelper>();
281281

282+
// Mock race condition scenario where the second instance request arrives before the rest of the requests.
283+
// This could be on the same frame all requests are received or where they stagger over several frames.
284+
// Until we resolve the CMB service issue where the order in which messages are received does not always
285+
// reflect the order in which they are forwarded to their destination (relative to other messages received from
286+
// other clients).
287+
firstInstanceHelper.OnlyAllowTargetClientId = true;
288+
firstInstanceHelper.ClientToAllowOwnership = secondClient.LocalClientId;
289+
282290
// Send out a request from three clients at the same time
283-
// The first one sent (and received for this test) gets ownership
291+
// The first one received gets ownership
284292
requestStatus = secondInstance.RequestOwnership();
285293
Assert.True(requestStatus == NetworkObject.OwnershipRequestStatus.RequestSent, $"Client-{secondClient.LocalClientId} was unable to send a request for ownership because: {requestStatus}!");
286294
requestStatus = thirdInstance.RequestOwnership();
@@ -291,7 +299,7 @@ public IEnumerator ValidateOwnershipPermissionsTest()
291299
// The 2nd and 3rd client should be denied and the 4th client should be approved
292300
yield return WaitForConditionOrTimeOut(() => WaitForOneClientToBeApproved(new[] { secondInstanceHelper, thirdInstanceHelper, fourthInstanceHelper }));
293301
AssertOnTimeout("[Targeted Owner] A client received an incorrect response. " +
294-
$"Expected one client to have {NetworkObject.OwnershipRequestResponseStatus.Approved} and the others to have {NetworkObject.OwnershipRequestResponseStatus.RequestInProgress}!."
302+
$"Expected one client to have {NetworkObject.OwnershipRequestResponseStatus.Approved} and the others to have {NetworkObject.OwnershipRequestResponseStatus.RequestInProgress} or {NetworkObject.OwnershipRequestResponseStatus.Denied}!."
295303
+ $"\n Client-{fourthClient.LocalClientId}: has {fourthInstanceHelper.OwnershipRequestResponseStatus}!"
296304
+ $"\n Client-{thirdClient.LocalClientId}: has {thirdInstanceHelper.OwnershipRequestResponseStatus}!"
297305
+ $"\n Client-{secondClient.LocalClientId}: has {secondInstanceHelper.OwnershipRequestResponseStatus}!");
@@ -305,6 +313,9 @@ public IEnumerator ValidateOwnershipPermissionsTest()
305313
yield return WaitForConditionOrTimeOut(ValidatePermissionsOnAllClients);
306314
AssertOnTimeout($"[Multiple request race condition][Permissions Mismatch] {secondInstance.name}");
307315

316+
// Reset this value once this part of the test is complete
317+
firstInstanceHelper.OnlyAllowTargetClientId = false;
318+
308319
///////////////////////////////////////////////
309320
// Test for targeted ownership request:
310321
///////////////////////////////////////////////

com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public static NetworkObject GetNetworkObjectById(ulong networkObjectId)
3131

3232
public override void OnNetworkSpawn()
3333
{
34+
MyNetworkVariable.OnValueChanged += Changed;
35+
MyOwnerReadNetworkVariable.OnValueChanged += OwnerReadChanged;
3436
if (NetworkManager.LocalClientId == ClientIdToTarget)
3537
{
3638
ClientTargetedNetworkObjects.Add(this);
@@ -42,7 +44,7 @@ public override void OnNetworkSpawn()
4244
}
4345
else
4446
{
45-
Debug.Assert(MyListSetOnSpawn.Count == 1);
47+
Debug.Assert(MyListSetOnSpawn.Count == 1, $"[Session Authority][Client-{NetworkManager.LocalClientId}][{name}] Count = {MyListSetOnSpawn.Count} when expecting only 1!");
4648
Debug.Assert(MyListSetOnSpawn[0] == 45);
4749
}
4850

@@ -53,34 +55,23 @@ public override void OnNetworkSpawn()
5355

5456
public override void OnNetworkDespawn()
5557
{
58+
MyNetworkVariable.OnValueChanged -= Changed;
59+
MyOwnerReadNetworkVariable.OnValueChanged -= OwnerReadChanged;
5660
if (ClientTargetedNetworkObjects.Contains(this))
5761
{
5862
ClientTargetedNetworkObjects.Remove(this);
5963
}
6064
base.OnNetworkDespawn();
6165
}
6266

63-
public NetworkVariable<int> MyNetworkVariable;
64-
public NetworkList<int> MyListSetOnSpawn;
65-
public NetworkVariable<int> MyOwnerReadNetworkVariable;
66-
public NetworkList<int> MyList;
67+
public NetworkVariable<int> MyNetworkVariable = new NetworkVariable<int>();
68+
public NetworkList<int> MyListSetOnSpawn = new NetworkList<int>();
69+
public NetworkVariable<int> MyOwnerReadNetworkVariable = new NetworkVariable<int>(readPerm: NetworkVariableReadPermission.Owner);
70+
public NetworkList<int> MyList = new NetworkList<int>();
6771
public static NetworkManager NetworkManagerOfInterest;
6872

6973
internal static int GainOwnershipCount = 0;
7074

71-
private void Awake()
72-
{
73-
// Debug.Log($"Awake {NetworkManager.LocalClientId}");
74-
MyNetworkVariable = new NetworkVariable<int>();
75-
MyNetworkVariable.OnValueChanged += Changed;
76-
77-
MyListSetOnSpawn = new NetworkList<int>();
78-
MyList = new NetworkList<int>();
79-
80-
MyOwnerReadNetworkVariable = new NetworkVariable<int>(readPerm: NetworkVariableReadPermission.Owner);
81-
MyOwnerReadNetworkVariable.OnValueChanged += OwnerReadChanged;
82-
}
83-
8475
public override void OnGainedOwnership()
8576
{
8677
GainOwnershipCount++;
@@ -532,6 +523,7 @@ public IEnumerator NetworkHideChangeOwnership()
532523
AssertOnTimeout($"NetworkObject is still visible to Client-{m_ClientWithoutVisibility} or other clients think it is still visible to Client-{m_ClientWithoutVisibility}:\n {m_ErrorLog}");
533524

534525
yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientTargetedNetworkObjects.Count == 0);
526+
AssertOnTimeout($"Timed out waiting for ShowHideObject.ClientTargetedNetworkObjects to have a count of 0 but was {ShowHideObject.ClientTargetedNetworkObjects.Count}!");
535527

536528
foreach (var client in m_ClientNetworkManagers)
537529
{
@@ -564,8 +556,31 @@ public IEnumerator NetworkHideChangeOwnership()
564556
}
565557

566558
yield return WaitForConditionOrTimeOut(() => ShowHideObject.ClientTargetedNetworkObjects.Count == 1);
559+
AssertOnTimeout($"Timed out waiting for ShowHideObject.ClientTargetedNetworkObjects to have a count of 1 but was {ShowHideObject.ClientTargetedNetworkObjects.Count}!");
560+
561+
m_ClientIdToCheck = firstClient.LocalClientId;
562+
yield return WaitForConditionOrTimeOut(CheckIsClientOwner);
563+
AssertOnTimeout($"Timed out waiting for client owner check!");
564+
}
567565

568-
Assert.True(ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId == firstClient.LocalClientId);
566+
private ulong m_ClientIdToCheck;
567+
private bool CheckIsClientOwner(StringBuilder errorLog)
568+
{
569+
if (ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId != m_ClientIdToCheck)
570+
{
571+
errorLog.AppendLine($"[CheckIsClientOwner][Index: 0][{ShowHideObject.ClientTargetedNetworkObjects[0].name}] OwnerClientId is {ShowHideObject.ClientTargetedNetworkObjects[0].OwnerClientId} when it was expected to be {m_ClientIdToCheck}!");
572+
if (ShowHideObject.ClientTargetedNetworkObjects.Count > 1)
573+
{
574+
for (int i = 1; i < ShowHideObject.ClientTargetedNetworkObjects.Count; i++)
575+
{
576+
var target = ShowHideObject.ClientTargetedNetworkObjects[i];
577+
errorLog.AppendLine($"[CheckIsClientOwner][Index: {i}][{target.name}] OwnerClientId is {target.OwnerClientId}.");
578+
}
579+
}
580+
return false;
581+
}
582+
583+
return true;
569584
}
570585

571586
private bool AllClientsSpawnedObject1()

0 commit comments

Comments
 (0)