Skip to content

Commit 9b54e1a

Browse files
fix: PopulateScenePlacedObjects only checks IsSceneObject for null [MTT-3041] (#1850)
* fix MTT-3041 This fixes the issue when using a single scene containing both a NetworkManager and one or more in-scene placed NetworkObjects where a client could disconnect but not unload the scene and reconnect only to receive a soft synchronization error. The fix is to check for both HasValue and Value being true in order to account for already previously instantiated NetworkObjects. * style * Update CHANGELOG.md MTT-3041 changelog update * update cleaned up PopulateScenePlacedObjects for clarity. reduce the networkObjectInstance.IsSceneObject check. made PopulateScenePlacedObjects internal for testing purposes. * test MTT-3041 This test verifies the changes made to PopulateScenePlacedObjects * style Adding some comments * test just spawning one of each type of simulated in-scene placed NetworkObject
1 parent 9ffd22b commit 9b54e1a

File tree

4 files changed

+102
-11
lines changed

4 files changed

+102
-11
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
2323
- Removed `com.unity.modules.animation`, `com.unity.modules.physics` and `com.unity.modules.physics2d` dependencies from the package (#1812)
2424

2525
### Fixed
26+
- Fixed in-scene placed NetworkObjects not being found/ignored after a client disconnects and then reconnects. (#1850)
2627
- Fixed issue where `UnityTransport` send queues were not flushed when calling `DisconnectLocalClient` or `DisconnectRemoteClient`. (#1847)
2728
- Fixed NetworkBehaviour dependency verification check for an existing NetworkObject not searching from root parent transform relative GameObject. (#1841)
2829
- Fixed issue where entries were not being removed from the NetworkSpawnManager.OwnershipToObjectsTable. (#1838)

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1840,7 +1840,7 @@ internal void MoveObjectsToDontDestroyOnLoad()
18401840
/// Using the local scene relative Scene.handle as a sub-key to the root dictionary allows us to
18411841
/// distinguish between duplicate in-scene placed NetworkObjects
18421842
/// </summary>
1843-
private void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true)
1843+
internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePlacedObjects = true)
18441844
{
18451845
if (clearScenePlacedObjects)
18461846
{
@@ -1855,25 +1855,26 @@ private void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearScenePl
18551855
// at the end of scene loading we use this list to soft synchronize all in-scene placed NetworkObjects
18561856
foreach (var networkObjectInstance in networkObjects)
18571857
{
1858-
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (additive scenes)
1859-
if (networkObjectInstance.IsSceneObject == null && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
1860-
networkObjectInstance.gameObject.scene.handle == sceneToFilterBy.handle)
1858+
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
1859+
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
1860+
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
1861+
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
1862+
sceneHandle == sceneToFilterBy.handle)
18611863
{
1862-
if (!ScenePlacedObjects.ContainsKey(networkObjectInstance.GlobalObjectIdHash))
1864+
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
18631865
{
1864-
ScenePlacedObjects.Add(networkObjectInstance.GlobalObjectIdHash, new Dictionary<int, NetworkObject>());
1866+
ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary<int, NetworkObject>());
18651867
}
18661868

1867-
if (!ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash].ContainsKey(networkObjectInstance.gameObject.scene.handle))
1869+
if (!ScenePlacedObjects[globalObjectIdHash].ContainsKey(sceneHandle))
18681870
{
1869-
ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash].Add(networkObjectInstance.gameObject.scene.handle, networkObjectInstance);
1871+
ScenePlacedObjects[globalObjectIdHash].Add(sceneHandle, networkObjectInstance);
18701872
}
18711873
else
18721874
{
1873-
var exitingEntryName = ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash][networkObjectInstance.gameObject.scene.handle] != null ?
1874-
ScenePlacedObjects[networkObjectInstance.GlobalObjectIdHash][networkObjectInstance.gameObject.scene.handle].name : "Null Entry";
1875+
var exitingEntryName = ScenePlacedObjects[globalObjectIdHash][sceneHandle] != null ? ScenePlacedObjects[globalObjectIdHash][sceneHandle].name : "Null Entry";
18751876
throw new Exception($"{networkObjectInstance.name} tried to registered with {nameof(ScenePlacedObjects)} which already contains " +
1876-
$"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {networkObjectInstance.GlobalObjectIdHash} for {exitingEntryName}!");
1877+
$"the same {nameof(NetworkObject.GlobalObjectIdHash)} value {globalObjectIdHash} for {exitingEntryName}!");
18771878
}
18781879
}
18791880
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
using System.Collections;
2+
using System.Collections.Generic;
3+
using UnityEngine;
4+
using UnityEngine.SceneManagement;
5+
using UnityEngine.TestTools;
6+
using NUnit.Framework;
7+
using Unity.Netcode;
8+
using Unity.Netcode.TestHelpers.Runtime;
9+
using Object = UnityEngine.Object;
10+
11+
namespace TestProject.RuntimeTests
12+
{
13+
public class NetworkSceneManagerPopulateInSceneTests : NetcodeIntegrationTest
14+
{
15+
protected override int NumberOfClients => 0;
16+
17+
18+
protected Dictionary<uint, GameObject> m_InSceneObjectList = new Dictionary<uint, GameObject>();
19+
20+
protected override IEnumerator OnSetup()
21+
{
22+
m_InSceneObjectList.Clear();
23+
return base.OnSetup();
24+
}
25+
26+
protected override void OnServerAndClientsCreated()
27+
{
28+
// Create one that simulates when an in-scene placed NetworkObject is first instantiated when
29+
// the scene is loaded (i.e. IsSceneObject is null)
30+
var inScenePrefab = CreateNetworkObjectPrefab("NewSceneObject");
31+
var networkObject = inScenePrefab.GetComponent<NetworkObject>();
32+
networkObject.IsSceneObject = null;
33+
networkObject.NetworkManagerOwner = m_ServerNetworkManager;
34+
m_InSceneObjectList.Add(networkObject.GlobalObjectIdHash, inScenePrefab);
35+
36+
// Create one that simulates when an in-scene placed NetworkObject has already been instantiated
37+
// (i.e. IsSceneObject is true) which can happen if a client disconnects and then reconnects without
38+
// unloading/reloading any scenes.
39+
inScenePrefab = CreateNetworkObjectPrefab("SetInSceneObject");
40+
networkObject = inScenePrefab.GetComponent<NetworkObject>();
41+
networkObject.IsSceneObject = true;
42+
networkObject.NetworkManagerOwner = m_ServerNetworkManager;
43+
m_InSceneObjectList.Add(networkObject.GlobalObjectIdHash, inScenePrefab);
44+
}
45+
46+
[UnityTest]
47+
public IEnumerator PopulateScenePlacedObjectsTest()
48+
{
49+
var activeScene = SceneManager.GetActiveScene();
50+
51+
m_ServerNetworkManager.SceneManager.PopulateScenePlacedObjects(activeScene, true);
52+
var scenePlacedNetworkObjects = m_ServerNetworkManager.SceneManager.ScenePlacedObjects;
53+
foreach (var entry in m_InSceneObjectList)
54+
{
55+
// Verify the GlobalObjectIdHash for this object has an entry
56+
Assert.IsTrue(scenePlacedNetworkObjects.ContainsKey(entry.Key), $"Failed to find {nameof(NetworkObject.GlobalObjectIdHash)}({entry.Key}) for {entry.Value.name} in the {nameof(NetworkSceneManager.ScenePlacedObjects)}!");
57+
58+
// Verify the active scene for this object has an entry
59+
Assert.IsTrue(scenePlacedNetworkObjects[entry.Key].ContainsKey(activeScene.handle), $"Failed to find the scene handle {activeScene.handle} ({activeScene.name}) entry for {entry.Value.name} in the {nameof(NetworkSceneManager.ScenePlacedObjects)}!");
60+
61+
// Verify the GameObject is the same one
62+
var inSceneGameObject = scenePlacedNetworkObjects[entry.Key][activeScene.handle].gameObject;
63+
Assert.IsTrue(inSceneGameObject == entry.Value, $"{nameof(GameObject)} {entry.Value.name} is not the same as {inSceneGameObject.name}!");
64+
}
65+
66+
yield break;
67+
}
68+
69+
protected override IEnumerator OnTearDown()
70+
{
71+
foreach (var spawnedInstance in m_InSceneObjectList)
72+
{
73+
Object.Destroy(spawnedInstance.Value);
74+
}
75+
return base.OnTearDown();
76+
}
77+
}
78+
}

testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)