Skip to content

Commit e2582e4

Browse files
fix: defer ObjectSceneChanged events to occur after client has finished synchronizing (#2502)
1 parent 39728b1 commit e2582e4

4 files changed

Lines changed: 135 additions & 5 deletions

File tree

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
4141

4242
### Fixed
4343

44+
- Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502)
4445
- Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492)
4546
- Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481)
4647
- Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481)

com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,11 @@ private void HandleClientSceneEvent(uint sceneEventId)
20582058
ClientId = m_NetworkManager.LocalClientId, // Client sent this to the server
20592059
});
20602060

2061+
// Process any SceneEventType.ObjectSceneChanged messages that
2062+
// were deferred while synchronizing and migrate the associated
2063+
// NetworkObjects to their newly assigned scenes.
2064+
sceneEventData.ProcessDeferredObjectSceneChangedEvents();
2065+
20612066
// Only if PostSynchronizationSceneUnloading is set and we are running in client synchronization
20622067
// mode additive do we unload any remaining scene that was not synchronized (otherwise any loaded
20632068
// scene not synchronized by the server will remain loaded)
@@ -2412,6 +2417,7 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject)
24122417
}
24132418

24142419
// Don't notify if a scene event is in progress
2420+
// Note: This does not apply to SceneEventType.Synchronize since synchronization isn't a global connected client event.
24152421
foreach (var sceneEventEntry in SceneEventProgressTracking)
24162422
{
24172423
if (!sceneEventEntry.Value.HasTimedOut() && sceneEventEntry.Value.Status == SceneEventProgressStatus.Started)
@@ -2430,8 +2436,10 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject)
24302436

24312437
/// <summary>
24322438
/// Invoked by clients when processing a <see cref="SceneEventType.ObjectSceneChanged"/> event
2439+
/// or invoked by <see cref="SceneEventData.ProcessDeferredObjectSceneChangedEvents"/> when a client finishes
2440+
/// synchronization.
24332441
/// </summary>
2434-
private void MigrateNetworkObjectsIntoScenes()
2442+
internal void MigrateNetworkObjectsIntoScenes()
24352443
{
24362444
try
24372445
{
@@ -2476,5 +2484,13 @@ internal void CheckForAndSendNetworkObjectSceneChanged()
24762484
SendSceneEventData(sceneEvent.SceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray());
24772485
EndSceneEvent(sceneEvent.SceneEventId);
24782486
}
2487+
2488+
// Used to handle client-side scene migration messages received while
2489+
// a client is synchronizing
2490+
internal struct DeferredObjectsMovedEvent
2491+
{
2492+
internal Dictionary<int, List<ulong>> ObjectsMigratedTable;
2493+
}
2494+
internal List<DeferredObjectsMovedEvent> DeferredObjectsMovedEvents = new List<DeferredObjectsMovedEvent>();
24792495
}
24802496
}

com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,15 @@ internal void Deserialize(FastBufferReader reader)
575575

576576
if (SceneEventType == SceneEventType.ObjectSceneChanged)
577577
{
578-
DeserializeObjectsMovedIntoNewScene(reader);
578+
// Defer these scene event types if a client hasn't finished synchronizing
579+
if (!m_NetworkManager.IsConnectedClient)
580+
{
581+
DeferObjectsMovedIntoNewScene(reader);
582+
}
583+
else
584+
{
585+
DeserializeObjectsMovedIntoNewScene(reader);
586+
}
579587
return;
580588
}
581589

@@ -1036,14 +1044,94 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader)
10361044
reader.ReadValueSafe(out networkObjectId);
10371045
if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId))
10381046
{
1039-
throw new Exception($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it no longer exists!");
1047+
NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!");
1048+
continue;
10401049
}
1050+
// Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed
1051+
//
10411052
sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(spawnManager.SpawnedObjects[networkObjectId]);
10421053
}
10431054
}
10441055
}
10451056

10461057

