From 164a71356ab0af9e2032cf12aa7a0e1eae2c94a8 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 23 Apr 2026 14:54:42 -0500 Subject: [PATCH 1/7] Integration tests WIP --- .../Components/Helpers/NetworkObjectBridge.cs | 72 ++++-- .../Helpers/UnifiedUpdateConnections.cs | 98 ++++---- .../Runtime/Components/NetworkTransform.cs | 2 +- .../Runtime/Configuration/NetworkPrefabs.cs | 6 +- .../Connection/NetworkConnectionManager.cs | 3 + .../Runtime/Core/NetworkManager.cs | 17 +- .../Runtime/Core/NetworkObject.cs | 2 +- .../Messaging/NetworkMessageManager.cs | 4 + .../Unified/UnifiedNetcodeTransport.cs | 50 +++- .../UnifiedNetworkTransformTest.cs | 91 +++++++ .../UnifiedNetworkTransformTest.cs.meta | 3 + .../TestHelpers/NetcodeIntegrationTest.cs | 113 +++++++++ .../Unity.Netcode.Runtime.Tests.asmdef | 4 +- testproject/Assets/NetCodeConfig.asset | 76 ++++++ testproject/Assets/NetCodeConfig.asset.meta | 8 + testproject/Packages/manifest.json | 34 +-- testproject/Packages/packages-lock.json | 234 +++++++++++------- .../PackageManagerSettings.asset | 35 +-- .../ProjectSettings/ProjectSettings.asset | 39 +-- 19 files changed, 656 insertions(+), 235 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs.meta create mode 100644 testproject/Assets/NetCodeConfig.asset create mode 100644 testproject/Assets/NetCodeConfig.asset.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs index 103da29d6a..189aba00fd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs @@ -2,10 +2,12 @@ using System; using Unity.Entities; using Unity.NetCode; +using UnityEngine; namespace Unity.Netcode { +#if UNIFIED_NETCODE /// /// TODO-UNIFIED: Needs further peer review and exploring alternate ways of handling this. /// @@ -15,7 +17,7 @@ namespace Unity.Netcode public partial class NetworkObjectBridge : GhostBehaviour { -#if UNITY_EDITOR +#if UNITY_EDITOR && !UNITY_INCLUDE_TESTS [UnityEngine.HideInInspector] [UnityEngine.SerializeField] private bool m_Sorted = false; @@ -52,6 +54,7 @@ public void SetNetworkObjectId(ulong value) NetworkObjectId.Value = value; } } +#endif /// /// TODO-UNIFIED: Would need to be reviewed for alternate ways of handling this. @@ -63,13 +66,20 @@ internal class UnifiedBootStrap : ClientServerBootstrap public static UnifiedBootStrap Instance { get; private set; } public static Action OnInitialized; public static ushort Port = 7979; + public static NetworkManager CurrentNetworkManagerForInitialization; - public static World World { get; private set; } + public static World LastCreatedWorld { get; private set; } + private static int WorldCounter = 0; + public override bool Initialize(string defaultWorldName) { - var networkManager = NetworkManager.Singleton; - Instance = this; + var networkManager = CurrentNetworkManagerForInitialization; + if (networkManager == NetworkManager.Singleton) + { + Instance = this; + } + AutoConnectPort = Port; if (base.Initialize(defaultWorldName)) { @@ -77,32 +87,44 @@ public override bool Initialize(string defaultWorldName) return true; } - World = networkManager.IsServer ? CreateSingleWorldHost("ClientAndServerWorld") : CreateClientWorld("ClientWorld"); - - if (World == null) + if (networkManager != null) { - UnityEngine.Debug.LogError($"[{nameof(UnifiedBootStrap)}] World is null!"); - return false; - } + Debug.Log($"Starting a world for {(networkManager.IsServer ? "Host" : "Client")}"); + LastCreatedWorld = networkManager.IsServer + ? CreateSingleWorldHost($"ClientAndServerWorld {WorldCounter++}") + : CreateClientWorld($"ClientWorld {WorldCounter++}"); - if (!World.IsCreated) - { - UnityEngine.Debug.LogError($"[{nameof(UnifiedBootStrap)}] World was not created!"); - return false; - } + if (LastCreatedWorld == null) + { + UnityEngine.Debug.LogError($"[{nameof(UnifiedBootStrap)}] World is null!"); + return false; + } - if (networkManager.LogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"[{nameof(UnifiedBootStrap)}] Created world: {World.Name}"); - } + if (!LastCreatedWorld.IsCreated) + { + UnityEngine.Debug.LogError($"[{nameof(UnifiedBootStrap)}] World was not created!"); + return false; + } - if (networkManager.NetworkConfig.Prefabs.HasPendingGhostPrefabs) - { - if (networkManager.LogLevel <= LogLevel.Developer) + //if (networkManager.LogLevel <= LogLevel.Developer) { - NetworkLog.LogInfo($"[{nameof(UnifiedBootStrap)}] Registering hybrid prefabs..."); + NetworkLog.LogInfo($"[{nameof(UnifiedBootStrap)}] Created world: {LastCreatedWorld.Name} / {LastCreatedWorld.SequenceNumber}"); } - networkManager.NetworkConfig.Prefabs.RegisterGhostPrefabs(networkManager); + + networkManager.NetcodeWorld = (NetcodeWorld)LastCreatedWorld; + if (networkManager.NetworkConfig.Prefabs.HasPendingGhostPrefabs) + { + if (networkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo($"[{nameof(UnifiedBootStrap)}] Registering hybrid prefabs..."); + } + + networkManager.NetworkConfig.Prefabs.RegisterGhostPrefabs(networkManager); + } + } + else + { + LastCreatedWorld = CreateLocalWorld("LocalWorld"); } OnInitialized?.Invoke(); @@ -112,7 +134,7 @@ public override bool Initialize(string defaultWorldName) ~UnifiedBootStrap() { - World = null; + LastCreatedWorld = 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 faa1b12d67..b7730b49cd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs @@ -3,6 +3,7 @@ using Unity.Collections; using Unity.Entities; using Unity.NetCode; +using UnityEngine; namespace Unity.Netcode.Components { @@ -36,64 +37,75 @@ 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); - m_TempConnections.Add(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }); - } - foreach (var con in m_TempConnections) +// var networkManager = NetworkManager.Singleton; + foreach (var networkManager in GameObject.FindObjectsByType()) { - NetworkManager.OnNetCodeDisconnect?.Invoke(con); - } - - m_TempConnections.Clear(); + foreach (var (networkId, connectionState, entity) in SystemAPI.Query() + .WithNone().WithEntityAccess()) + { + commandBuffer.RemoveComponent(entity); + m_TempConnections.Add(new NetcodeConnection + { World = World, Entity = entity, NetworkId = networkId.Value }); + } - // 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()) - { - if (!m_NewConnections.ContainsKey(networkId.Value)) + foreach (var con in m_TempConnections) { - var newConnection = new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }; - m_NewConnections.Add(networkId.Value, newConnection); + NetworkManager.OnNetCodeDisconnect?.Invoke(con); } - } - // If we have any pending connections - if (m_NewConnections.Count > 0) - { - foreach (var entry in m_NewConnections) + 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()) { - // Server: always connect - // Client: wait until we have synchronized before announcing we are ready to receive snapshots - if (networkManager.IsServer || (!networkManager.IsServer && networkManager.IsConnectedClient)) + if (!m_NewConnections.ContainsKey(networkId.Value)) { - // Set the connection in-game - commandBuffer.AddComponent(entry.Value.Entity); - commandBuffer.AddComponent(entry.Value.Entity, default(ConnectionState)); - NetworkManager.OnNetCodeConnect?.Invoke(entry.Value); - m_TempConnections.Add(entry.Value); + var newConnection = new NetcodeConnection + { World = World, Entity = entity, NetworkId = networkId.Value }; + m_NewConnections.Add(networkId.Value, newConnection); } } - // Remove any connections that have "gone in-game". - foreach (var connection in m_TempConnections) + + // If we have any pending connections + if (m_NewConnections.Count > 0) { - m_NewConnections.Remove(connection.NetworkId); + foreach (var entry in m_NewConnections) + { + // 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); + commandBuffer.AddComponent(entry.Value.Entity, default(ConnectionState)); + NetworkManager.OnNetCodeConnect?.Invoke(entry.Value); + m_TempConnections.Add(entry.Value); + } + } + + // Remove any connections that have "gone in-game". + foreach (var connection in m_TempConnections) + { + m_NewConnections.Remove(connection.NetworkId); + } } - } - 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()) + 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) { - commandBuffer.RemoveComponent(entity); - NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }); + 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); } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 8474b3b415..e74d2e20d1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3741,7 +3741,7 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() /// The internal initialzation method to allow for internal API adjustments /// /// - private void InternalInitialization(bool isOwnershipChange = false) + internal virtual void InternalInitialization(bool isOwnershipChange = false) { #if UNIFIED_NETCODE diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs index f414283c0d..c402adf04c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs @@ -362,7 +362,7 @@ internal void RegisterGhostPrefabs(NetworkManager networkManager) var networkPrefab = m_PendingGhostRegistration[i]; // Returns false if the single world is not available yet - if (NetCode.Netcode.RegisterPrefabSingleWorld(networkPrefab.Prefab, isHost)) + if (NetCode.Netcode.RegisterPrefabSingleWorld(networkPrefab.Prefab, isHost, networkManager.NetcodeWorld)) { Debug.Log($"[{nameof(NetworkPrefabs)}][{nameof(RegisterGhostPrefabs)}] Registered hybrid spawned object: {networkPrefab.Prefab.name}"); m_PendingGhostRegistration.RemoveAt(i); @@ -394,9 +394,9 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab) #if UNIFIED_NETCODE if (networkPrefab.HasGhost) { - HasPendingGhostPrefabs = true; + //HasPendingGhostPrefabs = true; HasGhostPrefabs = true; - m_PendingGhostRegistration.Add(networkPrefab); + //m_PendingGhostRegistration.Add(networkPrefab); } #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 058fb6a38b..eefb1e51da 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -6,6 +6,9 @@ using System.Runtime.CompilerServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; +#if UNIFIED_NETCODE +using Unity.Netcode.Unified; +#endif using Unity.Profiling; using UnityEngine; using Debug = UnityEngine.Debug; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b3bba1c4b4..5f8473d89c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1319,20 +1319,25 @@ private bool CanStart(StartType type) return true; } + public NetcodeWorld NetcodeWorld { get; internal set; } + #if UNIFIED_NETCODE private System.Collections.IEnumerator WaitForHybridPrefabRegistration(StartType startType) { - if (NetCode.Netcode.IsActive) + if (this == Singleton) { - NetworkLog.LogInfo($"[{nameof(WaitForHybridPrefabRegistration)}] Netcode is not active but has an instance at this point."); + if (NetCode.Netcode.IsActive) + { + 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(); } - /// !! 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: + 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. diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 43de83f0ce..803284ac45 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -3886,7 +3886,7 @@ private void Start() private void InitGhost() { // All instances with Ghosts are automatically registered - if (HasGhost && NetworkObjectBridge) + if (HasGhost && NetworkObjectBridge && !GhostAdapter.IsPrefab()) { if (NetworkManager.LogLevel == LogLevel.Developer) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index c66aa62273..40be416a00 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -228,6 +228,8 @@ 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); @@ -394,6 +396,7 @@ 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; @@ -870,6 +873,7 @@ 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/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs index f8d97072fb..f95c10e721 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs @@ -253,8 +253,10 @@ public override unsafe void Send(ulong clientId, ArraySegment payload, Net var amount = connectionInfo.SendQueue.FillWriterWithBytes(ref writer, k_MaxPacketSize); rpc.Buffer.Length = amount; rpc.Order = ++connectionInfo.LastSent; - - connectionInfo.Connection.SendOutOfBandMessage(rpc); + + var req = m_NetworkManager.NetcodeWorld.EntityManager.CreateEntity(ComponentType.ReadWrite(), ComponentType.ReadWrite()); + m_NetworkManager.NetcodeWorld.EntityManager.SetComponentData(req, new SendRpcCommandRequest{TargetConnection = connectionInfo.Connection.ConnectionEntity}); + m_NetworkManager.NetcodeWorld.EntityManager.SetComponentData(req, rpc); connectionInfo.SendQueue.Consume(amount); } @@ -369,41 +371,63 @@ private void OnServerClientDisconnected(Connection connection, NetCodeConnection { InvokeOnTransportEvent(NetworkEvent.Disconnect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup); } + + private void OnClientConnectionEvent(Connection connection, NetCodeConnectionEvent connectionEvent) + { + switch (connectionEvent.State) + { + case ConnectionState.State.Connected: + OnClientConnectedToServer(connection, connectionEvent); + break; + case ConnectionState.State.Disconnected: + OnClientDisconnectFromServer(connection, connectionEvent); + break; + } + } + + private void OnServerConnectionEvent(Connection connection, NetCodeConnectionEvent connectionEvent) + { + switch (connectionEvent.State) + { + case ConnectionState.State.Connected: + OnServerNewClientConnection(connection, connectionEvent); + break; + case ConnectionState.State.Disconnected: + OnServerClientDisconnected(connection, connectionEvent); + break; + } + } public override bool StartClient() { - NetCode.Netcode.Client.OnConnect = OnClientConnectedToServer; - NetCode.Netcode.Client.OnDisconnect = OnClientDisconnectFromServer; - var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); + m_NetworkManager.NetcodeWorld.OnConnectionEvent += OnClientConnectionEvent; + var updateSystem = m_NetworkManager.NetcodeWorld.GetExistingSystemManaged(); updateSystem.Transport = this; return true; } public override bool StartServer() { - foreach (var connection in NetCode.Netcode.Server.Connections) + foreach (var connection in m_NetworkManager.NetcodeWorld.AllConnections) { OnServerNewClientConnection(connection, default); } - NetCode.Netcode.Server.OnConnect = OnServerNewClientConnection; - NetCode.Netcode.Server.OnDisconnect = OnServerClientDisconnected; - var updateSystem = NetCode.Netcode.GetWorld(true).GetExistingSystemManaged(); + m_NetworkManager.NetcodeWorld.OnConnectionEvent += OnServerConnectionEvent; + var updateSystem = m_NetworkManager.NetcodeWorld.GetExistingSystemManaged(); updateSystem.Transport = this; return true; } public override void DisconnectRemoteClient(ulong clientId) { - var updateSystem = NetCode.Netcode.GetWorld(true).GetExistingSystemManaged(); - updateSystem.Disconnect(m_Connections[(int)clientId].Connection); + m_NetworkManager.NetcodeWorld.DisconnectAClient(m_Connections[(int)clientId].Connection); m_Connections.Remove((int)clientId); } public override void DisconnectLocalClient() { - var updateSystem = NetCode.Netcode.GetWorld(false).GetExistingSystemManaged(); - updateSystem.Disconnect(m_Connections[(int)ServerClientId].Connection); + m_NetworkManager.NetcodeWorld.RequestDisconnectFromServer(); m_Connections.Remove((int)ServerClientId); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs new file mode 100644 index 0000000000..fd41c89b00 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs @@ -0,0 +1,91 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.NetCode; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// Test class that deliberately removes some functionality from NetworkTransform that is conditionally disabled + /// by the presence of ghost objects in the base class. This is to help be certain that the network transform + /// is not doing the work, but that the work is being done by N4E's snapshots. + /// + public class DoNothingNetworkTransform : NetworkTransform + { + public override void OnNetworkSpawn() + { + // Deliberately left empty + } + + internal override void InternalInitialization(bool isOwnershipChange = false) + { + // Deliberately left empty + } + } + + public class UnifiedNetworkTransformTest : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 2; + + private GameObject m_Prefab; + private NetworkObject m_Instance; + + protected override void OnServerAndClientsCreated() + { + m_Prefab = CreateNetworkObjectPrefab("Test prefab"); + SetupGhostAdapterForNetworkObjectPrefab(ref m_Prefab); + + m_Prefab.AddComponent(); + + /*NetCode.Netcode.RunOnServerStarted(() => + { + NetCode.Netcode.RegisterPrefabSingleWorld(m_PlayerPrefab, true); + });*/ + } + + protected override IEnumerator OnServerAndClientsConnected() + { + m_Instance = SpawnObject(m_Prefab, m_ServerNetworkManager).GetComponent(); + yield return WaitForConditionOrTimeOut(() => + { + foreach (var client in m_ClientNetworkManagers) + { + if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId) || !s_GlobalNetworkObjects[client.LocalClientId].ContainsKey(m_Instance.NetworkObjectId)) + { + return false; + } + } + + return true; + }); + AssertOnTimeout($"Timed out waiting for objects to spawn!"); + yield return null; + } + + [UnityTest] + public IEnumerator BasicMovementTest() + { + var authority = GetAuthorityNetworkManager(); + var originalPos = authority.LocalClient.PlayerObject.transform.position; + var newPos = originalPos + new Vector3(1, 1, 1); + + m_Instance.transform.position = newPos; + + foreach (var client in m_ClientNetworkManagers) + { + Assert.IsTrue(Approximately(originalPos, s_GlobalNetworkObjects[client.LocalClientId][m_Instance.NetworkObjectId].transform.position)); + } + + yield return new WaitForSeconds(1); + + foreach (var client in m_ClientNetworkManagers) + { + Assert.IsTrue(Approximately(newPos, s_GlobalNetworkObjects[client.LocalClientId][m_Instance.NetworkObjectId].transform.position)); + } + } + } +} \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs.meta new file mode 100644 index 0000000000..10d990cfa3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0f26fa7bd5474b3f9947e0813374b50f +timeCreated: 1775078549 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 9e439ba098..49194efb71 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1,13 +1,17 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using NUnit.Framework; +using Unity.Entities; +using Unity.NetCode; using Unity.Netcode.RuntimeTests; using Unity.Netcode.Transports.UTP; +using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; @@ -2192,6 +2196,95 @@ protected GameObject CreateNetworkObjectPrefab(string baseName) return prefabObject; } +#if UNIFIED_NETCODE + + private GameObject CreatePrefab(string directoryPath, GameObject go) + { + if (!Directory.Exists(directoryPath)) + Directory.CreateDirectory(directoryPath); + var assetPath = $"{directoryPath}/{go.name}.prefab"; + if (AssetDatabase.AssetPathExists(assetPath)) + AssetDatabase.DeleteAsset(assetPath); + Assert.IsFalse(AssetDatabase.AssetPathExists(assetPath), $"path already exists for asset {assetPath}"); + var prefab = PrefabUtility.SaveAsPrefabAsset(go, assetPath); + + var networkPrefab = new NetworkPrefab() { Prefab = prefab }; + + var authorityNetworkManager = GetAuthorityNetworkManager(); + var clients = m_ClientNetworkManagers; + // 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.NetworkConfig.Prefabs.Remove(go); + foreach (var clientNetworkManager in clients) + { + if (clientNetworkManager == authorityNetworkManager) + { + continue; + } + clientNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = prefab }); + clientNetworkManager.NetworkConfig.Prefabs.Remove(go); + } + Object.DestroyImmediate(go); + + return prefab; + } + + protected void SetupGhostAdapterForNetworkObjectPrefab(ref GameObject prefabObject) + { + prefabObject.SetActive(false); + var adapter = prefabObject.AddComponent(); + var bridge = prefabObject.AddComponent(); + var no = prefabObject.GetComponent(); + no.HasGhost = true; + no.GhostAdapter = adapter; + no.HadBridge = true; + no.NetworkObjectBridge = bridge; + + /*prefabObject = CreatePrefab("Assets/Temp", prefabObject);*/ + + GhostPrefabReference.s_IsPostProcessing = true; + adapter.prefabReference = ScriptableObject.CreateInstance(); + adapter.prefabReference.name = "GhostPrefabReference"; + + adapter.prefabReference.Prefab = prefabObject; + adapter.prefabReference.Ghost = adapter; + GhostPrefabReference.s_IsPostProcessing = false; + + //prefabObject.SetActive(true); + UpdateGhostPrefabs(); + } + + void UpdateGhostPrefabs() + { + foreach (var prefabObject in GameObject.FindObjectsByType()) + { + var reference = prefabObject.GetComponent(); + if (!reference || reference.Prefab != prefabObject.gameObject) + { + continue; + } + + foreach (var world in World.All) + { + // check all possible worlds for the prefab that was just created and reenable it there too, since normal prefab creation would think the prefab is inactive + // and automatically set the associated entity disabled too + var link = GhostEntityMapping.LookupEntityReferencePrefab(prefabObject.GetEntityId(), world.Unmanaged); + if (link.WasInitialized) + { + link.World.EntityManager.SetEnabled(link.Entity, true); + // also have to override this in tests, since prefab registration will have had the wrong value during registration + var pendingGameObjectSpawn = + link.World.EntityManager.GetComponentData(link.Entity); + pendingGameObjectSpawn.ShouldBeActive = true; + link.World.EntityManager.SetComponentData(link.Entity, pendingGameObjectSpawn); + } + } + } + } + +#endif + /// /// Overloaded method /// @@ -2223,6 +2316,21 @@ protected GameObject SpawnPlayerObject(GameObject prefabGameObject, NetworkManag internal void SpawnInstanceWithOwnership(NetworkObject networkObjectToSpawn, NetworkManager spawnAuthority, ulong clientId, bool destroyWithScene = false, bool isPlayerObject = false) { + if (networkObjectToSpawn.HasGhost) + { + foreach(var type in networkObjectToSpawn.NetworkObjectBridge.ComponentTypes) + { + if (type.GetManagedType().Name == + $"{nameof(NetworkObjectBridge)}_{nameof(NetworkObjectBridge.NetworkObjectId)}_gen") + { + networkObjectToSpawn.NetworkObjectBridge.NetworkObjectId.Initialize( + networkObjectToSpawn.GhostAdapter.World, networkObjectToSpawn.GhostAdapter.Entity, type, + false); + } + + break; + } + } if (spawnAuthority.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) { networkObjectToSpawn.NetworkManagerOwner = spawnAuthority; // Required to assure the client does the spawning @@ -2294,7 +2402,9 @@ protected void SpawnObjectInstance(NetworkObject networkObjectToSpawn, NetworkMa private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager owner, bool destroyWithScene = false, bool isPlayerObject = false) { Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash > 0, $"{nameof(GameObject)} {prefabNetworkObject.name} has a {nameof(NetworkObject.GlobalObjectIdHash)} value of 0! Make sure to make it a valid prefab before trying to spawn!"); + NetCode.Netcode.Instance.m_ActiveWorld = owner.NetcodeWorld; var newInstance = Object.Instantiate(prefabNetworkObject.gameObject); + newInstance.SetActive(true); var networkObjectToSpawn = newInstance.GetComponent(); SpawnObjectInstance(networkObjectToSpawn, owner, destroyWithScene, isPlayerObject); return newInstance; @@ -2343,6 +2453,9 @@ public NetcodeIntegrationTest() { var topologyType = OnGetNetworkTopologyType(); InitializeTestConfiguration(topologyType, null); +#if UNIFIED_NETCODE + UnifiedBootStrap.OnInitialized += UpdateGhostPrefabs; +#endif } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef index fa718f8b04..86b6bae95f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef @@ -13,7 +13,9 @@ "Unity.Netcode.TestHelpers.Runtime", "Unity.Mathematics", "UnityEngine.TestRunner", - "UnityEditor.TestRunner" + "UnityEditor.TestRunner", + "Unity.NetCode", + "Unity.Entities" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/testproject/Assets/NetCodeConfig.asset b/testproject/Assets/NetCodeConfig.asset new file mode 100644 index 0000000000..b0d1add6cc --- /dev/null +++ b/testproject/Assets/NetCodeConfig.asset @@ -0,0 +1,76 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: abd30ee0214cf6a45b2d76765a4615b1, type: 3} + m_Name: NetCodeConfig + m_EditorClassIdentifier: Unity.NetCode::Unity.NetCode.NetCodeConfig + IsGlobalConfig: 1 + EnableClientServerBootstrap: 0 + HostWorldModeSelection: 1 + ClientServerTickRate: + SimulationTickRate: 60 + PredictedFixedStepSimulationTickRatio: 1 + NetworkTickRate: 0 + MaxSimulationStepsPerFrame: 1 + MaxSimulationStepBatchSize: 4 + TargetFrameRateMode: 0 + m_SendSnapshotsForCatchUpTicks: 0 + SnapshotAckMaskCapacity: 4096 + m_ClampPartialTicksThreshold: 5 + HandshakeApprovalTimeoutMS: 5000 + ClientTickRate: + InterpolationTimeNetTicks: 2 + InterpolationTimeMS: 0 + MaxExtrapolationTimeSimTicks: 20 + ForcedInputLatencyTicks: 0 + MaxPredictAheadTimeMS: 500 + NumAdditionalClientPredictedGhostLifetimeTicks: 0 + DefaultClassificationAllowableTickPeriod: 5 + TargetCommandSlack: 2 + NumAdditionalCommandsToSend: 2 + MaxPredictionStepBatchSizeRepeatedTick: 0 + MaxPredictionStepBatchSizeFirstTimeTick: 0 + PredictionLoopUpdateMode: 0 + InterpolationDelayJitterScale: 1.25 + InterpolationDelayMaxDeltaTicksFraction: 0.1 + InterpolationDelayCorrectionFraction: 0.1 + InterpolationTimeScaleMin: 0.85 + InterpolationTimeScaleMax: 1.1 + CommandAgeCorrectionFraction: 0.1 + PredictionTimeScaleMin: 0.9 + PredictionTimeScaleMax: 1.1 + GhostSendSystemData: + DefaultSnapshotPacketSize: 0 + PercentReservedForDespawnMessages: 0.33 + MinSendImportance: 0 + MinDistanceScaledSendImportance: 0 + MaxIterateChunks: 0 + MaxSendChunks: 0 + MaxSendEntities: 0 + m_ForceSingleBaseline: 0 + m_ForcePreSerialize: 0 + m_KeepSnapshotHistoryOnStructuralChange: 1 + m_EnablePerComponentProfiling: 0 + CleanupConnectionStatePerTick: 1 + m_FirstSendImportanceMultiplier: 1 + m_IrrelevantImportanceDownScale: 1 + m_TempStreamSize: 8192 + m_UseCustomSerializer: 0 + ConnectTimeoutMS: 1000 + MaxConnectAttempts: 60 + DisconnectTimeoutMS: 30000 + HeartbeatTimeoutMS: 500 + ReconnectionTimeoutMS: 2000 + ClientSendQueueCapacity: 64 + ClientReceiveQueueCapacity: 64 + ServerSendQueueCapacity: 512 + ServerReceiveQueueCapacity: 512 + MaxMessageSize: 1400 diff --git a/testproject/Assets/NetCodeConfig.asset.meta b/testproject/Assets/NetCodeConfig.asset.meta new file mode 100644 index 0000000000..222ccfadf4 --- /dev/null +++ b/testproject/Assets/NetCodeConfig.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c547acbddd81d32a0ba5e62ddfc4f4e3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index f7832aaad0..e64bb3bdad 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,23 +1,25 @@ { "disableProjectUpdate": false, "dependencies": { - "com.unity.addressables": "2.7.4", - "com.unity.ai.navigation": "2.0.9", - "com.unity.collab-proxy": "2.10.1", - "com.unity.ide.rider": "3.0.38", - "com.unity.ide.visualstudio": "2.0.25", - "com.unity.mathematics": "1.3.3", - "com.unity.multiplayer.tools": "2.2.6", + "com.unity.addressables": "2.9.1", + "com.unity.ai.navigation": "2.0.12", + "com.unity.collab-proxy": "2.12.4", + "com.unity.entities": "6.5.0", + "com.unity.ide.rider": "3.0.40", + "com.unity.ide.visualstudio": "2.0.26", + "com.unity.mathematics": "1.4.0", + "com.unity.multiplayer.tools": "2.2.8", + "com.unity.netcode": "6.6.0", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects", "com.unity.package-validation-suite": "0.49.0-preview", - "com.unity.services.authentication": "3.5.2", - "com.unity.services.multiplayer": "1.2.0", - "com.unity.test-framework": "1.6.0", - "com.unity.test-framework.performance": "3.2.0", - "com.unity.timeline": "1.8.9", - "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.11", - "com.unity.ugui": "2.0.0", + "com.unity.services.authentication": "3.6.1", + "com.unity.services.multiplayer": "2.1.3", + "com.unity.test-framework": "1.8.0", + "com.unity.test-framework.performance": "3.4.0", + "com.unity.timeline": "1.8.12", + "com.unity.ugui": "2.6.0", "com.unity.modules.accessibility": "1.0.0", + "com.unity.modules.adaptiveperformance": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", @@ -31,10 +33,12 @@ "com.unity.modules.particlesystem": "1.0.0", "com.unity.modules.physics": "1.0.0", "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.physicscore2d": "1.0.0", "com.unity.modules.screencapture": "1.0.0", "com.unity.modules.terrain": "1.0.0", "com.unity.modules.terrainphysics": "1.0.0", "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.timelinefoundation": "1.0.0", "com.unity.modules.ui": "1.0.0", "com.unity.modules.uielements": "1.0.0", "com.unity.modules.umbra": "1.0.0", @@ -44,9 +48,9 @@ "com.unity.modules.unitywebrequestaudio": "1.0.0", "com.unity.modules.unitywebrequesttexture": "1.0.0", "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vectorgraphics": "1.0.0", "com.unity.modules.vehicles": "1.0.0", "com.unity.modules.video": "1.0.0", - "com.unity.modules.vr": "1.0.0", "com.unity.modules.wind": "1.0.0", "com.unity.modules.xr": "1.0.0" }, diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 675de94bf2..00988aa549 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -1,7 +1,7 @@ { "dependencies": { "com.unity.addressables": { - "version": "2.7.4", + "version": "2.9.1", "depth": 0, "source": "registry", "dependencies": { @@ -11,13 +11,13 @@ "com.unity.modules.jsonserialize": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.scriptablebuildpipeline": "2.4.3", + "com.unity.scriptablebuildpipeline": "2.6.1", "com.unity.modules.unitywebrequestassetbundle": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.ai.navigation": { - "version": "2.0.9", + "version": "2.0.12", "depth": 0, "source": "registry", "dependencies": { @@ -26,7 +26,7 @@ "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.8.25", + "version": "1.8.29", "depth": 1, "source": "registry", "dependencies": { @@ -36,33 +36,52 @@ "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "2.10.1", + "version": "2.12.4", "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "2.6.2", + "version": "6.5.0", "depth": 1, - "source": "registry", + "source": "builtin", "dependencies": { - "com.unity.burst": "1.8.23", - "com.unity.mathematics": "1.3.2", + "com.unity.burst": "1.8.25", + "com.unity.nuget.mono-cecil": "1.11.6", "com.unity.test-framework": "1.4.6", - "com.unity.nuget.mono-cecil": "1.11.5", - "com.unity.test-framework.performance": "3.0.3" - }, - "url": "https://packages.unity.com" + "com.unity.test-framework.performance": "3.2.0" + } + }, + "com.unity.entities": { + "version": "6.5.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.burst": "1.8.25", + "com.unity.collections": "6.5.0", + "com.unity.nuget.mono-cecil": "1.11.6", + "com.unity.profiling.core": "1.0.3", + "com.unity.scriptablebuildpipeline": "1.23.1", + "com.unity.serialization": "6.5.0", + "com.unity.test-framework.performance": "3.2.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.hierarchy": "1.0.0" + } }, "com.unity.ext.nunit": { - "version": "2.0.5", + "version": "2.1.0", "depth": 1, "source": "builtin", "dependencies": {} }, "com.unity.ide.rider": { - "version": "3.0.38", + "version": "3.0.40", "depth": 0, "source": "registry", "dependencies": { @@ -71,23 +90,22 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.25", + "version": "2.0.26", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.31" + "com.unity.test-framework": "1.1.33" }, "url": "https://packages.unity.com" }, "com.unity.mathematics": { - "version": "1.3.3", + "version": "1.4.0", "depth": 0, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" + "source": "builtin", + "dependencies": {} }, "com.unity.multiplayer.tools": { - "version": "2.2.6", + "version": "2.2.8", "depth": 0, "source": "registry", "dependencies": { @@ -101,6 +119,16 @@ }, "url": "https://packages.unity.com" }, + "com.unity.netcode": { + "version": "6.6.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.transport": "6.5.0", + "com.unity.entities": "6.5.0", + "com.unity.modules.animation": "1.0.0" + } + }, "com.unity.netcode.gameobjects": { "version": "file:../../com.unity.netcode.gameobjects", "depth": 0, @@ -111,14 +139,14 @@ } }, "com.unity.nuget.mono-cecil": { - "version": "1.11.5", + "version": "1.11.6", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.nuget.newtonsoft-json": { - "version": "3.2.1", + "version": "3.2.2", "depth": 1, "source": "registry", "dependencies": {}, @@ -134,14 +162,14 @@ "url": "https://packages.unity.com" }, "com.unity.profiling.core": { - "version": "1.0.2", + "version": "1.0.3", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.scriptablebuildpipeline": { - "version": "2.4.3", + "version": "2.6.1", "depth": 1, "source": "registry", "dependencies": { @@ -150,8 +178,17 @@ }, "url": "https://packages.unity.com" }, + "com.unity.serialization": { + "version": "6.5.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.collections": "2.1.2", + "com.unity.burst": "1.7.2" + } + }, "com.unity.services.authentication": { - "version": "3.5.2", + "version": "3.6.1", "depth": 0, "source": "registry", "dependencies": { @@ -163,7 +200,7 @@ "url": "https://packages.unity.com" }, "com.unity.services.core": { - "version": "1.15.1", + "version": "1.16.0", "depth": 1, "source": "registry", "dependencies": { @@ -174,7 +211,7 @@ "url": "https://packages.unity.com" }, "com.unity.services.deployment": { - "version": "1.6.2", + "version": "1.7.2", "depth": 1, "source": "registry", "dependencies": { @@ -184,44 +221,44 @@ "url": "https://packages.unity.com" }, "com.unity.services.deployment.api": { - "version": "1.1.2", + "version": "1.1.3", "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.services.multiplayer": { - "version": "1.2.0", + "version": "2.1.3", "depth": 0, "source": "registry", "dependencies": { - "com.unity.transport": "2.5.0", + "com.unity.transport": "2.6.0", "com.unity.collections": "2.2.1", - "com.unity.services.qos": "1.3.0", - "com.unity.services.core": "1.15.1", - "com.unity.services.wire": "1.4.0", - "com.unity.services.deployment": "1.6.2", - "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.services.qos": "1.4.1", + "com.unity.services.core": "1.16.0", + "com.unity.services.wire": "1.4.1", + "com.unity.services.deployment": "1.7.1", + "com.unity.nuget.newtonsoft-json": "3.2.2", "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "3.5.1" + "com.unity.services.authentication": "3.6.0" }, "url": "https://packages.unity.com" }, "com.unity.services.qos": { - "version": "1.3.0", + "version": "1.4.1", "depth": 1, "source": "registry", "dependencies": { "com.unity.collections": "1.2.4", - "com.unity.services.core": "1.12.4", + "com.unity.services.core": "1.12.5", "com.unity.nuget.newtonsoft-json": "3.0.2", "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "2.0.0" + "com.unity.services.authentication": "3.5.2" }, "url": "https://packages.unity.com" }, "com.unity.services.wire": { - "version": "1.4.0", + "version": "1.4.2", "depth": 1, "source": "registry", "dependencies": { @@ -231,34 +268,18 @@ }, "url": "https://packages.unity.com" }, - "com.unity.sysroot": { - "version": "2.0.10", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot.linux-x86_64": { - "version": "2.0.9", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10" - }, - "url": "https://packages.unity.com" - }, "com.unity.test-framework": { - "version": "1.6.0", + "version": "1.8.0", "depth": 0, "source": "builtin", "dependencies": { - "com.unity.ext.nunit": "2.0.3", + "com.unity.ext.nunit": "2.1.0", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" } }, "com.unity.test-framework.performance": { - "version": "3.2.0", + "version": "3.4.0", "depth": 0, "source": "registry", "dependencies": { @@ -268,7 +289,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.8.9", + "version": "1.8.12", "depth": 0, "source": "registry", "dependencies": { @@ -279,34 +300,26 @@ }, "url": "https://packages.unity.com" }, - "com.unity.toolchain.win-x86_64-linux-x86_64": { - "version": "2.0.11", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10", - "com.unity.sysroot.linux-x86_64": "2.0.9" - }, - "url": "https://packages.unity.com" - }, "com.unity.transport": { - "version": "2.6.0", + "version": "6.5.0", "depth": 1, - "source": "registry", + "source": "builtin", "dependencies": { + "com.unity.collections": "6.5.0", "com.unity.burst": "1.8.24", - "com.unity.collections": "2.2.1", - "com.unity.mathematics": "1.3.2" - }, - "url": "https://packages.unity.com" + "com.unity.mathematics": "1.4.0" + } }, "com.unity.ugui": { - "version": "2.0.0", + "version": "2.6.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0" + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.physics": "1.0.0" } }, "com.unity.modules.accessibility": { @@ -315,6 +328,14 @@ "source": "builtin", "dependencies": {} }, + "com.unity.modules.adaptiveperformance": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.subsystems": "1.0.0" + } + }, "com.unity.modules.ai": { "version": "1.0.0", "depth": 0, @@ -325,7 +346,9 @@ "version": "1.0.0", "depth": 0, "source": "builtin", - "dependencies": {} + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } }, "com.unity.modules.animation": { "version": "1.0.0", @@ -362,6 +385,16 @@ "com.unity.modules.animation": "1.0.0" } }, + "com.unity.modules.hierarchy": { + "version": "1.0.0", + "depth": 1, + "source": "builtin", + "dependencies": { + "com.unity.modules.hierarchycore": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.imgui": "1.0.0" + } + }, "com.unity.modules.hierarchycore": { "version": "1.0.0", "depth": 1, @@ -399,6 +432,14 @@ "dependencies": {} }, "com.unity.modules.physics2d": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.physicscore2d": "1.0.0" + } + }, + "com.unity.modules.physicscore2d": { "version": "1.0.0", "depth": 0, "source": "builtin", @@ -443,6 +484,14 @@ "com.unity.modules.physics2d": "1.0.0" } }, + "com.unity.modules.timelinefoundation": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.jsonserialize": "1.0.0" + } + }, "com.unity.modules.ui": { "version": "1.0.0", "depth": 0, @@ -458,7 +507,8 @@ "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", "com.unity.modules.hierarchycore": "1.0.0", - "com.unity.modules.physics": "1.0.0" + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.animation": "1.0.0" } }, "com.unity.modules.umbra": { @@ -522,32 +572,32 @@ "com.unity.modules.imageconversion": "1.0.0" } }, - "com.unity.modules.vehicles": { + "com.unity.modules.vectorgraphics": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { - "com.unity.modules.physics": "1.0.0" + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0" } }, - "com.unity.modules.video": { + "com.unity.modules.vehicles": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" + "com.unity.modules.physics": "1.0.0" } }, - "com.unity.modules.vr": { + "com.unity.modules.video": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.xr": "1.0.0" + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0" } }, "com.unity.modules.wind": { diff --git a/testproject/ProjectSettings/PackageManagerSettings.asset b/testproject/ProjectSettings/PackageManagerSettings.asset index b01b2f8da9..6e57db2799 100644 --- a/testproject/ProjectSettings/PackageManagerSettings.asset +++ b/testproject/ProjectSettings/PackageManagerSettings.asset @@ -12,32 +12,33 @@ MonoBehaviour: m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: - m_EnablePreviewPackages: 1 - m_EnablePackageDependencies: 1 + m_EnablePreReleasePackages: 0 m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + m_DismissPreviewPackagesInUse: 0 oneTimeWarningShown: 1 - m_Registries: - - m_Id: main + oneTimePackageErrorsPopUpShown: 0 + m_MainRegistry: + m_Id: main m_Name: m_Url: https://packages.unity.com m_Scopes: [] m_IsDefault: 1 + m_IsUnityRegistry: 1 m_Capabilities: 7 + m_ConfigSource: 0 + m_Compliance: + m_Status: 0 + m_Violations: [] + m_ScopedRegistries: [] m_UserSelectedRegistryName: m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: - m_ErrorMessage: - m_Original: - m_Id: - m_Name: - m_Url: - m_Scopes: [] - m_IsDefault: 0 - m_Capabilities: 0 m_Modified: 0 - m_Name: - m_Url: - m_Scopes: - - - m_SelectedScopeIndex: 0 + m_ErrorMessage: + m_UserModificationsEntityId: + m_rawData: 568105584918791443 + m_OriginalEntityId: + m_rawData: 568105584918791444 + m_LoadAssets: 0 diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 3bb16ba357..06561ea008 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -3,7 +3,7 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 28 + serializedVersion: 30 productGUID: bba99b16607b94720b7d04f7f1a82989 AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 @@ -41,7 +41,6 @@ PlayerSettings: height: 1 m_SplashScreenLogos: [] m_VirtualRealitySplashScreen: {fileID: 0} - m_HolographicTrackingLossScreen: {fileID: 0} defaultScreenWidth: 1280 defaultScreenHeight: 720 defaultScreenWidthWeb: 960 @@ -66,10 +65,15 @@ PlayerSettings: useOSAutorotation: 1 use32BitDisplayBuffer: 1 preserveFramebufferAlpha: 0 + adjustIOSFPSUsingThermalState: 1 + thermalStateSeriousIOSFPS: 30 + thermalStateCriticalIOSFPS: 15 disableDepthAndStencilBuffers: 0 androidStartInFullscreen: 1 androidRenderOutsideSafeArea: 1 androidUseSwappy: 1 + androidRequestedVisibleInsets: 0 + androidSystemBarsBehavior: 2 androidDisplayOptions: 1 androidBlitType: 0 androidResizeableActivity: 0 @@ -84,6 +88,7 @@ PlayerSettings: defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 + callOnDisableOnAssetBundleUnload: 1 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 @@ -114,6 +119,7 @@ PlayerSettings: xboxEnableGuest: 0 xboxEnablePIXSampling: 0 metalFramebufferOnly: 0 + metalUseMetalDisplayLink: 0 xboxOneResolution: 0 xboxOneSResolution: 0 xboxOneXResolution: 3 @@ -147,12 +153,10 @@ PlayerSettings: preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 - m_HolographicPauseOnTrackingLoss: 1 xboxOneDisableKinectGpuReservation: 1 xboxOneEnable7thCore: 1 vrSettings: enable360StereoCapture: 0 - isWsaHolographicRemotingEnabled: 0 enableFrameTimingStats: 0 enableOpenGLProfilerGPURecorders: 1 allowHDRDisplaySupport: 0 @@ -174,9 +178,10 @@ PlayerSettings: tvOS: 0 overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 - AndroidMinSdkVersion: 23 + AndroidMinSdkVersion: 26 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 + AndroidPreferredDataLocation: 1 aotOptions: nimt-trampolines=1024 stripEngineCode: 1 iPhoneStrippingLevel: 0 @@ -191,13 +196,14 @@ PlayerSettings: VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 iOSSimulatorArchitecture: 0 - iOSTargetOSVersionString: 13.0 + iOSTargetOSVersionString: 15.0 tvOSSdkVersion: 0 tvOSSimulatorArchitecture: 0 tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: 13.0 + tvOSTargetOSVersionString: 15.0 VisionOSSdkVersion: 0 VisionOSTargetOSVersionString: 1.0 + xcodeProjectType: 0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 @@ -451,12 +457,6 @@ PlayerSettings: - m_BuildTarget: WindowsStandaloneSupport m_APIs: 0200000012000000 m_Automatic: 0 - m_BuildTargetVRSettings: - - m_BuildTarget: Standalone - m_Enabled: 0 - m_Devices: - - Oculus - - OpenVR m_DefaultShaderChunkSizeInMB: 16 m_DefaultShaderChunkCount: 0 openGLRequireES31: 0 @@ -484,7 +484,7 @@ PlayerSettings: locationUsageDescription: microphoneUsageDescription: bluetoothUsageDescription: - macOSTargetOSVersion: 11.0 + macOSTargetOSVersion: 12.0 switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 @@ -630,6 +630,8 @@ PlayerSettings: switchMicroSleepForYieldTime: 25 switchRamDiskSpaceSize: 12 switchUpgradedPlayerSettingsToNMETA: 0 + switchCaStoreSource: 0 + switchCaStoreFilePath: ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: @@ -739,7 +741,7 @@ PlayerSettings: webWasm2023: 0 webEnableSubmoduleStrippingCompatibility: 0 scriptingDefineSymbols: - Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT;NETCODE_GAMEOBJECT_BRIDGE_EXPERIMENTAL;NETCODE_EXPERIMENTAL_SINGLE_WORLD_HOST;OUT_OF_BAND_RPC additionalCompilerArguments: Standalone: - -warnaserror @@ -747,6 +749,7 @@ PlayerSettings: scriptingBackend: {} il2cppCompilerConfiguration: {} il2cppCodeGeneration: {} + il2cppLTOMode: {} il2cppStacktraceInformation: {} managedStrippingLevel: EmbeddedLinux: 1 @@ -794,8 +797,7 @@ PlayerSettings: metroDefaultTileSize: 1 metroTileForegroundText: 2 metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} - metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, - a: 1} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 0 syncCapabilities: 0 platformCapabilities: {} @@ -831,7 +833,6 @@ PlayerSettings: XboxOneXTitleMemory: 8 XboxOneOverrideIdentityName: XboxOneOverrideIdentityPublisher: - vrEditorSettings: {} cloudServicesEnabled: Analytics: 0 Build: 0 @@ -862,6 +863,7 @@ PlayerSettings: captureStartupLogs: {} activeInputHandler: 0 windowsGamepadBackendHint: 0 + enableDirectStorage: 0 cloudProjectId: framebufferDepthMemorylessMode: 0 qualitySettingsNames: [] @@ -877,3 +879,4 @@ PlayerSettings: androidVulkanAllowFilterList: [] androidVulkanDeviceFilterListAsset: {fileID: 0} d3d12DeviceFilterListAsset: {fileID: 0} + allowedHttpConnections: 3 From 0cc6391bbd7ae4741ba161a76b3086014fc58fe9 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 23 Apr 2026 16:27:35 -0500 Subject: [PATCH 2/7] Removed SetActive calls --- .../TestHelpers/NetcodeIntegrationTest.cs | 72 ------------------- 1 file changed, 72 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 49194efb71..fd28fda266 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -2197,42 +2197,8 @@ protected GameObject CreateNetworkObjectPrefab(string baseName) } #if UNIFIED_NETCODE - - private GameObject CreatePrefab(string directoryPath, GameObject go) - { - if (!Directory.Exists(directoryPath)) - Directory.CreateDirectory(directoryPath); - var assetPath = $"{directoryPath}/{go.name}.prefab"; - if (AssetDatabase.AssetPathExists(assetPath)) - AssetDatabase.DeleteAsset(assetPath); - Assert.IsFalse(AssetDatabase.AssetPathExists(assetPath), $"path already exists for asset {assetPath}"); - var prefab = PrefabUtility.SaveAsPrefabAsset(go, assetPath); - - var networkPrefab = new NetworkPrefab() { Prefab = prefab }; - - var authorityNetworkManager = GetAuthorityNetworkManager(); - var clients = m_ClientNetworkManagers; - // 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.NetworkConfig.Prefabs.Remove(go); - foreach (var clientNetworkManager in clients) - { - if (clientNetworkManager == authorityNetworkManager) - { - continue; - } - clientNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = prefab }); - clientNetworkManager.NetworkConfig.Prefabs.Remove(go); - } - Object.DestroyImmediate(go); - - return prefab; - } - protected void SetupGhostAdapterForNetworkObjectPrefab(ref GameObject prefabObject) { - prefabObject.SetActive(false); var adapter = prefabObject.AddComponent(); var bridge = prefabObject.AddComponent(); var no = prefabObject.GetComponent(); @@ -2241,8 +2207,6 @@ protected void SetupGhostAdapterForNetworkObjectPrefab(ref GameObject prefabObje no.HadBridge = true; no.NetworkObjectBridge = bridge; - /*prefabObject = CreatePrefab("Assets/Temp", prefabObject);*/ - GhostPrefabReference.s_IsPostProcessing = true; adapter.prefabReference = ScriptableObject.CreateInstance(); adapter.prefabReference.name = "GhostPrefabReference"; @@ -2250,39 +2214,7 @@ protected void SetupGhostAdapterForNetworkObjectPrefab(ref GameObject prefabObje adapter.prefabReference.Prefab = prefabObject; adapter.prefabReference.Ghost = adapter; GhostPrefabReference.s_IsPostProcessing = false; - - //prefabObject.SetActive(true); - UpdateGhostPrefabs(); - } - - void UpdateGhostPrefabs() - { - foreach (var prefabObject in GameObject.FindObjectsByType()) - { - var reference = prefabObject.GetComponent(); - if (!reference || reference.Prefab != prefabObject.gameObject) - { - continue; - } - - foreach (var world in World.All) - { - // check all possible worlds for the prefab that was just created and reenable it there too, since normal prefab creation would think the prefab is inactive - // and automatically set the associated entity disabled too - var link = GhostEntityMapping.LookupEntityReferencePrefab(prefabObject.GetEntityId(), world.Unmanaged); - if (link.WasInitialized) - { - link.World.EntityManager.SetEnabled(link.Entity, true); - // also have to override this in tests, since prefab registration will have had the wrong value during registration - var pendingGameObjectSpawn = - link.World.EntityManager.GetComponentData(link.Entity); - pendingGameObjectSpawn.ShouldBeActive = true; - link.World.EntityManager.SetComponentData(link.Entity, pendingGameObjectSpawn); - } - } - } } - #endif /// @@ -2404,7 +2336,6 @@ private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash > 0, $"{nameof(GameObject)} {prefabNetworkObject.name} has a {nameof(NetworkObject.GlobalObjectIdHash)} value of 0! Make sure to make it a valid prefab before trying to spawn!"); NetCode.Netcode.Instance.m_ActiveWorld = owner.NetcodeWorld; var newInstance = Object.Instantiate(prefabNetworkObject.gameObject); - newInstance.SetActive(true); var networkObjectToSpawn = newInstance.GetComponent(); SpawnObjectInstance(networkObjectToSpawn, owner, destroyWithScene, isPlayerObject); return newInstance; @@ -2453,9 +2384,6 @@ public NetcodeIntegrationTest() { var topologyType = OnGetNetworkTopologyType(); InitializeTestConfiguration(topologyType, null); -#if UNIFIED_NETCODE - UnifiedBootStrap.OnInitialized += UpdateGhostPrefabs; -#endif } /// From 4308345b2db7209f5c5c3af54c37c897aaa8bab5 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sat, 25 Apr 2026 15:05:57 -0500 Subject: [PATCH 3/7] update Some potential fixes (etc) for hybrid spawn integration testing. --- .../Components/Helpers/NetworkObjectBridge.cs | 22 ++-- .../Connection/NetworkConnectionManager.cs | 3 - .../Runtime/Core/NetworkObject.cs | 33 ++++-- .../Messaging/Messages/CreateObjectMessage.cs | 2 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 10 ++ .../Unified/UnifiedNetcodeTransport.cs | 23 +++- .../UnifiedNetworkTransformTest.cs | 97 ++++++++++----- .../TestHelpers/NetcodeIntegrationTest.cs | 111 +++++++++++++----- 8 files changed, 217 insertions(+), 84 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs index 189aba00fd..8cb0f48a8f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs @@ -48,10 +48,10 @@ private void OnValidate() /// N4E-spawned hybrid prefab instances. /// internal GhostField NetworkObjectId = new GhostField(); - - public void SetNetworkObjectId(ulong value) + public void SetNetworkObjectId(ulong networkObjectId) { - NetworkObjectId.Value = value; + NetworkObjectId.PresetValue(networkObjectId); + NetworkObjectId.Value = networkObjectId; } } #endif @@ -70,7 +70,7 @@ internal class UnifiedBootStrap : ClientServerBootstrap public static World LastCreatedWorld { get; private set; } - private static int WorldCounter = 0; + private static int s_WorldCounter = 0; public override bool Initialize(string defaultWorldName) { @@ -83,26 +83,28 @@ public override bool Initialize(string defaultWorldName) AutoConnectPort = Port; if (base.Initialize(defaultWorldName)) { - UnityEngine.Debug.LogError($"[{nameof(UnifiedBootStrap)}] Auto-bootstrap is enabled!!! This will break the POC!"); + Debug.LogError($"[{nameof(UnifiedBootStrap)}] Auto-bootstrap is enabled!!! This will break the POC!"); return true; } if (networkManager != null) { Debug.Log($"Starting a world for {(networkManager.IsServer ? "Host" : "Client")}"); - LastCreatedWorld = networkManager.IsServer - ? CreateSingleWorldHost($"ClientAndServerWorld {WorldCounter++}") - : CreateClientWorld($"ClientWorld {WorldCounter++}"); + s_WorldCounter++; + LastCreatedWorld = networkManager.IsServer ? CreateSingleWorldHost($"HostSingleWorld-{s_WorldCounter}") + : CreateClientWorld($"ClientWorld-{s_WorldCounter}"); if (LastCreatedWorld == null) { - UnityEngine.Debug.LogError($"[{nameof(UnifiedBootStrap)}] World is null!"); + s_WorldCounter--; + Debug.LogError($"[{nameof(UnifiedBootStrap)}] World is null!"); return false; } if (!LastCreatedWorld.IsCreated) { - UnityEngine.Debug.LogError($"[{nameof(UnifiedBootStrap)}] World was not created!"); + s_WorldCounter--; + Debug.LogError($"[{nameof(UnifiedBootStrap)}] World was not created!"); return false; } diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index eefb1e51da..058fb6a38b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -6,9 +6,6 @@ using System.Runtime.CompilerServices; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; -#if UNIFIED_NETCODE -using Unity.Netcode.Unified; -#endif using Unity.Profiling; using UnityEngine; using Debug = UnityEngine.Debug; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 803284ac45..53faae062f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -3852,9 +3852,13 @@ private void OnEnable() private void OnDisable() { Debug.Log("Disabled!"); - if (IsSpawned) + if (IsSpawned || HasGhost) { - enabled = true; + if (HasGhost && GhostAdapter.IsPrefab()) + { + return; + } + gameObject.SetActive(true); } try @@ -3870,13 +3874,6 @@ private void OnDisable() private void Start() { - // TODO-UNIFIED: Remove once the prefab registration is in place. - if (!enabled) - { - Debug.LogWarning($"[{nameof(NetworkObject)}][{name}] Was not enabled on start! Enabling."); - enabled = true; - } - InitGhost(); } [SerializeField] @@ -3892,7 +3889,7 @@ private void InitGhost() { Debug.Log($"[{nameof(NetworkObject)}] GhostBridge {name} detected and instantiated."); } - if (NetworkObjectBridge.NetworkObjectId.Value != 0) + if (GhostAdapter.WasInitialized && NetworkObjectBridge.NetworkObjectId.Value != 0) { RegisterGhostBridge(); } @@ -3904,9 +3901,23 @@ internal void RegisterGhostBridge() if (NetworkManager.LogLevel == LogLevel.Developer) { Debug.Log($"[{nameof(NetworkObject)}][{nameof(NetworkObjectId)}] NetworkObjectBridge notified instance exists with assigned ID of: {NetworkObjectBridge.NetworkObjectId.Value}"); + if (!NetworkManager.IsListening) + { + Debug.LogWarning($"[{nameof(NetworkObject)}] Did not register because there is no session in progress!"); + return; + } } - if (!NetworkManager.IsServer) + // Set when running through integration tests in order to initially bypass the + // normal registration. This is because at this point in the instantiation process, + // NetworkObject's NetworkManager is pointing to the singleton which means all instances + // (even if intended to be for a specific client) will end up registering with whichever + // NetworkManager instance is being pointed to by the singleton. + if (NetworkSpawnManager.RegisterPendingGhost != null) + { + NetworkSpawnManager.RegisterPendingGhost(this, NetworkObjectBridge.NetworkObjectId.Value); + } + else if (!NetworkManager.IsServer) { NetworkManager.SpawnManager.RegisterGhostPendingSpawn(this, NetworkObjectBridge.NetworkObjectId.Value); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index ac1c07efc0..4812c3680d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -131,7 +131,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int { if (networkManager.LogLevel == LogLevel.Developer) { - UnityEngine.Debug.Log($"Deferring {nameof(CreateObjectMessage)} to wait for Ghost."); + UnityEngine.Debug.Log($"[{nameof(NetworkObject)}-{ObjectInfo.NetworkObjectId}] Deferring {nameof(CreateObjectMessage)} to wait for Ghost."); } networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, ObjectInfo.NetworkObjectId, reader, ref context, k_Name); return false; diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index df40f2cdac..dc2a663b34 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; +using Unity.Entities; +using Unity.NetCode; using UnityEngine; namespace Unity.Netcode @@ -35,6 +37,13 @@ public class NetworkSpawnManager internal readonly Dictionary GhostsPendingSpawn = new Dictionary(); + // TODO: We might want to make this a mock interfacebut temporary solution to validate + // the need to assure we are registering with the right NetworkManager instance when testing (everything + // will use the singleton during Awake and Start when we need to register). + internal delegate void RegisterPendingGhostDelegateHandler(NetworkObject networkObject, ulong networkObjectId); + + internal static RegisterPendingGhostDelegateHandler RegisterPendingGhost; + internal void RegisterGhostPendingSpawn(NetworkObject networkObject, ulong networkObjectId) { if (NetworkManager.LogLevel == LogLevel.Developer) @@ -43,6 +52,7 @@ internal void RegisterGhostPendingSpawn(NetworkObject networkObject, ulong netwo } if (GhostsPendingSpawn.TryAdd(networkObjectId, networkObject)) { + // TODO-REVIEW-BELOW: *** This is very likely no longer an issue with the new connection sequence *** // TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn. // Edge-Case scenario: During initial client synchronization (i.e. !NetworkManager.IsConnectedClient). // diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs index f95c10e721..85393e4759 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs @@ -427,8 +427,29 @@ public override void DisconnectRemoteClient(ulong clientId) public override void DisconnectLocalClient() { - m_NetworkManager.NetcodeWorld.RequestDisconnectFromServer(); + // Remove the connection 1st (the world might not be available) m_Connections.Remove((int)ServerClientId); + + // TODO-FIX-REVIEW-ME: + // This was causing errors to occur upon shutdown during an integration test. + // The cases being trapped for below yield no errors, but there might be some + // form of other underlying issue here: + + if (m_NetworkManager.NetcodeWorld == null || !m_NetworkManager.NetcodeWorld.IsCreated) + { + return; + } + + if (m_NetworkManager.IsServer || m_NetworkManager.NetcodeWorld.IsHost()) + { + if (m_NetworkManager.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning("Host is attempting to shutdown the local client which is not required with a single world host."); + } + return; + } + m_NetworkManager.NetcodeWorld.RequestDisconnectFromServer(); + } public override ulong GetCurrentRtt(ulong clientId) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs index fd41c89b00..4325695783 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs @@ -1,12 +1,12 @@ using System.Collections; -using System.Collections.Generic; using NUnit.Framework; -using Unity.NetCode; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; +using Unity.NetCode; using UnityEngine; using UnityEngine.TestTools; + namespace Unity.Netcode.RuntimeTests { /// @@ -14,7 +14,7 @@ namespace Unity.Netcode.RuntimeTests /// by the presence of ghost objects in the base class. This is to help be certain that the network transform /// is not doing the work, but that the work is being done by N4E's snapshots. /// - public class DoNothingNetworkTransform : NetworkTransform + internal class DoNothingNetworkTransform : NetworkTransform { public override void OnNetworkSpawn() { @@ -26,53 +26,93 @@ internal override void InternalInitialization(bool isOwnershipChange = false) // Deliberately left empty } } - - public class UnifiedNetworkTransformTest : IntegrationTestWithApproximation + + internal class UnifiedNetworkTransformTest : IntegrationTestWithApproximation { protected override int NumberOfClients => 2; private GameObject m_Prefab; private NetworkObject m_Instance; - protected override void OnServerAndClientsCreated() + protected override IEnumerator OnSetup() { - m_Prefab = CreateNetworkObjectPrefab("Test prefab"); - SetupGhostAdapterForNetworkObjectPrefab(ref m_Prefab); - + // Creates the hybrid prefab + m_Prefab = CreateHybridPrefab("HybridPrefab", true); m_Prefab.AddComponent(); + NetworkSpawnManager.RegisterPendingGhost = RegisterPendingGhost; + return base.OnSetup(); + } - /*NetCode.Netcode.RunOnServerStarted(() => - { - NetCode.Netcode.RegisterPrefabSingleWorld(m_PlayerPrefab, true); - });*/ + protected override IEnumerator OnTearDown() + { + NetworkSpawnManager.RegisterPendingGhost = null; + m_EnableVerboseDebug = false; + return base.OnTearDown(); } - protected override IEnumerator OnServerAndClientsConnected() + private void RegisterPendingGhost(NetworkObject networkObject, ulong networkObjectId) { - m_Instance = SpawnObject(m_Prefab, m_ServerNetworkManager).GetComponent(); - yield return WaitForConditionOrTimeOut(() => + var ghost = networkObject.GetComponent(); + Assert.IsNotNull(ghost, $"[RegisterPendingGhost][NetworkObject-{networkObjectId}] Has no {nameof(GhostAdapter)}!"); + foreach (var networkManager in m_NetworkManagers) { - foreach (var client in m_ClientNetworkManagers) + // If the world matches, then register the instance with this NetworkManager's spawn manager. + if (networkManager.NetcodeWorld == ghost.World) { - if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId) || !s_GlobalNetworkObjects[client.LocalClientId].ContainsKey(m_Instance.NetworkObjectId)) - { - return false; - } + networkManager.SpawnManager.RegisterGhostPendingSpawn(networkObject, networkObjectId); + return; } + } + Debug.LogError($"Did not find a world for NetworkObject-{networkObjectId}!!"); + } + + protected override void OnServerAndClientsCreated() + { - return true; - }); - AssertOnTimeout($"Timed out waiting for objects to spawn!"); - yield return null; + // Add the hybrid prefab to the prefabs list for + // all NetworkManager instances. + // TODO: Emma and I discussed actually not making it + // a requirement to have NetworkManager instances. + // We can get that PR landed and merged back into the + // unified branch so this is no longer needed. + // (We can modify CreateHybridPrefab to use whatever list + // is used to handle this when using the normal prefab creation + // methods). + var networkPrefab = new NetworkPrefab() + { + Prefab = m_Prefab, + }; + foreach (var networkManager in m_NetworkManagers) + { + networkManager.LogLevel = LogLevel.Developer; + networkManager.NetworkConfig.Prefabs.Add(networkPrefab); + // Set the deferred message timeout to be 5 seconds for this test. + // (To see if the messages for the instances ever get processed.) + // Enable this to debug deferred + //networkManager.NetworkConfig.SpawnTimeout = 5; + } } [UnityTest] public IEnumerator BasicMovementTest() { + m_EnableVerboseDebug = true; var authority = GetAuthorityNetworkManager(); + m_Instance = SpawnObject(m_Prefab, m_ServerNetworkManager).GetComponent(); + + // Wait 5 seconds so we will dump any deferred messages if it failed on clients + // when checking to see if it spawned or not on the clients next. + // Enable this to debug deferred + //yield return new WaitForSeconds(5); + + yield return WaitForSpawnedOnAllOrTimeOut(m_Instance); + AssertOnTimeout($"Failed to spawn {m_Instance.name} on all clients!"); + + VerboseDebug("All clients spawned instance!"); + var originalPos = authority.LocalClient.PlayerObject.transform.position; var newPos = originalPos + new Vector3(1, 1, 1); - + m_Instance.transform.position = newPos; foreach (var client in m_ClientNetworkManagers) @@ -81,11 +121,12 @@ public IEnumerator BasicMovementTest() } yield return new WaitForSeconds(1); - + foreach (var client in m_ClientNetworkManagers) { Assert.IsTrue(Approximately(newPos, s_GlobalNetworkObjects[client.LocalClientId][m_Instance.NetworkObjectId].transform.position)); } + VerboseDebug("Test Passed!"); } } -} \ No newline at end of file +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index fd28fda266..6cd33dc89a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1749,6 +1749,19 @@ protected void DestroySceneNetworkObjects() if (CanDestroyNetworkObject(networkObject)) { + // Handle removing the prefab reference and destroying it + // and then destroying the ghostAdapter prior to destroying + // a hybrid prefab. + var ghostAdapter = networkObject.GetComponent(); + if (ghostAdapter != null && ghostAdapter.prefabReference != null) + { + var prefabReference = ghostAdapter.prefabReference; + prefabReference.Prefab = null; + ghostAdapter.prefabReference = null; + Object.Destroy(prefabReference); + Object.Destroy(ghostAdapter); + } + // Destroy the GameObject that holds the NetworkObject component Object.DestroyImmediate(networkObject.gameObject); } @@ -2197,23 +2210,76 @@ protected GameObject CreateNetworkObjectPrefab(string baseName) } #if UNIFIED_NETCODE - protected void SetupGhostAdapterForNetworkObjectPrefab(ref GameObject prefabObject) - { - var adapter = prefabObject.AddComponent(); - var bridge = prefabObject.AddComponent(); - var no = prefabObject.GetComponent(); - no.HasGhost = true; - no.GhostAdapter = adapter; - no.HadBridge = true; - no.NetworkObjectBridge = bridge; - GhostPrefabReference.s_IsPostProcessing = true; - adapter.prefabReference = ScriptableObject.CreateInstance(); - adapter.prefabReference.name = "GhostPrefabReference"; + protected GameObject CreateHybridPrefab(string baseName, bool moveToDDOL = true) + { + // Prevent from trying to register/spawn when creating this hybrid prefab + GhostBehaviour.IsCreatingPrefab = true; + var gameObject = new GameObject + { + name = baseName + }; - adapter.prefabReference.Prefab = prefabObject; - adapter.prefabReference.Ghost = adapter; - GhostPrefabReference.s_IsPostProcessing = false; + // Wrap the rest in case a bug (or the like) is introduced and an exception is thrown + // so we can assure to set IsCreatingPrefab back to false. + try + { + // TODO: We might think if there is a better way to handle this, but GhostBehaviour is dependent + // upon GhostObject having run through its Awake prior to it and NetworkObject needs to access + // the NetowrkObjectId GhostField which depends upon GhostBehaviour. + + // Order of operations in how these execute is actually important. + // GhostObject should execute 1st. + // NetworkObjectBridge 2nd. + // NetworkObject 3rd. + // NetworkBehaviours will execute in the order they are arranged unless otherwise specified. + var adapter = gameObject.AddComponent(); + var bridge = gameObject.AddComponent(); + var no = gameObject.AddComponent(); + + // Ghost specific settings + no.HasGhost = true; + no.GhostAdapter = adapter; + no.HadBridge = true; + no.NetworkObjectBridge = bridge; + + // This gets automatically disabled when NetworkObject is validated. + no.SynchronizeTransform = false; + no.SceneMigrationSynchronization = false; + + // Make sure this is not a scene object + no.IsSceneObject = false; + + // TODO: This might be part of the CreateHybridPrefab parameters + // For now, just use normal interpolation until we get integration + // tests running. + // Once we have validated prediction works and have a working manual + // test, we can circle back to this (possibly make that a sub-task + // with the dependency to prediction manual test). + adapter.SupportedGhostModes = GhostModeMask.Interpolated; + + // Mark the reference as post processing to avoid registering this instance automatically + GhostPrefabReference.s_IsPostProcessing = true; + adapter.prefabReference = ScriptableObject.CreateInstance(); + adapter.prefabReference.name = "GhostPrefabReference"; + adapter.prefabReference.Prefab = gameObject; + adapter.prefabReference.Ghost = adapter; + GhostPrefabReference.s_IsPostProcessing = false; + + // Turn it into a test prefab + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(no); + if (moveToDDOL) + { + Object.DontDestroyOnLoad(gameObject); + } + } + catch (Exception ex) + { + Debug.LogException(ex); + } + // Always reset + GhostBehaviour.IsCreatingPrefab = false; + return gameObject; } #endif @@ -2248,21 +2314,6 @@ protected GameObject SpawnPlayerObject(GameObject prefabGameObject, NetworkManag internal void SpawnInstanceWithOwnership(NetworkObject networkObjectToSpawn, NetworkManager spawnAuthority, ulong clientId, bool destroyWithScene = false, bool isPlayerObject = false) { - if (networkObjectToSpawn.HasGhost) - { - foreach(var type in networkObjectToSpawn.NetworkObjectBridge.ComponentTypes) - { - if (type.GetManagedType().Name == - $"{nameof(NetworkObjectBridge)}_{nameof(NetworkObjectBridge.NetworkObjectId)}_gen") - { - networkObjectToSpawn.NetworkObjectBridge.NetworkObjectId.Initialize( - networkObjectToSpawn.GhostAdapter.World, networkObjectToSpawn.GhostAdapter.Entity, type, - false); - } - - break; - } - } if (spawnAuthority.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority) { networkObjectToSpawn.NetworkManagerOwner = spawnAuthority; // Required to assure the client does the spawning From 2916b670f36273938b0344b859b96492fcdee9c4 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sat, 25 Apr 2026 15:50:56 -0500 Subject: [PATCH 4/7] style removing unused namespace directives. --- .../Runtime/Spawning/NetworkSpawnManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index dc2a663b34..de8bc86192 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -4,8 +4,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; -using Unity.Entities; -using Unity.NetCode; using UnityEngine; namespace Unity.Netcode From 59f82086e29ee1e5e59170204445f62f665083ca Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 27 Apr 2026 16:39:19 -0500 Subject: [PATCH 5/7] update Adjustments based on earlier discussion. --- .../TestHelpers/NetcodeIntegrationTest.cs | 116 ++++++++---------- 1 file changed, 53 insertions(+), 63 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 6cd33dc89a..0b383cc7c0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1,17 +1,14 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using NUnit.Framework; -using Unity.Entities; using Unity.NetCode; using Unity.Netcode.RuntimeTests; using Unity.Netcode.Transports.UTP; -using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.TestTools; @@ -2210,75 +2207,68 @@ protected GameObject CreateNetworkObjectPrefab(string baseName) } #if UNIFIED_NETCODE - protected GameObject CreateHybridPrefab(string baseName, bool moveToDDOL = true) { // Prevent from trying to register/spawn when creating this hybrid prefab - GhostBehaviour.IsCreatingPrefab = true; var gameObject = new GameObject { name = baseName }; - // Wrap the rest in case a bug (or the like) is introduced and an exception is thrown - // so we can assure to set IsCreatingPrefab back to false. - try - { - // TODO: We might think if there is a better way to handle this, but GhostBehaviour is dependent - // upon GhostObject having run through its Awake prior to it and NetworkObject needs to access - // the NetowrkObjectId GhostField which depends upon GhostBehaviour. - - // Order of operations in how these execute is actually important. - // GhostObject should execute 1st. - // NetworkObjectBridge 2nd. - // NetworkObject 3rd. - // NetworkBehaviours will execute in the order they are arranged unless otherwise specified. - var adapter = gameObject.AddComponent(); - var bridge = gameObject.AddComponent(); - var no = gameObject.AddComponent(); - - // Ghost specific settings - no.HasGhost = true; - no.GhostAdapter = adapter; - no.HadBridge = true; - no.NetworkObjectBridge = bridge; - - // This gets automatically disabled when NetworkObject is validated. - no.SynchronizeTransform = false; - no.SceneMigrationSynchronization = false; - - // Make sure this is not a scene object - no.IsSceneObject = false; - - // TODO: This might be part of the CreateHybridPrefab parameters - // For now, just use normal interpolation until we get integration - // tests running. - // Once we have validated prediction works and have a working manual - // test, we can circle back to this (possibly make that a sub-task - // with the dependency to prediction manual test). - adapter.SupportedGhostModes = GhostModeMask.Interpolated; - - // Mark the reference as post processing to avoid registering this instance automatically - GhostPrefabReference.s_IsPostProcessing = true; - adapter.prefabReference = ScriptableObject.CreateInstance(); - adapter.prefabReference.name = "GhostPrefabReference"; - adapter.prefabReference.Prefab = gameObject; - adapter.prefabReference.Ghost = adapter; - GhostPrefabReference.s_IsPostProcessing = false; - - // Turn it into a test prefab - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(no); - if (moveToDDOL) - { - Object.DontDestroyOnLoad(gameObject); - } - } - catch (Exception ex) - { - Debug.LogException(ex); + // Order of operations in how these execute is actually important. + // GhostObject should execute 1st. + // NetworkObjectBridge 2nd. + // NetworkObject 3rd. + // NetworkBehaviours will execute in the order they are arranged unless otherwise specified. + + // When adding a Hybrid/Ghost prefab: + // - We disabled the GameObject prior to adding the GhostPrefabReference (so IsPrefab() == true). + // - Add the GhostAdapter and GhostPrefabReference + // - Then set it back to active. + gameObject.SetActive(false); + var adapter = gameObject.AddComponent(); + // Mark the reference as post processing to avoid registering this instance automatically. + GhostPrefabReference.s_IsPostProcessing = true; + adapter.prefabReference = ScriptableObject.CreateInstance(); + adapter.prefabReference.name = "GhostPrefabReference"; + adapter.prefabReference.Prefab = gameObject; + adapter.prefabReference.Ghost = adapter; + GhostPrefabReference.s_IsPostProcessing = false; + + // TODO: This might be part of the CreateHybridPrefab parameters + // For now, just use normal interpolation until we get integration + // tests running. + // Once we have validated prediction works and have a working manual + // test, we can circle back to this (possibly make that a sub-task + // with the dependency to prediction manual test). + adapter.SupportedGhostModes = GhostModeMask.Interpolated; + + // Once done with setting up the GhostAdapter, we can set it back to active in the hierarchy + gameObject.SetActive(true); + + // GhostBehaviours that are part of a prefab will not invoke Ghost.InternalAcquireEntityReference + // Add the bridge + var bridge = gameObject.AddComponent(); + + // Now add NGO components + var no = gameObject.AddComponent(); + + // NetworkObject Ghost specific settings + no.HasGhost = true; + no.GhostAdapter = adapter; + no.HadBridge = true; + no.NetworkObjectBridge = bridge; + + // Disable transform synchronization for NetworkObject serialization + // since that is handled by the GhostAdapter. + no.SynchronizeTransform = false; + + // Turn it into a test prefab + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(no); + if (moveToDDOL) + { + Object.DontDestroyOnLoad(gameObject); } - // Always reset - GhostBehaviour.IsCreatingPrefab = false; return gameObject; } #endif From d07687db145ba08f94c42759e2286523ffdd6614 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Wed, 6 May 2026 14:15:05 -0500 Subject: [PATCH 6/7] 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 7/7] 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); }