Skip to content

Commit bff71a5

Browse files
authored
fix: OnNetworkSpawn timing (#1299)
* fix: OnNetworkSpawn timing Fixes MTT-1431 and MTT-1432 - ensures that OnNetworkSpawn is called before RPCs sent during OnNetworkSpawn on the client, and that OnValueChanged is not executed for initialization of values. * - Added comment per review feedback - standards fix
1 parent f487de2 commit bff71a5

File tree

7 files changed

+117
-17
lines changed

7 files changed

+117
-17
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
119119
/// <inheritdoc />
120120
public override void ReadField(FastBufferReader reader)
121121
{
122-
ReadDelta(reader, false);
122+
reader.ReadValueSafe(out m_InternalValue);
123123
}
124124

125125
/// <inheritdoc />

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,10 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong
475475
networkObject.ApplyNetworkParenting();
476476
NetworkObject.CheckOrphanChildren();
477477

478+
networkObject.InvokeBehaviourNetworkSpawn();
479+
480+
// This must happen after InvokeBehaviourNetworkSpawn, otherwise ClientRPCs and other messages can be
481+
// processed before the object is fully spawned. This must be the last thing done in the spawn process.
478482
if (m_Triggers.ContainsKey(networkId))
479483
{
480484
var triggerInfo = m_Triggers[networkId];
@@ -487,8 +491,6 @@ private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong
487491
triggerInfo.TriggerData.Dispose();
488492
m_Triggers.Remove(networkId);
489493
}
490-
491-
networkObject.InvokeBehaviourNetworkSpawn();
492494
}
493495

494496
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)

testproject/Assets/Tests/Runtime/MessageOrdering.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public IEnumerator SetUp()
1919
Support.SpawnRpcDespawn.ClientUpdateCount = 0;
2020
Support.SpawnRpcDespawn.ServerUpdateCount = 0;
2121
Support.SpawnRpcDespawn.ClientNetworkSpawnRpcCalled = false;
22+
Support.SpawnRpcDespawn.ExecuteClientRpc = false;
2223
yield break;
2324
}
2425

@@ -34,6 +35,7 @@ public IEnumerator Teardown()
3435
Support.SpawnRpcDespawn.ClientUpdateCount = 0;
3536
Support.SpawnRpcDespawn.ServerUpdateCount = 0;
3637
Support.SpawnRpcDespawn.ClientNetworkSpawnRpcCalled = false;
38+
Support.SpawnRpcDespawn.ExecuteClientRpc = false;
3739
}
3840
yield break;
3941
}
@@ -175,6 +177,7 @@ public IEnumerator SpawnRpcDespawn()
175177
[UnityTest]
176178
public IEnumerator RpcOnNetworkSpawn()
177179
{
180+
Support.SpawnRpcDespawn.ExecuteClientRpc = true;
178181
// Must be 1 for this test.
179182
const int numClients = 1;
180183
Assert.True(MultiInstanceHelpers.Create(numClients, out NetworkManager server, out NetworkManager[] clients));

testproject/Assets/Tests/Runtime/NetworkVariableInitializationOnNetworkSpawnTest.cs

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public IEnumerator SetUp()
1818
// Make sure these static values are reset
1919
NetworkVariableInitOnNetworkSpawn.NetworkSpawnCalledOnClient = false;
2020
NetworkVariableInitOnNetworkSpawn.NetworkSpawnCalledOnServer = false;
21+
NetworkVariableInitOnNetworkSpawn.OnValueChangedCalledOnClient = false;
2122
yield break;
2223
}
2324

@@ -33,12 +34,11 @@ public IEnumerator Teardown()
3334
}
3435
NetworkVariableInitOnNetworkSpawn.NetworkSpawnCalledOnClient = false;
3536
NetworkVariableInitOnNetworkSpawn.NetworkSpawnCalledOnServer = false;
37+
NetworkVariableInitOnNetworkSpawn.OnValueChangedCalledOnClient = false;
3638
yield break;
3739
}
3840

