diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 4a03809c92..3903b2edca 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -18,6 +18,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Issue where every `NetworkObject` that started before a session logged a "Did not register because there is no session in progress!" warning when `NetworkManager.LogLevel` was `Developer`. Only `GhostObject` instances log it now. (#4245) - Ensured a useful and human readable error is logged when a NetworkVariable is trying to serialize a private or protected type. (#4239) - Issue where `NetworkAnimator` logged an error for a trigger-conditioned transition into an Exit node, and a client that joined during that transition, or during one into a sub-state machine, did not synchronize with it. Transitions that pass through Exit, Entry or sub-state machine nodes now resolve to the state they reach, and a late-joining client cross-fades from the matching originating state when several states transition into the same destination. (#4238) - Ensured all callbacks are wrapped with exception handling to avoid silent errors. (#4161) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 028e508982..e9815947e0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -4208,6 +4208,8 @@ private void Start() [HideInInspector] internal NetworkObjectBridge NetworkObjectBridge; + internal const string NotRegisteredWithoutSessionWarning = "Did not register because there is no session in progress!"; + private void InitGhost() { // Note: If hybrid prefabs are created prior to any NetworkManager instances, @@ -4220,22 +4222,21 @@ private void InitGhost() return; } + if (!HasGhost || !NetworkObjectBridge || GhostObject.IsPrefab()) + { + // Nothing to register + return; + } if (!NetworkManager.IsListening) { if (NetworkManager.LogLevel == LogLevel.Developer) { - Debug.LogWarning($"[{nameof(NetworkObject)}] Did not register because there is no session in progress!"); + Debug.LogWarning($"[{nameof(NetworkObject)}][{name}] {NotRegisteredWithoutSessionWarning}"); } return; } - if (!HasGhost || !NetworkObjectBridge || GhostObject.IsPrefab()) - { - // Nothing to register - return; - } - // All instances with Ghosts are automatically registered if (NetworkManager.LogLevel == LogLevel.Developer) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/UnifiedHybridPrefabValidationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/UnifiedHybridPrefabValidationTests.cs index 2cf756995d..f4bb04ac50 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/UnifiedHybridPrefabValidationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/UnifiedHybridPrefabValidationTests.cs @@ -1,6 +1,7 @@ #if UNIFIED_NETCODE using System; using System.Collections; +using System.Collections.Generic; using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; @@ -17,6 +18,7 @@ namespace Unity.Netcode.RuntimeTests /// - A hybrid prefab instance spawned during a session that started without hybrid prefabs. It is not spawned.
/// - A hybrid player prefab counts toward hybrid mode before the session starts, so distributed authority rejects it.
/// - A NetworkPrefab override with a hybrid source or target prefab, in any topology. The entry is ignored.
+ /// - A hybrid instance created before the session starts. With developer logging it warns, and a non-hybrid instance does not.
/// /// /// The fixture's managers are not started. Each test creates its own that registers only the prefabs it needs. @@ -215,6 +217,58 @@ public void PrefabListAddDuringSessionRegistersOnce() UnityEngine.Object.Destroy(prefabList); } + /// + /// Validates that only a hybrid instance created before the session starts warns that it was not registered. + /// + [UnityTest] + public IEnumerator PreSessionInstanceWarnsOnlyWhenHybrid() + { + var networkManager = CreateNetworkManager(NetworkTopologyTypes.ClientServer); + var cases = new (GameObject Prefab, LogLevel LogLevel, bool ExpectWarning)[] + { + (m_Prefab, LogLevel.Developer, false), + (m_HybridPrefab, LogLevel.Normal, false), + (m_HybridPrefab, LogLevel.Developer, true), + }; + + var warnings = new List(); + Application.LogCallback onLogMessage = (message, stackTrace, logType) => + { + if (logType == LogType.Warning && message.Contains(NetworkObject.NotRegisteredWithoutSessionWarning)) + { + warnings.Add(message); + } + }; + Application.logMessageReceived += onLogMessage; + try + { + foreach (var testCase in cases) + { + warnings.Clear(); + networkManager.LogLevel = testCase.LogLevel; + var instance = UnityEngine.Object.Instantiate(testCase.Prefab).GetComponent(); + instance.NetworkManagerOwner = networkManager; + var instanceName = instance.name; + var caseName = $"{instanceName}][{testCase.LogLevel}"; + + // Start, where the instance is registered, runs on the next frame. + yield return null; + UnityEngine.Object.Destroy(instance.gameObject); + yield return null; + + Assert.AreEqual(testCase.ExpectWarning ? 1 : 0, warnings.Count, $"[{caseName}] Unexpected number of not registered warnings!"); + if (testCase.ExpectWarning) + { + StringAssert.Contains(instanceName, warnings[0], $"[{caseName}] The warning does not name the instance!"); + } + } + } + finally + { + Application.logMessageReceived -= onLogMessage; + } + } + /// /// Validates that an override with a hybrid source or target is ignored, with an error naming it. ///