From 6ee768f3dccc5a1d587c6471db956c8be69c666e Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 6 Jun 2025 15:47:12 -0500 Subject: [PATCH 1/4] fix This fixes the issue where spawning a prefab that is configured to be spawned with no observers just prior to synchronizing a client and showing the spawned object to the client about to be synchronized within a NetworkBehaviour attached to the newly spawn prefab instance would result in a duplicate creation of the same prefab instance due to NetworkShow being deferred until the end of the frame. This fix excludes any spawned NetworkObjects that have pending visibility for a client that is being synchronized. --- .../Runtime/SceneManagement/SceneEventData.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 943f54aff2..7892d4c07c 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -324,6 +324,12 @@ internal void AddSpawnedNetworkObjects() var distributedAuthoritySendingToService = m_NetworkManager.DistributedAuthorityMode && TargetClientId == NetworkManager.ServerClientId; foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList) { + var spawnedObject = sobj; + // Don't synchronize objects that have pending visibility as that will be sent as a CreateObjectMessage towards the end of the current frame + if (TargetClientId != NetworkManager.ServerClientId && m_NetworkManager.SpawnManager.IsObjectVisibilityPending(TargetClientId,ref spawnedObject)) + { + continue; + } if (sobj.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService) { m_NetworkObjectsSync.Add(sobj); From c035075f756de4e314f3cf1257e83f5fd6baa8a3 Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 6 Jun 2025 15:47:33 -0500 Subject: [PATCH 2/4] style adding white space --- .../Runtime/SceneManagement/SceneEventData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 7892d4c07c..a70fcaf363 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -326,7 +326,7 @@ internal void AddSpawnedNetworkObjects() { var spawnedObject = sobj; // Don't synchronize objects that have pending visibility as that will be sent as a CreateObjectMessage towards the end of the current frame - if (TargetClientId != NetworkManager.ServerClientId && m_NetworkManager.SpawnManager.IsObjectVisibilityPending(TargetClientId,ref spawnedObject)) + if (TargetClientId != NetworkManager.ServerClientId && m_NetworkManager.SpawnManager.IsObjectVisibilityPending(TargetClientId, ref spawnedObject)) { continue; } From 3e4ba8954a34c5f53f7913052e56ca6cfb80e42c Mon Sep 17 00:00:00 2001 From: NoelStephensUnity Date: Fri, 6 Jun 2025 18:13:08 -0500 Subject: [PATCH 3/4] test PlayerSpawnObjectVisibilityTests integration test to validate this fix. --- .../PlayerSpawnObjectVisibilityTests.cs | 91 +++++++++++++++++++ .../PlayerSpawnObjectVisibilityTests.cs.meta | 2 + 2 files changed, 93 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/PlayerSpawnObjectVisibilityTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/PlayerSpawnObjectVisibilityTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/PlayerSpawnObjectVisibilityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/PlayerSpawnObjectVisibilityTests.cs new file mode 100644 index 0000000000..a29361e195 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/PlayerSpawnObjectVisibilityTests.cs @@ -0,0 +1,91 @@ +using System.Collections; +using System.Text.RegularExpressions; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.Server)] + [TestFixture(HostOrServer.Host)] + internal class PlayerSpawnObjectVisibilityTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + public enum PlayerSpawnStages + { + OnNetworkSpawn, + OnNetworkPostSpawn, + } + + public PlayerSpawnObjectVisibilityTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + public class PlayerVisibilityTestComponent : NetworkBehaviour + { + public PlayerSpawnStages Stage; + + private void Awake() + { + var networkObject = GetComponent(); + // Assure the player prefab will not spawn with observers. + // This assures that when the server/host spawns the connecting client's + // player prefab, the spawn object will initially not be spawnd on the client side. + networkObject.SpawnWithObservers = false; + } + + public override void OnNetworkSpawn() + { + ShowToClient(PlayerSpawnStages.OnNetworkSpawn); + base.OnNetworkSpawn(); + } + + protected override void OnNetworkPostSpawn() + { + ShowToClient(PlayerSpawnStages.OnNetworkPostSpawn); + base.OnNetworkPostSpawn(); + } + + private void ShowToClient(PlayerSpawnStages currentStage) + { + if (!HasAuthority || Stage != currentStage) + { + return; + } + NetworkObject.NetworkShow(OwnerClientId); + } + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + + /// + /// Tests the scenario where under a client-server network topology if a player prefab + /// is spawned by the server with no observers but the player prefab itself has server + /// side script that will network show the spawned object to the owning client. + /// + /// Because NetworkShow will defer the CreateObjectMessage until the late update, the + /// server/host needs to filter out including anything within the synchronization + /// message that already has pending visibility. + /// + /// Spawn stages to test + /// IEnumerator + [UnityTest] + public IEnumerator NetworkShowOnSpawnTest([Values] PlayerSpawnStages spawnStage) + { + m_PlayerPrefab.GetComponent().Stage = spawnStage; + + yield return CreateAndStartNewClient(); + + yield return new WaitForSeconds(0.25f); + + NetcodeLogAssert.LogWasNotReceived(LogType.Warning, new Regex("but it is already in the spawned list!")); + var client = GetNonAuthorityNetworkManager(); + Assert.True(client.LocalClient.PlayerObject != null, $"Client-{client.LocalClientId} does not have a player object!"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/PlayerSpawnObjectVisibilityTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/PlayerSpawnObjectVisibilityTests.cs.meta new file mode 100644 index 0000000000..c0555bdfca --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/PlayerSpawnObjectVisibilityTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 635d9e057e7179446906eccfd7fc9a90 \ No newline at end of file From 6b65299749e6728c6dfb3d5a547404d2326c6d45 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 9 Jun 2025 15:41:54 -0500 Subject: [PATCH 4/4] Update CHANGELOG.md --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 581b9414c5..cc466663c4 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -12,7 +12,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed: Issue where there was a potential for a small memory leak in the `ConnectionApprovedMessage`. (#3486) +- Fixed issue where the initial client synchronization pre-serialization process was not excluding spawned `NetworkObject` instances that already had pending visibility for the client being synchronized. (#3488) +- Fixed issue where there was a potential for a small memory leak in the `ConnectionApprovedMessage`. (#3486) ### Changed