39-
[UnityTest]
40-
[Description("When a network variable is initialized in OnNetworkSpawn on the server, the spawned object's NetworkVariable on the client is initialized with the same value.")]
41-
public IEnumerator WhenANetworkVariableIsInitializedInOnNetworkSpawnOnTheServer_TheSpawnedObjectsNetworkVariableOnTheClientIsInitializedWithTheSameValue()
41+
private IEnumerator RunTest()
4242
{
4343
const int numClients = 1;
4444
Assert.True(MultiInstanceHelpers.Create(numClients, out NetworkManager server, out NetworkManager[] clients));
@@ -95,8 +95,88 @@ public IEnumerator WhenANetworkVariableIsInitializedInOnNetworkSpawnOnTheServer_
9595
var nextFrameNumber = Time.frameCount + 1;
9696
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
9797
}
98+
}
99+
100+
[UnityTest]
101+
[Description("When a network variable is initialized in OnNetworkSpawn on the server, the spawned object's NetworkVariable on the client is initialized with the same value.")]
102+
public IEnumerator WhenANetworkVariableIsInitializedInOnNetworkSpawnOnTheServer_TheSpawnedObjectsNetworkVariableOnTheClientIsInitializedWithTheSameValue()
103+
{
104+
yield return RunTest();
98105
Assert.IsTrue(NetworkVariableInitOnNetworkSpawn.NetworkSpawnCalledOnServer);
99106
Assert.IsTrue(NetworkVariableInitOnNetworkSpawn.NetworkSpawnCalledOnClient);
100107
}
108+
109+
[UnityTest]
110+
[Description("When a network variable is initialized in OnNetworkSpawn on the server, OnValueChanged is not called")]
111+
public IEnumerator WhenANetworkVariableIsInitializedInOnNetworkSpawnOnTheServer_OnValueChangedIsNotCalled()
112+
{
113+
yield return RunTest();
114+
Assert.IsFalse(NetworkVariableInitOnNetworkSpawn.OnValueChangedCalledOnClient);
115+
}
116+
117+
[UnityTest]
118+
[Description("When a network variable is changed just after OnNetworkSpawn on the server, OnValueChanged is called after OnNetworkSpawn")]
119+
public IEnumerator WhenANetworkVariableIsInitializedJustAfterOnNetworkSpawnOnTheServer_OnValueChangedIsCalledAfterOnNetworkSpawn()
120+
{
121+
const int numClients = 1;
122+
Assert.True(MultiInstanceHelpers.Create(numClients, out NetworkManager server, out NetworkManager[] clients));
123+
m_Prefab = new GameObject("Object");
124+
var networkObject = m_Prefab.AddComponent<NetworkObject>();
125+
m_Prefab.AddComponent<NetworkVariableInitOnNetworkSpawn>();
126+
127+
// Make it a prefab
128+
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject);
129+
130+
var validNetworkPrefab = new NetworkPrefab();
131+
validNetworkPrefab.Prefab = m_Prefab;
132+
server.NetworkConfig.NetworkPrefabs.Add(validNetworkPrefab);
133+
foreach (var client in clients)
134+
{
135+
client.NetworkConfig.NetworkPrefabs.Add(validNetworkPrefab);
136+
}
137+
138+
// Start the instances
139+
if (!MultiInstanceHelpers.Start(true, server, clients))
140+
{
141+
Debug.LogError("Failed to start instances");
142+
Assert.Fail("Failed to start instances");
143+
}
144+
145+
// [Client-Side] Wait for a connection to the server
146+
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients, null, 512));
147+
148+
// [Host-Side] Check to make sure all clients are connected
149+
yield return MultiInstanceHelpers.Run(
150+
MultiInstanceHelpers.WaitForClientsConnectedToServer(server, clients.Length + 1, null, 512));
151+
152+
var serverObject = Object.Instantiate(m_Prefab, Vector3.zero, Quaternion.identity);
153+
NetworkObject serverNetworkObject = serverObject.GetComponent<NetworkObject>();
154+
serverNetworkObject.NetworkManagerOwner = server;
155+
serverNetworkObject.Spawn();
156+
serverNetworkObject.GetComponent<NetworkVariableInitOnNetworkSpawn>().Variable.Value = 10;
157+
Assert.IsFalse(NetworkVariableInitOnNetworkSpawn.OnValueChangedCalledOnClient);
158+
159+
// Wait until all objects have spawned.
160+
//const int expectedNetworkObjects = numClients + 2; // +2 = one for prefab, one for server.
161+
const int maxFrames = 240;
162+
var doubleCheckTime = Time.realtimeSinceStartup + 5.0f;
163+
while (!NetworkVariableInitOnNetworkSpawn.OnValueChangedCalledOnClient)
164+
{
165+
if (Time.frameCount > maxFrames)
166+
{
167+
// This is here in the event a platform is running at a higher
168+
// frame rate than expected
169+
if (doubleCheckTime < Time.realtimeSinceStartup)
170+
{
171+
Assert.Fail("Did not successfully spawn all expected NetworkObjects");
172+
break;
173+
}
174+
}
175+
var nextFrameNumber = Time.frameCount + 1;
176+
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
177+
}
178+
// Test of ordering is handled by an assert within OnNetworkSpawn
179+
Assert.IsTrue(NetworkVariableInitOnNetworkSpawn.OnValueChangedCalledOnClient);
180+
}
101181
}
102182
}

