From d07687db145ba08f94c42759e2286523ffdd6614 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Wed, 6 May 2026 14:15:05 -0500 Subject: [PATCH 1/4] Added hybrid spawning to a bunch of existing tests and fixed most of the issues that fell out of that. --- .../Runtime/Core/NetworkManager.cs | 59 +++---------- .../Runtime/Core/NetworkObject.cs | 6 ++ .../Messaging/DeferredMessageManager.cs | 15 ++++ .../IDeferredNetworkMessageManager.cs | 3 + .../Messaging/Messages/SceneEventMessage.cs | 10 +++ .../Messaging/NetworkMessageManager.cs | 4 - .../Runtime/Spawning/NetworkPrefabHandler.cs | 16 ++++ .../UnifiedNetworkTransformTest.cs | 26 +----- .../TestHelpers/NetcodeIntegrationTest.cs | 82 ++++++++++++++++++- .../NetcodeIntegrationTestHelpers.cs | 5 +- testproject/Assets/NetCodeConfig.asset | 2 +- .../Assets/Tests/Runtime/AddressablesTests.cs | 4 + .../Tests/Runtime/DontDestroyOnLoadTests.cs | 3 + .../NetworkBehaviourSessionSynchronized.cs | 3 + .../NetworkObjectDestroyWithSceneTests.cs | 5 +- .../Tests/Runtime/NetworkObjectSpawning.cs | 9 +- .../SceneEventDataTests.cs | 4 +- .../Runtime/OnNetworkSpawnExceptionTests.cs | 15 +++- .../Tests/Runtime/PrefabExtendedTests.cs | 10 ++- .../RespawnInSceneObjectsAfterShutdown.cs | 9 +- .../Assets/Tests/Runtime/RpcObserverTests.cs | 8 ++ .../Assets/Tests/Runtime/RpcTestsAutomated.cs | 9 ++ .../Runtime/RpcUserSerializableTypesTest.cs | 9 +- .../SceneObjectsNotDestroyedOnShutdownTest.cs | 9 +- .../Assets/Tests/Runtime/SenderIdTests.cs | 6 ++ .../Runtime/ServerDisconnectsClientTest.cs | 9 +- .../Runtime/TestProject.Runtime.Tests.asmdef | 5 ++ 27 files changed, 239 insertions(+), 106 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 5f8473d89c..9edaad1073 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1322,13 +1322,18 @@ private bool CanStart(StartType type) public NetcodeWorld NetcodeWorld { get; internal set; } #if UNIFIED_NETCODE - private System.Collections.IEnumerator WaitForHybridPrefabRegistration(StartType startType) + internal void InitializeNetcodeWorld() { + if (NetcodeWorld != null) + { + return; + } + if (this == Singleton) { if (NetCode.Netcode.IsActive) { - NetworkLog.LogInfo($"[{nameof(WaitForHybridPrefabRegistration)}] Netcode is not active but has an instance at this point."); + NetworkLog.LogInfo($"[{nameof(InitializeNetcodeWorld)}] Netcode is not active but has an instance at this point."); } /// !! Important !! /// Clear out any pre-existing configuration in the event this applicatioin instance has already been connected to a session. @@ -1339,42 +1344,6 @@ private System.Collections.IEnumerator WaitForHybridPrefabRegistration(StartType /// Worlds are created here: UnifiedBootStrap.CurrentNetworkManagerForInitialization = this; DefaultWorldInitialization.Initialize("Default World", false); - - // This should not be needed at this point, but this is here in the event something changes. - if (NetworkConfig.Prefabs.HasPendingGhostPrefabs) - { - NetworkLog.LogWarning($"[{nameof(WaitForHybridPrefabRegistration)}] !!!!! (Ghosts are still pending registration) !!!!!"); - var waitTime = new WaitForSeconds(0.016f); - while (NetworkConfig.Prefabs.HasPendingGhostPrefabs) - { - NetworkConfig.Prefabs.RegisterGhostPrefabs(this); - yield return waitTime; - } - } - - if (LogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"[{nameof(WaitForHybridPrefabRegistration)}] All hybrid prefabs have been registered!"); - NetworkLog.LogInfo($"[{nameof(WaitForHybridPrefabRegistration)}] Finalizing NetworkManager start..."); - } - switch (startType) - { - case StartType.Server: - { - InternalStartServer(); - break; - } - case StartType.Host: - { - InternalStartHost(); - break; - } - case StartType.Client: - { - InternalStartClient(); - break; - } - } } private bool UnifiedIsConfiguredCorrectly() @@ -1442,8 +1411,8 @@ public bool StartServer() { Debug.Log("Creating world: Default world"); } - StartCoroutine(WaitForHybridPrefabRegistration(StartType.Server)); - return true; + InitializeNetcodeWorld(); + return InternalStartServer(); } else { @@ -1529,9 +1498,8 @@ public bool StartClient() { Debug.Log("Creating world: Default world"); } - StartCoroutine(WaitForHybridPrefabRegistration(StartType.Client)); - // TODO-UNIFIED: Need a way to signal everything completed. - return true; + InitializeNetcodeWorld(); + return InternalStartClient(); } else { @@ -1616,9 +1584,8 @@ public bool StartHost() { Debug.Log("Creating world: Default world"); } - StartCoroutine(WaitForHybridPrefabRegistration(StartType.Host)); - // TODO-UNIFIED: Need a way to signal everything completed. - return true; + InitializeNetcodeWorld(); + return InternalStartHost(); } else { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 53faae062f..3992345b99 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2005,6 +2005,12 @@ public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false) /// (true) the will be destroyed (false) the will persist after being despawned public void Despawn(bool destroy = true) { +#if UNIFIED_NETCODE + if (HasGhost && destroy == false) + { + throw new NotSupportedException("Despawn without destroy is not supported for hybrid objects."); + } +#endif if (!IsSpawned) { if (NetworkManager.LogLevel <= LogLevel.Error) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs index f05d000fec..c68173e60d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs @@ -125,6 +125,16 @@ protected virtual void PurgeTrigger(IDeferredNetworkMessageManager.TriggerType t triggerInfo.TriggerData.Dispose(); } + public bool HasAnyOfTrigger(IDeferredNetworkMessageManager.TriggerType trigger) + { + if (m_Triggers.TryGetValue(trigger, out var triggers)) + { + return triggers.Count != 0; + } + + return false; + } + public virtual void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType trigger, ulong key) { if (m_Triggers.TryGetValue(trigger, out var triggers)) @@ -143,6 +153,11 @@ public virtual void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType t triggerInfo.TriggerData.Dispose(); } } + + if (trigger != IDeferredNetworkMessageManager.TriggerType.OnOtherTriggerFinishedProcessing) + { + ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnOtherTriggerFinishedProcessing, (ulong)trigger); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs index 2e6dd7cfbd..64ad123166 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs @@ -11,6 +11,7 @@ internal enum TriggerType #if UNIFIED_NETCODE OnGhostSpawned, #endif + OnOtherTriggerFinishedProcessing, } /// @@ -31,6 +32,8 @@ internal enum TriggerType public void ProcessTriggers(TriggerType trigger, ulong key); + public bool HasAnyOfTrigger(IDeferredNetworkMessageManager.TriggerType trigger); + /// /// Cleans up any trigger that's existed for more than a second. /// These triggers were probably for situations where a request was received after a despawn rather than before a spawn. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs index c0452146b4..4a3376d912 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs @@ -8,6 +8,7 @@ internal struct SceneEventMessage : INetworkMessage public SceneEventData EventData; + private const string k_Name = "SceneEventMessage"; private FastBufferReader m_ReceivedData; @@ -18,6 +19,15 @@ public void Serialize(FastBufferWriter writer, int targetVersion) public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) { + var networkManager = (NetworkManager)context.SystemOwner; +#if UNIFIED_NETCODE + if (networkManager.DeferredMessageManager.HasAnyOfTrigger(IDeferredNetworkMessageManager.TriggerType + .OnGhostSpawned)) + { + networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnOtherTriggerFinishedProcessing, (ulong)IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, reader, ref context, k_Name); + return false; + } +#endif m_ReceivedData = reader; return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 40be416a00..c66aa62273 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -228,8 +228,6 @@ internal void HandleIncomingData(ulong clientId, ArraySegment data, float { unsafe { - - //Debug.Log($"Receiving {data.Count} bytes: {ByteArrayToString(data.Array, data.Offset, data.Count)}"); fixed (byte* dataPtr = data.Array) { var batchReader = new FastBufferReader(dataPtr + data.Offset, Allocator.None, data.Count); @@ -396,7 +394,6 @@ public void HandleMessage(in NetworkMessageHeader header, FastBufferReader reade }; var type = m_ReverseTypeMap[header.MessageType]; - Debug.Log($"Got message of type {type}"); if (!CanReceive(senderId, type, reader, ref context)) { return; @@ -873,7 +870,6 @@ internal unsafe void ProcessSendQueues() try { - //Debug.Log($"Sending {queueItem.Writer.Length} bytes: {ByteArrayToString(queueItem.Writer.ToArray(), 0, queueItem.Writer.Length)}"); m_Sender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer); for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index c8aab939f4..11a5a0be65 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +#if UNIFIED_NETCODE +using Unity.NetCode; +#endif using UnityEngine; namespace Unity.Netcode @@ -418,6 +421,19 @@ public void AddNetworkPrefab(GameObject prefab) { m_NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash); } + +#if UNIFIED_NETCODE + if (m_NetworkManager.IsListening) + { + var ghost = prefab.GetComponent(); + if (ghost) + { + m_NetworkManager.InitializeNetcodeWorld(); + NetCode.Netcode.RegisterPrefabSingleWorld(prefab, m_NetworkManager.IsHost, + m_NetworkManager.NetcodeWorld); + } + } +#endif } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs index 4325695783..ef09d0829a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs @@ -39,33 +39,9 @@ protected override IEnumerator OnSetup() // Creates the hybrid prefab m_Prefab = CreateHybridPrefab("HybridPrefab", true); m_Prefab.AddComponent(); - NetworkSpawnManager.RegisterPendingGhost = RegisterPendingGhost; return base.OnSetup(); } - - protected override IEnumerator OnTearDown() - { - NetworkSpawnManager.RegisterPendingGhost = null; - m_EnableVerboseDebug = false; - return base.OnTearDown(); - } - - private void RegisterPendingGhost(NetworkObject networkObject, ulong networkObjectId) - { - var ghost = networkObject.GetComponent(); - Assert.IsNotNull(ghost, $"[RegisterPendingGhost][NetworkObject-{networkObjectId}] Has no {nameof(GhostAdapter)}!"); - foreach (var networkManager in m_NetworkManagers) - { - // If the world matches, then register the instance with this NetworkManager's spawn manager. - if (networkManager.NetcodeWorld == ghost.World) - { - networkManager.SpawnManager.RegisterGhostPendingSpawn(networkObject, networkObjectId); - return; - } - } - Debug.LogError($"Did not find a world for NetworkObject-{networkObjectId}!!"); - } - + protected override void OnServerAndClientsCreated() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 0b383cc7c0..bf067389ae 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -224,7 +224,17 @@ public enum HostOrServer /// /// Denotes that distributed authority is being used. /// - DAHost + DAHost, +#if UNIFIED_NETCODE + /// + /// Use N4E-backed hybrid spawning in server mode + /// + UnifiedServer, + /// + /// Use N4E-backed hybrid spawning in host mode + /// + UnifiedHost +#endif } /// @@ -627,6 +637,10 @@ private void InternalOnOneTimeSetup() /// protected virtual IEnumerator OnSetup() { + if(m_allPrefabsAsHybrid) + { + NetworkSpawnManager.RegisterPendingGhost = RegisterPendingGhost; + } yield return null; } @@ -640,6 +654,10 @@ protected virtual IEnumerator OnSetup() /// protected virtual void OnInlineSetup() { + if(m_allPrefabsAsHybrid) + { + NetworkSpawnManager.RegisterPendingGhost = RegisterPendingGhost; + } } /// @@ -706,6 +724,22 @@ public IEnumerator SetUp() VerboseDebug($"Exiting {nameof(SetUp)}"); } + private void RegisterPendingGhost(NetworkObject networkObject, ulong networkObjectId) + { + var ghost = networkObject.GetComponent(); + Assert.IsNotNull(ghost, $"[RegisterPendingGhost][NetworkObject-{networkObjectId}] Has no {nameof(GhostAdapter)}!"); + foreach (var networkManager in m_NetworkManagers) + { + // If the world matches, then register the instance with this NetworkManager's spawn manager. + if (networkManager.NetcodeWorld == ghost.World) + { + networkManager.SpawnManager.RegisterGhostPendingSpawn(networkObject, networkObjectId); + return; + } + } + Debug.LogError($"Did not find a world for NetworkObject-{networkObjectId}!!"); + } + /// /// Override this to add components or adjustments to the default player prefab /// @@ -1596,6 +1630,17 @@ protected IEnumerator CoroutineShutdownAndCleanUp() DestroyNetworkManagers(); } + protected void UnifiedCleanup() + { +#if UNIFIED_NETCODE + if(m_allPrefabsAsHybrid) + { + NetworkSpawnManager.RegisterPendingGhost = null; + CleanupPrefabReferences(); + } +#endif + } + /// /// Note: For mode /// this is called before ShutdownAndCleanUp. @@ -1603,6 +1648,7 @@ protected IEnumerator CoroutineShutdownAndCleanUp() /// protected virtual IEnumerator OnTearDown() { + UnifiedCleanup(); yield return null; } @@ -1611,6 +1657,7 @@ protected virtual IEnumerator OnTearDown() /// protected virtual void OnInlineTearDown() { + UnifiedCleanup(); } /// @@ -2185,6 +2232,10 @@ internal void WaitForMessagesReceivedWithTimeTravel(List messagesInOrder, Assert.True(WaitForConditionOrTimeOutWithTimeTravel(hooks), $"[Messages Not Recieved] {hooks.GetHooksStillWaiting()}"); } +#if UNIFIED_NETCODE + protected bool m_allPrefabsAsHybrid = false; +#endif + /// /// Creates a basic NetworkObject test prefab, assigns it to a new /// NetworkPrefab entry, and then adds it to the server and client(s) @@ -2194,6 +2245,12 @@ internal void WaitForMessagesReceivedWithTimeTravel(List messagesInOrder, /// The assigned to the new NetworkPrefab entry protected GameObject CreateNetworkObjectPrefab(string baseName) { +#if UNIFIED_NETCODE + if (m_allPrefabsAsHybrid) + { + return CreateHybridPrefab(baseName); + } +#endif var prefabCreateAssertError = $"You can only invoke this method during {nameof(OnServerAndClientsCreated)} " + $"but before {nameof(OnStartedServerAndClients)}!"; var authorityNetworkManager = GetAuthorityNetworkManager(); @@ -2207,6 +2264,13 @@ protected GameObject CreateNetworkObjectPrefab(string baseName) } #if UNIFIED_NETCODE + protected void CleanupPrefabReferences() + { + foreach (var reference in Object.FindObjectsByType()) + { + Object.Destroy(reference); + } + } protected GameObject CreateHybridPrefab(string baseName, bool moveToDDOL = true) { // Prevent from trying to register/spawn when creating this hybrid prefab @@ -2269,6 +2333,16 @@ protected GameObject CreateHybridPrefab(string baseName, bool moveToDDOL = true) { Object.DontDestroyOnLoad(gameObject); } + var authorityNetworkManager = GetAuthorityNetworkManager(); + authorityNetworkManager.AddNetworkPrefab(gameObject); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + if (clientNetworkManager == authorityNetworkManager) + { + continue; + } + clientNetworkManager.AddNetworkPrefab(gameObject); + } return gameObject; } #endif @@ -2481,8 +2555,12 @@ private void InitializeTestConfiguration(NetworkTopologyTypes networkTopologyTyp // Note: For m_DistributedAuthority to be true, the m_NetworkTopologyType must be set to NetworkTopologyTypes.DistributedAuthority hostOrServer = m_DistributedAuthority ? HostOrServer.DAHost : HostOrServer.Host; } +#if UNIFIED_NETCODE + m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost || hostOrServer == HostOrServer.UnifiedHost; + m_allPrefabsAsHybrid = (hostOrServer == HostOrServer.UnifiedServer || hostOrServer == HostOrServer.UnifiedHost); +#else m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost; - +#endif // If we are using a distributed authority network topology and the environment variable // to use the CMBService is set, then perform the m_UseCmbService check. if (m_DistributedAuthority && GetServiceEnvironmentVariable()) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index 32a0f734d7..6e031c6866 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -745,18 +745,17 @@ public static GameObject CreateNetworkObjectPrefab(string baseName, NetworkManag Assert.IsFalse(authorityNetworkManager.IsListening, prefabCreateAssertError); var gameObject = CreateNetworkObject(baseName); - var networkPrefab = new NetworkPrefab() { Prefab = gameObject }; // We could refactor this test framework to share a NetworkPrefabList instance, but at this point it's // probably more trouble than it's worth to verify these lists stay in sync across all tests... - authorityNetworkManager.NetworkConfig.Prefabs.Add(networkPrefab); + authorityNetworkManager.AddNetworkPrefab(gameObject); foreach (var clientNetworkManager in clients) { if (clientNetworkManager == authorityNetworkManager) { continue; } - clientNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = gameObject }); + clientNetworkManager.AddNetworkPrefab(gameObject); } return gameObject; } diff --git a/testproject/Assets/NetCodeConfig.asset b/testproject/Assets/NetCodeConfig.asset index b0d1add6cc..f6b1280970 100644 --- a/testproject/Assets/NetCodeConfig.asset +++ b/testproject/Assets/NetCodeConfig.asset @@ -16,7 +16,7 @@ MonoBehaviour: EnableClientServerBootstrap: 0 HostWorldModeSelection: 1 ClientServerTickRate: - SimulationTickRate: 60 + SimulationTickRate: 30 PredictedFixedStepSimulationTickRatio: 1 NetworkTickRate: 0 MaxSimulationStepsPerFrame: 1 diff --git a/testproject/Assets/Tests/Runtime/AddressablesTests.cs b/testproject/Assets/Tests/Runtime/AddressablesTests.cs index 4c9151c0f3..fb492b9540 100644 --- a/testproject/Assets/Tests/Runtime/AddressablesTests.cs +++ b/testproject/Assets/Tests/Runtime/AddressablesTests.cs @@ -15,6 +15,10 @@ namespace TestProject.RuntimeTests [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] +#if UNIFIED_NETCODE + [TestFixture(HostOrServer.UnifiedHost)] + [TestFixture(HostOrServer.UnifiedServer)] +#endif public class AddressablesTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs index 105265b08f..2d841049ca 100644 --- a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs +++ b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs @@ -11,6 +11,9 @@ namespace TestProject.RuntimeTests { [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.DAHost)] +#if UNIFIED_NETCODE + [TestFixture(HostOrServer.UnifiedHost)] +#endif public class DontDestroyOnLoadTests : NetcodeIntegrationTest { private const int k_ClientsToConnect = 4; diff --git a/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs b/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs index 771a90b9b8..0582211d0f 100644 --- a/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs +++ b/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs @@ -9,6 +9,9 @@ namespace TestProject.RuntimeTests { [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.DAHost)] +#if UNIFIED_NETCODE + [TestFixture(HostOrServer.UnifiedHost)] +#endif public class NetworkBehaviourSessionSynchronized : NetcodeIntegrationTest { private const string k_SceneToLoad = "SessionSynchronize"; diff --git a/testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.cs b/testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.cs index ad5fe5e1b0..57a2660601 100644 --- a/testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.cs @@ -10,6 +10,9 @@ namespace TestProject.RuntimeTests { [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(HostOrServer.UnifiedHost)] +#endif internal class NetworkObjectDestroyWithSceneTests : NetcodeIntegrationTest { private const string k_SceneToLoad = "EmptyScene"; @@ -98,7 +101,7 @@ public IEnumerator DestroyWithScene() // Depending on network topology, spawn the object with the appropriate owner. var owner = m_DistributedAuthority ? m_NotSessionOwner : m_SessionOwner; - m_SpawnedInstance = SpawnObject(m_TestPrefab.gameObject, m_NotSessionOwner, true).GetComponent(); + m_SpawnedInstance = SpawnObject(m_TestPrefab.gameObject, owner, true).GetComponent(); var instanceName = m_SpawnedInstance.name; yield return WaitForConditionOrTimeOut(() => ObjectSpawnedOnAllNetworkManagers(true)); diff --git a/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs index 6ed56b04ed..1b1f0e8c32 100644 --- a/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs +++ b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs @@ -9,8 +9,11 @@ namespace TestProject.RuntimeTests { - [TestFixture(NetworkTopologyTypes.ClientServer)] - [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)] +#endif + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] internal class NetworkObjectSpawning : NetcodeIntegrationTest { private const string k_SceneToLoad = "NetworkObjectSpawnerTest"; @@ -27,7 +30,7 @@ protected override bool UseCMBService() return false; } - public NetworkObjectSpawning(NetworkTopologyTypes networkTopology) : base(networkTopology) { } + public NetworkObjectSpawning(NetworkTopologyTypes networkTopology, HostOrServer hostOrServer) : base(networkTopology, hostOrServer) { } protected override IEnumerator OnSetup() diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventDataTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventDataTests.cs index a37a3244d2..f1bdc67c66 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventDataTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventDataTests.cs @@ -38,12 +38,10 @@ public IEnumerator FastReaderAllocationTest() var networkManager = networkManagerGameObject.AddComponent(); var unityTransport = networkManagerGameObject.AddComponent(); - var prefabs = ScriptableObject.CreateInstance(); - prefabs.Add(new NetworkPrefab()); networkManager.NetworkConfig = new NetworkConfig() { ConnectionApproval = false, - Prefabs = new NetworkPrefabs { NetworkPrefabsLists = new List { prefabs } }, + Prefabs = new NetworkPrefabs { NetworkPrefabsLists = new List { } }, NetworkTransport = unityTransport }; diff --git a/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs b/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs index ea81ac8409..9752ca9b80 100644 --- a/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs +++ b/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs @@ -2,11 +2,12 @@ using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; +using NUnit.Framework; using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; -using UnityEngine.Assertions; using UnityEngine.TestTools; +using Assert = UnityEngine.Assertions.Assert; using Random = UnityEngine.Random; namespace TestProject.RuntimeTests @@ -81,7 +82,11 @@ public override void OnNetworkDespawn() throw new Exception("Exception thrown in OnNetworkDespawn"); } } - + + [TestFixture(HostOrServer.Server)] +#if UNIFIED_NETCODE + [TestFixture(HostOrServer.UnifiedServer)] +#endif public class OnNetworkSpawnExceptionTests : NetcodeIntegrationTest { protected override int NumberOfClients => 1; @@ -101,6 +106,11 @@ protected override bool UseCMBService() return false; } + public OnNetworkSpawnExceptionTests(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + [UnityTest] public IEnumerator WhenOnNetworkSpawnThrowsException_FutureOnNetworkSpawnsAreNotPrevented() { @@ -238,7 +248,6 @@ public IEnumerator WhenOnNetworkDespawnThrowsException_FutureOnNetworkDespawnsAr protected override IEnumerator OnSetup() { - m_UseHost = false; OnNetworkSpawnThrowsExceptionComponent.NumClientSpawns = 0; OnNetworkSpawnThrowsExceptionComponent.NumServerSpawns = 0; OnNetworkSpawnNoExceptionComponent.NumClientSpawns = 0; diff --git a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs index 665590d342..8689074023 100644 --- a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs +++ b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs @@ -13,8 +13,12 @@ namespace TestProject.RuntimeTests { // DAMODE-TODO: When scene management is working in distributed authority mode we need to update this test - [TestFixture(SceneManagementTypes.SceneManagementEnabled)] - [TestFixture(SceneManagementTypes.SceneManagementDisabled)] + [TestFixture(SceneManagementTypes.SceneManagementEnabled, HostOrServer.Host)] + [TestFixture(SceneManagementTypes.SceneManagementDisabled, HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(SceneManagementTypes.SceneManagementEnabled, HostOrServer.UnifiedHost)] + [TestFixture(SceneManagementTypes.SceneManagementDisabled, HostOrServer.UnifiedHost)] +#endif public class PrefabExtendedTests : NetcodeIntegrationTest { private const string k_PrefabTestScene = "PrefabTestScene"; @@ -42,7 +46,7 @@ protected override bool UseCMBService() return false; } - public PrefabExtendedTests(SceneManagementTypes sceneManagementType) + public PrefabExtendedTests(SceneManagementTypes sceneManagementType, HostOrServer hostOrServer) : base(hostOrServer) { m_SceneManagementEnabled = sceneManagementType == SceneManagementTypes.SceneManagementEnabled; } diff --git a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs index 7dbdc2635f..857c14ee6f 100644 --- a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs +++ b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs @@ -8,8 +8,11 @@ namespace TestProject.RuntimeTests { - [TestFixture(NetworkTopologyTypes.DistributedAuthority)] - [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)] +#endif public class RespawnInSceneObjectsAfterShutdown : NetcodeIntegrationTest { public const string SceneToLoad = "InSceneNetworkObject"; @@ -23,7 +26,7 @@ protected override bool UseCMBService() return false; } - public RespawnInSceneObjectsAfterShutdown(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + public RespawnInSceneObjectsAfterShutdown(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } protected override void OnOneTimeSetup() { diff --git a/testproject/Assets/Tests/Runtime/RpcObserverTests.cs b/testproject/Assets/Tests/Runtime/RpcObserverTests.cs index b71c7e03dd..66d9834996 100644 --- a/testproject/Assets/Tests/Runtime/RpcObserverTests.cs +++ b/testproject/Assets/Tests/Runtime/RpcObserverTests.cs @@ -18,6 +18,10 @@ namespace TestProject.RuntimeTests [TestFixture(HostOrServer.DAHost)] [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] +#if UNIFIED_NETCODE + [TestFixture(HostOrServer.UnifiedHost)] + [TestFixture(HostOrServer.UnifiedServer)] +#endif public class RpcObserverTests : NetcodeIntegrationTest { protected override int NumberOfClients => 9; @@ -155,6 +159,10 @@ private IEnumerator RunRpcObserverTest(List nonObservers) [UnityTest] public IEnumerator DespawnRespawnObserverTest() { + if (m_allPrefabsAsHybrid) + { + Assert.Ignore("Hybrid spawning does not support despawn-without-destroy."); + } var nonObservers = new List(); m_ServerRpcObserverObject.ResetTest(); // Wait for all clients to report they have spawned an instance of our test prefab diff --git a/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs b/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs index 228bfbfdd5..704c0ed10c 100644 --- a/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs +++ b/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs @@ -10,6 +10,10 @@ namespace TestProject.RuntimeTests { + [TestFixture(HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(HostOrServer.UnifiedHost)] +#endif public class RpcTestsAutomated : NetcodeIntegrationTest { private bool m_TimedOut; @@ -23,6 +27,11 @@ protected override bool UseCMBService() return false; } + public RpcTestsAutomated(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() { return NetworkManagerInstatiationMode.DoNotCreate; diff --git a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs index ae10ea60c9..5a92112f32 100644 --- a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs +++ b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs @@ -49,8 +49,11 @@ public void NetworkSerialize(BufferSerializer } - [TestFixture(NetworkTopologyTypes.DistributedAuthority)] - [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)] +#endif public class RpcUserSerializableTypesTest : NetcodeIntegrationTest { private UserSerializableClass m_UserSerializableClass; @@ -88,7 +91,7 @@ protected override bool UseCMBService() return false; } - public RpcUserSerializableTypesTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + public RpcUserSerializableTypesTest(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() { diff --git a/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs b/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs index f82b7ed400..3fcc9feae6 100644 --- a/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs +++ b/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs @@ -9,8 +9,11 @@ namespace TestProject.RuntimeTests { - [TestFixture(NetworkTopologyTypes.ClientServer)] - [TestFixture(NetworkTopologyTypes.DistributedAuthority)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)] +#endif public class SceneObjectsNotDestroyedOnShutdownTest : NetcodeIntegrationTest { protected override int NumberOfClients => 0; @@ -20,7 +23,7 @@ public class SceneObjectsNotDestroyedOnShutdownTest : NetcodeIntegrationTest private Scene m_TestScene; private WaitForSeconds m_DefaultWaitForTick = new(1.0f / 30); - public SceneObjectsNotDestroyedOnShutdownTest(NetworkTopologyTypes topology) : base(topology) { } + public SceneObjectsNotDestroyedOnShutdownTest(NetworkTopologyTypes topology, HostOrServer hostOrServer) : base(topology, hostOrServer) { } [UnityTest] public IEnumerator SceneObjectsNotDestroyedOnShutdown() diff --git a/testproject/Assets/Tests/Runtime/SenderIdTests.cs b/testproject/Assets/Tests/Runtime/SenderIdTests.cs index 92ebf2f4a1..e42fe4ae84 100644 --- a/testproject/Assets/Tests/Runtime/SenderIdTests.cs +++ b/testproject/Assets/Tests/Runtime/SenderIdTests.cs @@ -10,12 +10,18 @@ namespace TestProject.RuntimeTests { + [TestFixture(HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(HostOrServer.UnifiedHost)] +#endif public class SenderIdTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; private NetworkManager FirstClient => m_ClientNetworkManagers[0]; private NetworkManager SecondClient => m_ClientNetworkManagers[1]; + + public SenderIdTests(HostOrServer hostOrServer) : base(hostOrServer) { } [UnityTest] public IEnumerator WhenSendingMessageFromServerToClient_SenderIdIsCorrect() diff --git a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs index c16cceda66..cd730b2099 100644 --- a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs +++ b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs @@ -9,8 +9,11 @@ namespace TestProject.RuntimeTests { - [TestFixture(NetworkTopologyTypes.DistributedAuthority)] - [TestFixture(NetworkTopologyTypes.ClientServer)] + [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)] + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)] +#if UNIFIED_NETCODE + [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)] +#endif public class ServerDisconnectsClientTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; @@ -21,7 +24,7 @@ protected override bool UseCMBService() return false; } - public ServerDisconnectsClientTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { } + public ServerDisconnectsClientTest(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { } protected override void OnCreatePlayerPrefab() { diff --git a/testproject/Assets/Tests/Runtime/TestProject.Runtime.Tests.asmdef b/testproject/Assets/Tests/Runtime/TestProject.Runtime.Tests.asmdef index 1bd9925469..c7028b927d 100644 --- a/testproject/Assets/Tests/Runtime/TestProject.Runtime.Tests.asmdef +++ b/testproject/Assets/Tests/Runtime/TestProject.Runtime.Tests.asmdef @@ -34,6 +34,11 @@ "name": "com.unity.addressables", "expression": "", "define": "TESTPROJECT_USE_ADDRESSABLES" + }, + { + "name": "com.unity.netcode", + "expression": "1.10.1", + "define": "UNIFIED_NETCODE" } ], "noEngineReferences": false From 34c7a4b0fa9457ee22067180694d7d783701507a Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Wed, 6 May 2026 21:47:58 -0500 Subject: [PATCH 2/4] Fix DontDestroyOnLoadTest --- .../Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs | 3 +++ .../Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index bf067389ae..0bd0522662 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1804,6 +1804,9 @@ protected void DestroySceneNetworkObjects() ghostAdapter.prefabReference = null; Object.Destroy(prefabReference); Object.Destroy(ghostAdapter); + // Only normally destroy hybrid-spawned objects. + Object.Destroy(networkObject.gameObject); + continue; } // Destroy the GameObject that holds the NetworkObject component diff --git a/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs b/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs index 0ab1566882..488dd63fb6 100644 --- a/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs +++ b/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs @@ -23,7 +23,7 @@ public uint CurrentPing /// /// When enabled, we move ourself to the DontDestroyOnLoad scene /// - private void OnEnable() + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) { DontDestroyOnLoad(this); } From 5cb5cd83cf87b730a391c7c77237ee06ff1810a6 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Wed, 6 May 2026 21:52:19 -0500 Subject: [PATCH 3/4] [MTT-14843] Refactor sending and receiving of N4E RPCs in UnifiedNetcodeTransport to not use entities (#3943) Refactor sending and receiving of RPCs to not use entities, thereby preserving send/receive order without having to encode an order value and reorder messages on the receive side. --- .../Runtime/Core/NetworkManager.cs | 3 +- .../Unified/UnifiedNetcodeTransport.cs | 125 +++++++++--------- 2 files changed, 65 insertions(+), 63 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b3bba1c4b4..9715756023 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -472,9 +472,10 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // This should be invoked just prior to the MessageManager processes its outbound queue. SceneManager.CheckForAndSendNetworkObjectSceneChanged(); - +#if !UNIFIED_NETCODE // Process outbound messages MessageManager.ProcessSendQueues(); +#endif // Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed. MetricsManager.UpdateMetrics(); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs index f8d97072fb..eadfd1db26 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs @@ -43,37 +43,41 @@ public static NativeArray ToNativeArray(in FixedBytes1280 data) } } + internal struct TransportRpcData : IBufferElementData + { + public FixedBytes1280 Buffer; + } + [BurstCompile] internal struct TransportRpc : IOutOfBandRpcCommand, IRpcCommandSerializer { - public FixedBytes1280 Buffer; - public ulong Order; + public TransportRpcData Value; public unsafe void Serialize(ref DataStreamWriter writer, in RpcSerializerState state, in TransportRpc data) { - writer.WriteULong(data.Order); - writer.WriteInt(data.Buffer.Length); - var span = new Span(FixedBytes1280.GetUnsafePtr(data.Buffer), data.Buffer.Length); + writer.WriteInt(data.Value.Buffer.Length); + var span = new Span(FixedBytes1280.GetUnsafePtr(data.Value.Buffer), data.Value.Buffer.Length); writer.WriteBytes(span); } public unsafe void Deserialize(ref DataStreamReader reader, in RpcDeserializerState state, ref TransportRpc data) { - data.Order = reader.ReadULong(); var length = reader.ReadInt(); - data.Buffer = new FixedBytes1280 + data.Value.Buffer = new FixedBytes1280 { Length = length }; - var span = new Span(FixedBytes1280.GetUnsafePtr(data.Buffer), length); + var span = new Span(FixedBytes1280.GetUnsafePtr(data.Value.Buffer), length); reader.ReadBytes(span); } [BurstCompile(DisableDirectCall = true)] private static void InvokeExecute(ref RpcExecutor.Parameters parameters) { - RpcExecutor.ExecuteCreateRequestComponent(ref parameters); + var element = new TransportRpc(); + element.Deserialize(ref parameters.Reader, parameters.DeserializerState, ref element); + parameters.CommandBuffer.AppendToBuffer(parameters.JobIndex, parameters.Connection, element.Value); } private static readonly PortableFunctionPointer k_InvokeExecuteFunctionPointer = new PortableFunctionPointer(InvokeExecute); @@ -115,9 +119,19 @@ public void OnUpdate(ref SystemState state) } } + [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation | WorldSystemFilterFlags.ClientSimulation | WorldSystemFilterFlags.ThinClientSimulation)] + [UpdateInGroup(typeof(SimulationSystemGroup), OrderLast = true)] + [UpdateBefore(typeof(RpcSystem))] internal partial class UnifiedNetcodeUpdateSystem : SystemBase { + public void OnCreate(ref SystemState state) + { + state.RequireForUpdate(); + state.RequireForUpdate(); + } + public UnifiedNetcodeTransport Transport; + public NetworkManager NetworkManager; public List DisconnectQueue = new List(); @@ -125,23 +139,39 @@ public void Disconnect(Connection connection) { DisconnectQueue.Add(connection); } + + public void SendRpc(TransportRpc rpc) + { + var rpcQueue = SystemAPI.GetSingleton().GetRpcQueue(); + var ghostInstance = GetComponentLookup(); + foreach (var rpcDataStreamBuffer in SystemAPI.Query>()) + { + rpcQueue.Schedule(rpcDataStreamBuffer, ghostInstance, rpc); + } + } protected override void OnUpdate() { + NetworkManager.MessageManager.ProcessSendQueues(); + using var commandBuffer = new EntityCommandBuffer(Allocator.Temp); - foreach (var (request, rpc, entity) in SystemAPI.Query, RefRO>().WithEntityAccess()) + foreach(var (networkId, _, entity) in SystemAPI.Query, RefRO>().WithEntityAccess()) { - var connectionId = SystemAPI.GetComponent(request.ValueRO.SourceConnection).Value; - - var buffer = rpc.ValueRO.Buffer; - try + var connectionId = networkId.ValueRO.Value; + DynamicBuffer rpcs = EntityManager.GetBuffer(entity); + foreach (var rpc in rpcs) { - Transport.DispatchMessage(connectionId, buffer, rpc.ValueRO.Order); - } - finally - { - commandBuffer.DestroyEntity(entity); + var buffer = rpc.Buffer; + try + { + Transport.DispatchMessage(connectionId, buffer); + } + catch(Exception e) + { + Debug.LogException(e); + } } + rpcs.Clear(); } foreach (var connection in DisconnectQueue) @@ -171,34 +201,15 @@ private class ConnectionInfo public BatchedSendQueue SendQueue; public BatchedReceiveQueue ReceiveQueue; public Connection Connection; - public ulong LastSent; - public ulong LastReceived; public Dictionary DeferredMessages; } private Dictionary m_Connections; - internal void DispatchMessage(int connectionId, in FixedBytes1280 buffer, ulong order) + internal void DispatchMessage(int connectionId, in FixedBytes1280 buffer) { var connectionInfo = m_Connections[connectionId]; - if (order <= connectionInfo.LastReceived) - { - Debug.LogWarning("Received duplicate message, ignoring."); - return; - } - - if (order != connectionInfo.LastReceived + 1) - { - if (connectionInfo.DeferredMessages == null) - { - connectionInfo.DeferredMessages = new Dictionary(); - } - - connectionInfo.DeferredMessages[order] = buffer; - return; - } - using var arr = FixedBytes1280.ToNativeArray(buffer); var reader = new DataStreamReader(arr); if (connectionInfo.ReceiveQueue == null) @@ -209,20 +220,7 @@ internal void DispatchMessage(int connectionId, in FixedBytes1280 buffer, ulong { connectionInfo.ReceiveQueue.PushReader(reader); } - - connectionInfo.LastReceived = order; - if (connectionInfo.DeferredMessages != null) - { - var next = order + 1; - while (connectionInfo.DeferredMessages.Remove(next, out var nextBuffer)) - { - reader = new DataStreamReader(FixedBytes1280.ToNativeArray(nextBuffer)); - connectionInfo.ReceiveQueue.PushReader(reader); - connectionInfo.LastReceived = next; - ++next; - } - } - + var message = connectionInfo.ReceiveQueue.PopMessage(); while (message.Count != 0) { @@ -243,18 +241,15 @@ public override unsafe void Send(ulong clientId, ArraySegment payload, Net while (!connectionInfo.SendQueue.IsEmpty) { - var rpc = new TransportRpc - { - Buffer = new FixedBytes1280(), - }; + var rpc = new TransportRpc(); - var writer = new DataStreamWriter(FixedBytes1280.GetUnsafePtr(rpc.Buffer), k_MaxPacketSize); + var writer = new DataStreamWriter(FixedBytes1280.GetUnsafePtr(rpc.Value.Buffer), k_MaxPacketSize); var amount = connectionInfo.SendQueue.FillWriterWithBytes(ref writer, k_MaxPacketSize); - rpc.Buffer.Length = amount; - rpc.Order = ++connectionInfo.LastSent; - - connectionInfo.Connection.SendOutOfBandMessage(rpc); + rpc.Value.Buffer.Length = amount; + + var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); + updateSystem.SendRpc(rpc); connectionInfo.SendQueue.Consume(amount); } @@ -278,6 +273,8 @@ private void OnClientConnectedToServer(Connection connection, NetCodeConnectionE }; m_ServerClientId = connection.NetworkId.Value; InvokeOnTransportEvent(NetworkEvent.Connect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup); + var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); + updateSystem.EntityManager.AddBuffer(connection.ConnectionEntity); } private void OnServerNewClientConnection(Connection connection, NetCodeConnectionEvent connectionEvent) @@ -289,6 +286,8 @@ private void OnServerNewClientConnection(Connection connection, NetCodeConnectio Connection = connection }; ; InvokeOnTransportEvent(NetworkEvent.Connect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup); + var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); + updateSystem.EntityManager.AddBuffer(connection.ConnectionEntity); } private const string k_InvalidRpcMessage = "An invalid RPC was received"; @@ -376,6 +375,7 @@ public override bool StartClient() NetCode.Netcode.Client.OnDisconnect = OnClientDisconnectFromServer; var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); updateSystem.Transport = this; + updateSystem.NetworkManager = m_NetworkManager; return true; } @@ -390,6 +390,7 @@ public override bool StartServer() NetCode.Netcode.Server.OnDisconnect = OnServerClientDisconnected; var updateSystem = NetCode.Netcode.GetWorld(true).GetExistingSystemManaged(); updateSystem.Transport = this; + updateSystem.NetworkManager = m_NetworkManager; return true; } From 7a92da28943404b4d8307b17e919123dab6adcab Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 7 May 2026 11:34:31 -0500 Subject: [PATCH 4/4] Post-merge fixes --- .../Runtime/Core/NetworkManager.cs | 11 ++++++++--- .../Transports/Unified/UnifiedNetcodeTransport.cs | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 9734dfc63e..5311dc7589 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -471,9 +471,14 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) // This should be invoked just prior to the MessageManager processes its outbound queue. SceneManager.CheckForAndSendNetworkObjectSceneChanged(); -#if !UNIFIED_NETCODE - // Process outbound messages - MessageManager.ProcessSendQueues(); +#if UNIFIED_NETCODE + if (!NetworkConfig.Prefabs.HasGhostPrefabs) + { +#endif + // Process outbound messages + MessageManager.ProcessSendQueues(); +#if UNIFIED_NETCODE + } #endif // Metrics update needs to be driven by NetworkConnectionManager's update to assure metrics are dispatched after the send queue is processed. diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs index 1b579a23ff..2b3d7e3b42 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs @@ -144,7 +144,7 @@ public void SendRpc(TransportRpc rpc) { var rpcQueue = SystemAPI.GetSingleton().GetRpcQueue(); var ghostInstance = GetComponentLookup(); - foreach (var rpcDataStreamBuffer in SystemAPI.Query>()) + foreach (var rpcDataStreamBuffer in SystemAPI.Query>()) { rpcQueue.Schedule(rpcDataStreamBuffer, ghostInstance, rpc); } @@ -273,7 +273,7 @@ private void OnClientConnectedToServer(Connection connection, NetCodeConnectionE }; m_ServerClientId = connection.NetworkId.Value; InvokeOnTransportEvent(NetworkEvent.Connect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup); - var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); + var updateSystem = m_NetworkManager.NetcodeWorld.GetExistingSystemManaged(); updateSystem.EntityManager.AddBuffer(connection.ConnectionEntity); } @@ -286,7 +286,7 @@ private void OnServerNewClientConnection(Connection connection, NetCodeConnectio Connection = connection }; ; InvokeOnTransportEvent(NetworkEvent.Connect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup); - var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); + var updateSystem = m_NetworkManager.NetcodeWorld.GetExistingSystemManaged(); updateSystem.EntityManager.AddBuffer(connection.ConnectionEntity); }