From ac625b243e627c2e8ae5cb03ce2ccbd6b964c9d0 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 5 Mar 2026 14:39:59 -0600 Subject: [PATCH 1/6] update Fixes for connection sequence where clients don't announce they are ready to receive snapshots until fully synchronized by NGO. Fixes for removing pending ghosts and ghost spawn data (especially when being destroyed). Fixes for NetworkSceneManager detecting pending ghosts as in-scene placed. Fixes for NetworkSpawnManager.RegisterGhostPendingSpawn trying to double process triggers and/or process triggers when it should not. --- .../Components/Helpers/NetworkObjectBridge.cs | 8 ++-- .../Helpers/UnifiedUpdateConnections.cs | 37 ++++++++++-------- .../Runtime/Core/NetworkObject.cs | 19 ++++----- .../SceneManagement/NetworkSceneManager.cs | 6 +++ .../Runtime/Spawning/NetworkSpawnManager.cs | 39 +++++++++++++------ 5 files changed, 70 insertions(+), 39 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs index 41dddaaf12..2f4c2cb006 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs @@ -7,15 +7,17 @@ namespace Unity.Netcode { /// - /// TODO-UNIFIED: Would need to be reviewed for alternate ways of handling this. + /// TODO-UNIFIED: Needs further peer review and exploring alternate ways of handling this. /// /// /// If used, we most likely would make this internal /// public partial class NetworkObjectBridge : GhostBehaviour { - public Action NetworkObjectIdChanged; - + /// + /// This is used to link data to + /// N4E-spawned hybrid prefab instances. + /// internal GhostField NetworkObjectId = new GhostField(); public void SetNetworkObjectId(ulong value) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs index 4237869496..0b2d6bd9d2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs @@ -13,8 +13,6 @@ public struct NetcodeConnection internal Entity Entity; public int NetworkId; - internal float ConnectedTime; - public bool IsServer => World.IsServer(); public void GoInGame() { @@ -38,6 +36,8 @@ protected override void OnUpdate() { var isServer = World.IsServer(); var commandBuffer = new EntityCommandBuffer(Allocator.Temp); + var networkManager = NetworkManager.Singleton; + foreach (var (networkId, connectionState, entity) in SystemAPI.Query().WithNone().WithEntityAccess()) { commandBuffer.RemoveComponent(entity); @@ -50,16 +50,12 @@ protected override void OnUpdate() m_TempConnections.Clear(); - + // TODO: We should figure out how to associate the N4E NetworkId with the NGO ClientId foreach (var (networkId, entity) in SystemAPI.Query().WithAll().WithNone().WithEntityAccess()) { - // TODO-Unified: For new connections, we have a delay before the N4E in-game state for the client to provide time for the NGO side of the client to synchronize. - // Note: Once both are using the same transport we should be able to get the transport id and determine the NGO assigned client-id and at that point once the - // client has signaled that it has synchronized (or has been sent the synchronization data) we finalize the in-game connection state (or something along those lines). if (!m_NewConnections.ContainsKey(networkId.Value)) { - var delayTime = 0.0f;// isServer ? 0.2f : 0.1f; - var newConnection = new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value, ConnectedTime = UnityEngine.Time.realtimeSinceStartup + delayTime}; + var newConnection = new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }; m_NewConnections.Add(networkId.Value, newConnection); } } @@ -69,8 +65,9 @@ protected override void OnUpdate() { foreach (var entry in m_NewConnections) { - // Check if the delay time has passed. - if (entry.Value.ConnectedTime < UnityEngine.Time.realtimeSinceStartup) + // Server: always connect + // Client: wait until we have synchronized before announcing we are ready to receive snapshots + if (networkManager.IsServer || (!networkManager.IsServer && networkManager.IsConnectedClient)) { // Set the connection in-game commandBuffer.AddComponent(entry.Value.Entity); @@ -87,21 +84,29 @@ protected override void OnUpdate() } m_TempConnections.Clear(); + // If the local NetworkManager is shutting down or no longer connected, then + // make sure we have disconnected all known connections. + if (networkManager.ShutdownInProgress || !networkManager.IsListening) + { + foreach (var (networkId, entity) in SystemAPI.Query().WithEntityAccess()) + { + commandBuffer.RemoveComponent(entity); + NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }); + } + } commandBuffer.Playback(EntityManager); } + /// + /// Always disconnect all known connections when being destroyed. + /// protected override void OnDestroy() { var commandBuffer = new EntityCommandBuffer(Allocator.Temp); foreach (var (networkId, entity) in SystemAPI.Query().WithEntityAccess()) { commandBuffer.RemoveComponent(entity); - // TODO: maybe disconnect reason? - m_TempConnections.Add(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }); - } - foreach (var con in m_TempConnections) - { - NetworkManager.OnNetCodeDisconnect?.Invoke(con); + NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }); } commandBuffer.Playback(EntityManager); base.OnDestroy(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 92bbffa48a..b1b98fa0c8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1732,8 +1732,15 @@ private void OnDestroy() return; } + var spawnManager = NetworkManager.SpawnManager; + // Always attempt to remove from scene changed updates - networkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); + spawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); + +#if UNIFIED_NETCODE + spawnManager?.GhostsPendingSpawn.Remove(NetworkObjectId); + spawnManager?.GhostsPendingSynchronization.Remove(NetworkObjectId); +#endif if (IsSpawned && !networkManager.ShutdownInProgress) { @@ -1763,11 +1770,11 @@ private void OnDestroy() } } - if (networkManager.SpawnManager != null && networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) + if (spawnManager != null && spawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { if (this == networkObject) { - networkManager.SpawnManager.OnDespawnObject(networkObject, false); + spawnManager.OnDespawnObject(networkObject, false); } } } @@ -3846,7 +3853,6 @@ private void InitGhost() { Debug.Log($"[{nameof(NetworkObject)}] GhostBridge {name} detected and instantiated."); } - NetworkObjectBridge.NetworkObjectIdChanged += OnNetworkObjectIdChanged; if (NetworkObjectBridge.NetworkObjectId.Value != 0) { RegisterGhostBridge(); @@ -3866,11 +3872,6 @@ internal void RegisterGhostBridge() NetworkManager.SpawnManager.RegisterGhostPendingSpawn(this, NetworkObjectBridge.NetworkObjectId.Value); } } - - private void OnNetworkObjectIdChanged(ulong networkObjectId) - { - RegisterGhostBridge(); - } #endif /// diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 0e75f0c92f..d4930c6026 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2740,8 +2740,14 @@ internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearSceneP var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash; var sceneHandle = networkObjectInstance.gameObject.scene.handle; // 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) +#if UNIFIED_NETCODE + if (!networkObjectInstance.HasGhost && networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == NetworkManager || + networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle) +#else if (networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == NetworkManager || networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle) + +#endif { if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash)) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 9168761037..c1b8f37750 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -43,19 +43,35 @@ internal void RegisterGhostPendingSpawn(NetworkObject networkObject, ulong netwo if (GhostsPendingSpawn.TryAdd(networkObjectId, networkObject)) { // TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn. - // For now, move any pending object into the DDOL. - UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); + // Edge-Case scenario: During initial client synchronization (i.e. !NetworkManager.IsConnectedClient). + // + // Description: A client can receive snapshots before finishing the NGO synchronization process. + // This is when an edge case scenario can happen where the initial NGO synchronization information + // can include new scenes to load. If one of those scenes is configured to load in SingleMode, then + // any instantiated ghosts pending synchronization would be instantiated in whatever the currently + // active scene was when the client was processing the synchronization data. If the ghosts pending + // synchrpnization are in the currently active scene when the new scene is loaded in SingleMode, then + // they would be destroyed. + // + // Current Fix: + // If the client is not yet synchronized, then any ghost pending spawn get migrated into the DDOL. + // + // Further review: + // We need to make sure that we are migrating NetworkObjects into their assigned scene (if scene + // management is enabled). Currently, we assume all instances were in the DDOL and just migrate + // them into the currently active scene upon spawn. + if (!NetworkManager.IsConnectedClient && !GhostsPendingSynchronization.ContainsKey(networkObjectId)) + { + UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); + } + else // There is matching spawn data for this pending Ghost, process the pending spawn for this hybrid instance. + { + NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, networkObjectId); + } } - - NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, networkObjectId); - if (GhostsArePendingSynchronization && GhostsPendingSynchronization.ContainsKey(networkObjectId)) + else { - // TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn. - // NOTE: We might be able to use the NetworkSceneHandle to get the associated local scene handle to which we can use to get the targeted scene. - UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(networkObject.gameObject, UnityEngine.SceneManagement.SceneManager.GetActiveScene()); - - // When the object is spawned, it will invoke GetGhostNetworkObjectForSpawn below which removes the entry from GhostsPendingSpawn - ProcessGhostPendingSynchronization(networkObjectId); + Debug.LogError($"[{networkObject.name}-{networkObjectId}] Has already been registered as a pending ghost!"); } } @@ -109,6 +125,7 @@ internal void ProcessGhostPendingSynchronization(ulong networkObjectId, bool rem //} if (removeUponSpawn) { + GhostsPendingSynchronization.Remove(networkObjectId); GhostsArePendingSynchronization = GhostsPendingSynchronization.Count > 0; ghostPendingSynch.Buffer.Dispose(); } From 038b157f5bfbf3d0aafd560bff75a13a32a1dc98 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 5 Mar 2026 16:42:25 -0600 Subject: [PATCH 2/6] update & fixes Some adjustments to the POC Transport. Some fixes. --- .../Components/Helpers/NetworkObjectBridge.cs | 13 +--- .../Helpers/UnifiedUpdateConnections.cs | 9 --- .../Runtime/Core/NetworkManager.cs | 27 +++++--- .../Runtime/Core/NetworkObject.cs | 6 +- .../Messaging/Messages/SceneEventMessage.cs | 9 --- .../Runtime/Spawning/NetworkSpawnManager.cs | 10 ++- .../Unified/UnifiedNetcodeTransport.cs | 65 ++++++------------- 7 files changed, 50 insertions(+), 89 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs index dae048fcde..db13f5ca2c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs @@ -82,21 +82,10 @@ public override bool Initialize(string defaultWorldName) return true; } - - public static void StopClient() - { - ClientWorld.Dispose(); - ClientWorlds.Remove(ClientWorld); - } - - public static void StopServer() - { - ServerWorld.Dispose(); - ServerWorlds.Remove(ServerWorld); - } ~UnifiedBootStrap() { + World = null; Instance = null; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs index addccd84e8..faa1b12d67 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs @@ -31,15 +31,6 @@ internal partial class UnifiedUpdateConnections : SystemBase private List m_TempConnections = new List(); private Dictionary m_NewConnections = new Dictionary(); - - public void MarkSync(int NetworkId) - { - if (m_NewConnections.TryGetValue(NetworkId, out var connection)) - { - connection.IsSynced = true; - m_NewConnections[NetworkId] = connection; - } - } protected override void OnUpdate() { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 22f6527caf..2cd7c4481e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1204,11 +1204,11 @@ internal void Initialize(bool server) // UnityTransport dependencies are then initialized RealTimeProvider = ComponentFactory.Create(this); - + #if UNIFIED_NETCODE NetworkConfig.NetworkTransport = gameObject.AddComponent(); #endif - + MetricsManager.Initialize(this); { @@ -1320,18 +1320,27 @@ private System.Collections.IEnumerator WaitForHybridPrefabRegistration(StartType { NetworkLog.LogInfo($"[{nameof(WaitForHybridPrefabRegistration)}] 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. + NetCode.Netcode.Reset(); + + /// !! Initialize worlds here !! + /// Worlds are created here: DefaultWorldInitialization.Initialize("Default World", false); - var waitTime = new WaitForSeconds(0.016f); - // This should not be needed at this point, but here in the event something changes. - while (NetworkConfig.Prefabs.HasPendingGhostPrefabs) + + // This should not be needed at this point, but this is here in the event something changes. + if (NetworkConfig.Prefabs.HasPendingGhostPrefabs) { - if (LogLevel <= LogLevel.Developer) + NetworkLog.LogWarning($"[{nameof(WaitForHybridPrefabRegistration)}] !!!!! (Ghosts are still pending registration) !!!!!"); + var waitTime = new WaitForSeconds(0.016f); + while (NetworkConfig.Prefabs.HasPendingGhostPrefabs) { - NetworkLog.LogInfo($"[{nameof(WaitForHybridPrefabRegistration)}] Ghosts are still pending registration!"); + NetworkConfig.Prefabs.RegisterGhostPrefabs(this); + yield return waitTime; } - NetworkConfig.Prefabs.RegisterGhostPrefabs(this); - yield return waitTime; } + if (LogLevel <= LogLevel.Developer) { NetworkLog.LogInfo($"[{nameof(WaitForHybridPrefabRegistration)}] All hybrid prefabs have been registered!"); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index b1b98fa0c8..de08d8b4c6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1740,9 +1740,11 @@ private void OnDestroy() #if UNIFIED_NETCODE spawnManager?.GhostsPendingSpawn.Remove(NetworkObjectId); spawnManager?.GhostsPendingSynchronization.Remove(NetworkObjectId); -#endif - + // N4E controls this on the client, allow this if there is a ghost + if (IsSpawned && !HasGhost && !networkManager.ShutdownInProgress) +#else if (IsSpawned && !networkManager.ShutdownInProgress) +#endif { // An authorized destroy is when done by the authority instance or done due to a scene event and the NetworkObject // was marked as destroy pending scene event (which means the destroy with scene property was set). diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs index 320dc2ba1a..c0452146b4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs @@ -1,7 +1,3 @@ - -using Unity.Netcode.Components; -using Unity.Netcode.Unified; - namespace Unity.Netcode { // Todo: Would be lovely to get this one nicely formatted with all the data it sends in the struct @@ -30,11 +26,6 @@ public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; networkManager.SceneManager.HandleSceneEvent(context.SenderId, m_ReceivedData); - -#if UNIFIED_NETCODE - var unifiedConnectionSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); - unifiedConnectionSystem.MarkSync((int)context.SenderId); -#endif } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index c1b8f37750..f870a7fb7a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -83,10 +83,14 @@ internal NetworkObject GetGhostNetworkObjectForSpawn(ulong networkObjectId) return null; } var networkObject = GhostsPendingSpawn[networkObjectId]; + GhostsPendingSpawn.Remove(networkObjectId); - // TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn. - // NOTE: We might be able to use the NetworkSceneHandle to get the associated local scene handle to which we can use to get the targeted scene. - UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(networkObject.gameObject, UnityEngine.SceneManagement.SceneManager.GetActiveScene()); + if (networkObject != null) + { + // TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn. + // NOTE: We might be able to use the NetworkSceneHandle to get the associated local scene handle to which we can use to get the targeted scene. + UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(networkObject.gameObject, UnityEngine.SceneManagement.SceneManager.GetActiveScene()); + } return networkObject; } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs index 6479526206..743145dff6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs @@ -1,19 +1,17 @@ #if UNIFIED_NETCODE using System; using System.Collections.Generic; -using System.Text; using Unity.Burst; using Unity.Burst.Intrinsics; using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; using Unity.NetCode; using Unity.Netcode.Transports.UTP; using Unity.Networking.Transport; -using UnityEngine; namespace Unity.Netcode.Unified { + [BurstCompile] internal struct TransportRpc : IRpcCommand, IRpcCommandSerializer { public FixedList4096Bytes Buffer; @@ -31,8 +29,10 @@ public unsafe void Deserialize(ref DataStreamReader reader, in RpcDeserializerSt { data.Order = reader.ReadULong(); var length = reader.ReadInt(); - data.Buffer = new FixedList4096Bytes(); - data.Buffer.Length = length; + data.Buffer = new FixedList4096Bytes() + { + Length = length + }; var span = new Span(data.Buffer.GetUnsafePtr(), length); reader.ReadBytes(span); } @@ -43,29 +43,29 @@ private static void InvokeExecute(ref RpcExecutor.Parameters parameters) RpcExecutor.ExecuteCreateRequestComponent(ref parameters); } - static readonly PortableFunctionPointer InvokeExecuteFunctionPointer = new PortableFunctionPointer(InvokeExecute); + private static readonly PortableFunctionPointer k_InvokeExecuteFunctionPointer = new PortableFunctionPointer(InvokeExecute); public PortableFunctionPointer CompileExecute() { - return InvokeExecuteFunctionPointer; + return k_InvokeExecuteFunctionPointer; } } [UpdateInGroup(typeof(RpcCommandRequestSystemGroup))] [CreateAfter(typeof(RpcSystem))] [BurstCompile] - partial struct TransportRpcCommandRequestSystem : ISystem + internal partial struct TransportRpcCommandRequestSystem : ISystem { private RpcCommandRequest m_Request; [BurstCompile] - struct SendRpc : IJobChunk + internal struct SendRpc : IJobChunk { - public RpcCommandRequest.SendRpcData data; + public RpcCommandRequest.SendRpcData Data; public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) { - data.Execute(chunk, unfilteredChunkIndex); + Data.Execute(chunk, unfilteredChunkIndex); } } @@ -77,7 +77,7 @@ public void OnCreate(ref SystemState state) [BurstCompile] public void OnUpdate(ref SystemState state) { - var sendJob = new SendRpc { data = m_Request.InitJobData(ref state) }; + var sendJob = new SendRpc { Data = m_Request.InitJobData(ref state) }; state.Dependency = sendJob.Schedule(m_Request.Query, state.Dependency); } } @@ -128,10 +128,6 @@ internal class UnifiedNetcodeTransport : NetworkTransport private int m_ServerClientId = -1; public override ulong ServerClientId => (ulong)m_ServerClientId; - private bool m_IsClient; - private bool m_IsServer; - private bool m_StartedServerWorld = false; - private bool m_StartedClientWorld = false; private NetworkManager m_NetworkManager; private IRealTimeProvider m_RealTimeProvider; @@ -265,12 +261,6 @@ private void OnServerClientDisconnected(Connection connection, NetCodeConnection public override bool StartClient() { - if (!UnifiedBootStrap.HasClientWorlds) - { - UnifiedBootStrap.CreateClientWorld("ClientWorld"); - m_StartedClientWorld = true; - } - NetCode.Netcode.Client.OnConnect = OnClientConnectedToServer; NetCode.Netcode.Client.OnDisconnect = OnClientDisconnectFromServer; var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); @@ -283,17 +273,9 @@ public override bool StartClient() public override bool StartServer() { - if (!UnifiedBootStrap.HasServerWorld) + foreach (var connection in NetCode.Netcode.Server.Connections) { - UnifiedBootStrap.CreateServerWorld("ServerWorld"); - m_StartedClientWorld = true; - } - else - { - foreach (var connection in NetCode.Netcode.Server.Connections) - { - OnServerNewClientConnection(connection, default); - } + OnServerNewClientConnection(connection, default); } NetCode.Netcode.Server.OnConnect = OnServerNewClientConnection; @@ -326,24 +308,17 @@ public override ulong GetCurrentRtt(ulong clientId) return (ulong)m_Connections[(int)transportId].Connection.RTT; } - public override void Shutdown() - { - if (m_StartedClientWorld) - { - UnifiedBootStrap.StopClient(); - } - if (m_StartedServerWorld) - { - UnifiedBootStrap.StopServer(); - } - } - public override void Initialize(NetworkManager networkManager = null) { m_Connections = new Dictionary(); m_RealTimeProvider = networkManager.RealTimeProvider; m_NetworkManager = networkManager; } + + public override void Shutdown() + { + + } } } -#endif \ No newline at end of file +#endif From 1e75ba1bf0e955a836e5a10de5e593088bd423bc Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 5 Mar 2026 17:13:45 -0600 Subject: [PATCH 3/6] fix Wrapping reference to namespace that only exists when in unified mode. --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 2cd7c4481e..5175d1031e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -8,7 +8,9 @@ #endif using Unity.Netcode.Components; using Unity.Netcode.Runtime; +#if UNIFIED_NETCOE using Unity.Netcode.Unified; +#endif using UnityEngine; #if UNITY_EDITOR using UnityEditor; From be7b59cc46313da0a9fb155b903cc36fae0f0663 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 5 Mar 2026 17:30:32 -0600 Subject: [PATCH 4/6] update fixing spelling of UNIFIED_NETCODE --- com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 5175d1031e..b8c29a67e4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -8,7 +8,7 @@ #endif using Unity.Netcode.Components; using Unity.Netcode.Runtime; -#if UNIFIED_NETCOE +#if UNIFIED_NETCODE using Unity.Netcode.Unified; #endif using UnityEngine; From 6f575ae386e30cfec897c2dacaacd84bb848b5e1 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 5 Mar 2026 20:30:19 -0600 Subject: [PATCH 5/6] update Increasing max RPC size to 1340. Re-enabling the UnifiedBootStrap.AutoConnectPort. (N4E handles the connection) Removing the initial listen and/or connection within the UnifiedTransport (N4E handle this). Moving unified validation invocation to be after OnValidation early exit checks. --- .../Components/Helpers/NetworkObjectBridge.cs | 2 +- .../Runtime/Core/NetworkObject.cs | 8 ++++---- .../Runtime/Spawning/NetworkSpawnManager.cs | 4 ++++ .../Transports/Unified/UnifiedNetcodeTransport.cs | 12 +++--------- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs index db13f5ca2c..3d857b4c67 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs @@ -43,7 +43,7 @@ public override bool Initialize(string defaultWorldName) { var networkManager = NetworkManager.Singleton; Instance = this; - AutoConnectPort = 0; + AutoConnectPort = Port; if (base.Initialize(defaultWorldName)) { UnityEngine.Debug.LogError($"[{nameof(UnifiedBootStrap)}] Auto-bootstrap is enabled!!! This will break the POC!"); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index de08d8b4c6..e5c96dbc17 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -257,10 +257,6 @@ private static void CheckPrefabStage(PrefabStage prefabStage) /// internal void OnValidate() { -#if UNIFIED_NETCODE - UnifiedValidation(); -#endif - // Always exit early if we are in prefab edit mode and this instance is the // prefab instance within the InContext or InIsolation edit scene. if (s_PrefabInstance == this) @@ -280,6 +276,10 @@ internal void OnValidate() return; } +#if UNIFIED_NETCODE + UnifiedValidation(); +#endif + // Get a global object identifier for this network prefab. var globalId = GlobalObjectId.GetGlobalObjectIdSlow(this); diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index f870a7fb7a..4f9b0fff14 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -67,6 +67,10 @@ internal void RegisterGhostPendingSpawn(NetworkObject networkObject, ulong netwo else // There is matching spawn data for this pending Ghost, process the pending spawn for this hybrid instance. { NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, networkObjectId); + if (GhostsPendingSynchronization.ContainsKey(networkObjectId)) + { + ProcessGhostPendingSynchronization(networkObjectId); + } } } else diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs index 743145dff6..ec10116d90 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs @@ -7,7 +7,6 @@ using Unity.Entities; using Unity.NetCode; using Unity.Netcode.Transports.UTP; -using Unity.Networking.Transport; namespace Unity.Netcode.Unified { @@ -206,9 +205,10 @@ public override unsafe void Send(ulong clientId, ArraySegment payload, Net { Buffer = new FixedList4096Bytes(), }; - var writer = new DataStreamWriter(rpc.Buffer.GetUnsafePtr(), 1024); + + var writer = new DataStreamWriter(rpc.Buffer.GetUnsafePtr(), 1340); - var amount = connectionInfo.SendQueue.FillWriterWithBytes(ref writer, 1024); + var amount = connectionInfo.SendQueue.FillWriterWithBytes(ref writer, 1340); rpc.Buffer.Length = amount; rpc.Order = ++connectionInfo.LastSent; @@ -265,9 +265,6 @@ public override bool StartClient() NetCode.Netcode.Client.OnDisconnect = OnClientDisconnectFromServer; var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); updateSystem.Transport = this; - using var drvQuery = updateSystem.EntityManager.CreateEntityQuery(ComponentType.ReadWrite()); - var driver = drvQuery.GetSingletonRW(); - driver.ValueRW.Connect(updateSystem.EntityManager, NetworkEndpoint.Parse("127.0.0.1", 7979)); return true; } @@ -282,9 +279,6 @@ public override bool StartServer() NetCode.Netcode.Server.OnDisconnect = OnServerClientDisconnected; var updateSystem = NetCode.Netcode.GetWorld(true).GetExistingSystemManaged(); updateSystem.Transport = this; - using var drvQuery = updateSystem.EntityManager.CreateEntityQuery(ComponentType.ReadWrite()); - var driver = drvQuery.GetSingletonRW(); - driver.ValueRW.Listen(NetworkEndpoint.Parse("127.0.0.1", 7979)); return true; } From 13cbe718c7873e3f85dbfd594a057fefc910cf58 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 5 Mar 2026 20:53:58 -0600 Subject: [PATCH 6/6] update Lowering max packet size to 1300. --- .../Runtime/Transports/Unified/UnifiedNetcodeTransport.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs index ec10116d90..225165ab88 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs @@ -124,6 +124,8 @@ protected override void OnUpdate() internal class UnifiedNetcodeTransport : NetworkTransport { + private const int k_MaxPacketSize = 1300; + private int m_ServerClientId = -1; public override ulong ServerClientId => (ulong)m_ServerClientId; @@ -206,9 +208,9 @@ public override unsafe void Send(ulong clientId, ArraySegment payload, Net Buffer = new FixedList4096Bytes(), }; - var writer = new DataStreamWriter(rpc.Buffer.GetUnsafePtr(), 1340); + var writer = new DataStreamWriter(rpc.Buffer.GetUnsafePtr(), k_MaxPacketSize); - var amount = connectionInfo.SendQueue.FillWriterWithBytes(ref writer, 1340); + var amount = connectionInfo.SendQueue.FillWriterWithBytes(ref writer, k_MaxPacketSize); rpc.Buffer.Length = amount; rpc.Order = ++connectionInfo.LastSent;