testproject/Assets/Tests/Runtime/Support/NetworkVariableInitOnNetworkSpawn.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,38 @@ namespace TestProject.RuntimeTests.Support
55
{
66
public class NetworkVariableInitOnNetworkSpawn : NetworkBehaviour
77
{
8-
private NetworkVariable<int> m_Variable = new NetworkVariable<int>();
8+
public NetworkVariable<int> Variable = new NetworkVariable<int>();
99
public static bool NetworkSpawnCalledOnServer;
1010
public static bool NetworkSpawnCalledOnClient;
11+
public static bool OnValueChangedCalledOnClient = false;
12+
13+
private void Awake()
14+
{
15+
Variable.OnValueChanged += OnValueChanged;
16+
}
17+
18+
public void OnValueChanged(int previousValue, int newValue)
19+
{
20+
if (!IsServer)
21+
{
22+
OnValueChangedCalledOnClient = true;
23+
}
24+
}
1125

1226
public override void OnNetworkSpawn()
1327
{
28+
Assert.IsFalse(OnValueChangedCalledOnClient);
1429
base.OnNetworkSpawn();
1530
if (IsServer)
1631
{
1732
NetworkSpawnCalledOnServer = true;
18-
m_Variable.Value = 5;
33+
Variable.Value = 5;
1934
}
2035
else
2136
{
2237
NetworkSpawnCalledOnClient = true;
2338
}
24-
Assert.AreEqual(5, m_Variable.Value);
39+
Assert.AreEqual(5, Variable.Value);
2540
}
2641
}
2742
}

testproject/Assets/Tests/Runtime/Support/SpawnRpcDespawn.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class SpawnRpcDespawn : NetworkBehaviour, INetworkUpdateSystem
1111
public static int ClientUpdateCount;
1212
public static int ServerUpdateCount;
1313
public static bool ClientNetworkSpawnRpcCalled;
14+
public static bool ExecuteClientRpc;
1415
public static NetworkUpdateStage StageExecutedByReceiver;
1516

1617
private bool m_Active = false;
@@ -42,10 +43,15 @@ public override void OnNetworkSpawn()
4243
{
4344
if (!IsServer)
4445
{
46+
// Asserting that the RPC is not called before OnNetworkSpawn
47+
Assert.IsFalse(ClientNetworkSpawnRpcCalled);
4548
return;
4649
}
4750

48-
TestClientRpc();
51+
if (ExecuteClientRpc)
52+
{
53+
TestClientRpc();
54+
}
4955
}
5056

5157
[ClientRpc]
@@ -54,12 +60,6 @@ private void TestClientRpc()
5460
ClientNetworkSpawnRpcCalled = true;
5561
}
5662

57-
public void NetworkStart()
58-
{
59-
Debug.Log($"Network Start on client {NetworkManager.LocalClientId.ToString()}");
60-
Assert.AreEqual(NetworkUpdateStage.EarlyUpdate, NetworkUpdateLoop.UpdateStage);
61-
}
62-
6363
public void Awake()
6464
{
6565
foreach (NetworkUpdateStage stage in Enum.GetValues(typeof(NetworkUpdateStage)))

testproject/Packages/packages-lock.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"depth": 0,
7171
"source": "local",
7272
"dependencies": {
73-
"com.unity.netcode.gameobjects": "0.0.1-preview.1",
73+
"com.unity.netcode.gameobjects": "0.2.0-preview.1",
7474
"com.unity.transport": "1.0.0-pre.5"
7575
}
7676
},

0 commit comments

Comments
 (0)