1058+
/// <summary>
1059+
/// While a client is synchronizing ObjectSceneChanged messages could be received.
1060+
/// This defers any ObjectSceneChanged message processing to occur after the client
1061+
/// has completed synchronization to assure the associated NetworkObjects being
1062+
/// migrated to a new scene are instantiated and spawned.
1063+
/// </summary>
1064+
private void DeferObjectsMovedIntoNewScene(FastBufferReader reader)
1065+
{
1066+
var sceneManager = m_NetworkManager.SceneManager;
1067+
var spawnManager = m_NetworkManager.SpawnManager;
1068+
var numberOfScenes = 0;
1069+
var sceneHandle = 0;
1070+
var objectCount = 0;
1071+
var networkObjectId = (ulong)0;
1072+
1073+
var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent()
1074+
{
1075+
ObjectsMigratedTable = new Dictionary<int, List<ulong>>()
1076+
};
1077+
1078+
reader.ReadValueSafe(out numberOfScenes);
1079+
for (int i = 0; i < numberOfScenes; i++)
1080+
{
1081+
reader.ReadValueSafe(out sceneHandle);
1082+
deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, new List<ulong>());
1083+
reader.ReadValueSafe(out objectCount);
1084+
for (int j = 0; j < objectCount; j++)
1085+
{
1086+
reader.ReadValueSafe(out networkObjectId);
1087+
deferredObjectsMovedEvent.ObjectsMigratedTable[sceneHandle].Add(networkObjectId);
1088+
}
1089+
}
1090+
sceneManager.DeferredObjectsMovedEvents.Add(deferredObjectsMovedEvent);
1091+
}
1092+
1093+
internal void ProcessDeferredObjectSceneChangedEvents()
1094+
{
1095+
var sceneManager = m_NetworkManager.SceneManager;
1096+
var spawnManager = m_NetworkManager.SpawnManager;
1097+
if (sceneManager.DeferredObjectsMovedEvents.Count == 0)
1098+
{
1099+
return;
1100+
}
1101+
foreach (var objectsMovedEvent in sceneManager.DeferredObjectsMovedEvents)
1102+
{
1103+
foreach (var keyEntry in objectsMovedEvent.ObjectsMigratedTable)
1104+
{
1105+
if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(keyEntry.Key))
1106+
{
1107+
sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, new List<NetworkObject>());
1108+
}
1109+
foreach (var objectId in keyEntry.Value)
1110+
{
1111+
if (!spawnManager.SpawnedObjects.ContainsKey(objectId))
1112+
{
1113+
NetworkLog.LogWarning($"[Deferred][Object Scene Migration] Trying to synchronize NetworkObjectId ({objectId}) but it was not spawned or no longer exists!");
1114+
continue;
1115+
}
1116+
var networkObject = spawnManager.SpawnedObjects[objectId];
1117+
if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Contains(networkObject))
1118+
{
1119+
sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Add(networkObject);
1120+
}
1121+
}
1122+
}
1123+
objectsMovedEvent.ObjectsMigratedTable.Clear();
1124+
}
1125+
1126+
sceneManager.DeferredObjectsMovedEvents.Clear();
1127+
1128+
// If there are any pending objects to migrate, then migrate them
1129+
if (sceneManager.ObjectsMigratedIntoNewScene.Count > 0)
1130+
{
1131+
sceneManager.MigrateNetworkObjectsIntoScenes();
1132+
}
1133+
}
1134+
10471135
/// <summary>
10481136
/// Used to release the pooled network buffer
10491137
/// </summary>

testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ private bool VerifySpawnedObjectsMigrated()
157157
return true;
158158
}
159159

160+
private const int k_MaxObjectsToSpawn = 9;
160161
/// <summary>
161162
/// Integration test to verify that migrating NetworkObjects
162163
/// into different scenes (in the same frame) is synchronized
@@ -167,7 +168,7 @@ private bool VerifySpawnedObjectsMigrated()
167168
public IEnumerator MigrateIntoNewSceneTest()
168169
{
169170
// Spawn 9 NetworkObject instances
170-
for (int i = 0; i < 9; i++)
171+
for (int i = 0; i < k_MaxObjectsToSpawn; i++)
171172
{
172173
var serverInstance = Object.Instantiate(m_TestPrefab);
173174
var serverNetworkObject = serverInstance.GetComponent<NetworkObject>();
@@ -204,12 +205,36 @@ public IEnumerator MigrateIntoNewSceneTest()
204205
yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated);
205206
AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!");
206207

207-
// Verify that a late joining client synchronizes properly
208+
// Register for the server-side client synchronization so we can send an object scene migration event at the same time
209+
// the new client begins to synchronize
210+
m_ServerNetworkManager.SceneManager.OnSynchronize += SceneManager_OnSynchronize;
211+
212+
// Verify that a late joining client synchronizes properly even while new scene migrations occur
213+
// during its synchronization
208214
yield return CreateAndStartNewClient();
209215
yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated);
216+
210217
AssertOnTimeout($"[Late Joined Client] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!");
211218
}
212219

220+
/// <summary>
221+
/// Migrate objects into other scenes when a client begins synchronization
222+
/// </summary>
223+
/// <param name="clientId"></param>
224+
private void SceneManager_OnSynchronize(ulong clientId)
225+
{
226+
var objectCount = k_MaxObjectsToSpawn - 1;
227+
228+
// Migrate the NetworkObjects into different scenes than they originally were migrated into
229+
foreach (var scene in m_ScenesLoaded)
230+
{
231+
SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount].gameObject, scene);
232+
SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount - 1].gameObject, scene);
233+
SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount - 2].gameObject, scene);
234+
objectCount -= 3;
235+
}
236+
}
237+
213238
/// <summary>
214239
/// Integration test to verify changing the currently active scene
215240
/// will migrate NetworkObjects with ActiveSceneSynchronization set

0 commit comments

Comments
 (0)