From e6693e90ccf3f0dc68e2626de3bfda3ad27d072d Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 2 Nov 2026 10:47:23 -0500 Subject: [PATCH 1/6] fix: handle NetworkManager Awake being overridden --- .../Runtime/Core/NetworkManager.cs | 27 ++++++++++-- .../Tests/Runtime/NetworkManagerTests.cs | 41 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 25b86e3da0..508180ea63 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1071,7 +1071,7 @@ public void SetSingleton() OnSingletonReady?.Invoke(); } - private void Awake() + protected virtual void Awake() { if (Log == null) { @@ -1088,7 +1088,7 @@ private void Awake() OnInstantiated?.Invoke(this); } - private void OnEnable() + protected virtual void OnEnable() { if (RunInBackground) { @@ -1337,6 +1337,11 @@ private bool CanStart(StartType type) /// returns true if started in server mode successfully; otherwise false public bool StartServer() { + if (Log == null) + { + Log = new ContextualLogger(this, false); + Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); + } Log.CaptureFunctionCall(); if (!CanStart(StartType.Server)) @@ -1397,6 +1402,11 @@ public bool StartServer() /// (/) returns true if started in client mode successfully. public bool StartClient() { + if (Log == null) + { + Log = new ContextualLogger(this, false); + Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); + } Log.CaptureFunctionCall(); if (!CanStart(StartType.Client)) @@ -1450,6 +1460,11 @@ public bool StartClient() /// (/) returns true if started in host mode successfully. public bool StartHost() { + if (Log == null) + { + Log = new ContextualLogger(this, false); + Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); + } Log.CaptureFunctionCall(); if (!CanStart(StartType.Host)) @@ -1752,8 +1767,14 @@ private void OnApplicationQuit() } // Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit - private void OnDestroy() + protected virtual void OnDestroy() { + if (Log == null) + { + Log = new ContextualLogger(this, false); + Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); + } + try { ShutdownInternal(); diff --git a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs index ccf46cb61c..e1262bd920 100644 --- a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs @@ -198,4 +198,45 @@ private void OnServerStopped(bool obj) m_ServerNetworkManager.Shutdown(); } } + + internal class DerivedNetworkManagerTests + { + private class TestNetworkManager : NetworkManager + { + public void Awake() { } + public void OnEnable() { } + } + + [UnityTest] + public IEnumerator ValidateDerivedNetworkManager() + { + var gameObject = new GameObject("TestNetworkManager"); + var server = gameObject.AddComponent(); + var mockTransport = server.gameObject.AddComponent(); + server.NetworkConfig ??= new NetworkConfig(); + server.NetworkConfig.NetworkTransport = mockTransport; + var client = Object.Instantiate(server); + + var isListening = server.StartServer(); + Assert.That(isListening, Is.True, "Server failed to start!"); + + client.StartClient(); + + var timeoutHelper = new TimeoutHelper(); + yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => client.IsListening, timeoutHelper); + Assert.That(timeoutHelper.TimedOut, Is.False, "Client timed out waiting to connect!"); + + Object.Destroy(client.gameObject); + + server.Shutdown(); + timeoutHelper = new TimeoutHelper(); + yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => !server.IsListening, timeoutHelper); + Assert.That(timeoutHelper.TimedOut, Is.False, "Timed out waiting for server to shutdown!"); + + isListening = server.StartHost(); + Assert.That(isListening, Is.True, "Host failed to start!"); + + Object.Destroy(gameObject); + } + } } From 6f907edebda2df77ddf0d4f07db459d8ec273e7e Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 3 Nov 2026 09:57:12 -0500 Subject: [PATCH 2/6] Fix the XML doc and the double destroy --- .../Runtime/Core/NetworkManager.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 508180ea63..0e3540975b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1071,6 +1071,13 @@ public void SetSingleton() OnSingletonReady?.Invoke(); } + /// + /// Unity calls Awake when loading an instance of a script component. + /// + /// + /// Override this method to implement custom initialization logic for a derived . + /// Ensure you call base.Awake() in your override to allow the base initialization to happen. + /// protected virtual void Awake() { if (Log == null) @@ -1088,6 +1095,13 @@ protected virtual void Awake() OnInstantiated?.Invoke(this); } + /// + /// Called when a component of an active GameObject is first enabled. + /// + /// + /// Override this method to implement custom initialization logic for a derived . + /// Ensure you call base.OnEnable() in your override to allow the base initialization to happen. + /// protected virtual void OnEnable() { if (RunInBackground) @@ -1766,8 +1780,21 @@ private void OnApplicationQuit() #endif } - // Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit + /// + /// Called when a GameObject or component is about to be destroyed. + /// + /// + /// Override this method to implement custom teardown logic for a derived . + /// Ensure you call base.OnDestroy() in your override to allow the base teardown to happen. + /// protected virtual void OnDestroy() + { + DestroyInternal(); + } + + // Internal destroy method that can't be overridden by derived classes. + // Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit + private void DestroyInternal() { if (Log == null) { From ddc074eda181e2ded8c16f10d6b34dd62b9f7d63 Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 3 Nov 2026 10:17:24 -0500 Subject: [PATCH 3/6] Fix malformed XML doc and simplify logger API --- .../Runtime/Core/NetworkManager.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 0e3540975b..b5f25694ae 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1077,12 +1077,12 @@ public void SetSingleton() /// /// Override this method to implement custom initialization logic for a derived . /// Ensure you call base.Awake() in your override to allow the base initialization to happen. - /// + /// protected virtual void Awake() { if (Log == null) { - Log = new ContextualLogger(this, false); + Log = new ContextualLogger(this); } NetworkConfig?.InitializePrefabs(); @@ -1353,7 +1353,7 @@ public bool StartServer() { if (Log == null) { - Log = new ContextualLogger(this, false); + Log = new ContextualLogger(this); Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); } Log.CaptureFunctionCall(); @@ -1418,7 +1418,7 @@ public bool StartClient() { if (Log == null) { - Log = new ContextualLogger(this, false); + Log = new ContextualLogger(this); Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); } Log.CaptureFunctionCall(); @@ -1476,7 +1476,7 @@ public bool StartHost() { if (Log == null) { - Log = new ContextualLogger(this, false); + Log = new ContextualLogger(this); Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); } Log.CaptureFunctionCall(); @@ -1642,7 +1642,7 @@ private void OnSceneUnloaded(Scene scene) { if (gameObject != null && scene == gameObject.scene) { - OnDestroy(); + DestroyInternal(); } } @@ -1757,7 +1757,7 @@ internal void ShutdownInternal() OnStopped?.Invoke(); } - // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application. + // Ensures that the NetworkManager is cleaned up before DestroyInternal is run on NetworkObjects and NetworkBehaviours when quitting the application. private void OnApplicationQuit() { // Abrupt shutdown (or immediate exit of play mode). @@ -1771,7 +1771,7 @@ private void OnApplicationQuit() { return; } - OnDestroy(); + DestroyInternal(); #if UNITY_EDITOR if (Singleton != null) { @@ -1798,7 +1798,7 @@ private void DestroyInternal() { if (Log == null) { - Log = new ContextualLogger(this, false); + Log = new ContextualLogger(this); Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); } @@ -1897,7 +1897,7 @@ internal void OnValidate() if (Log == null) { - Log = new ContextualLogger(this, false); + Log = new ContextualLogger(this); } // Do a validation pass on NetworkConfig properties From 7c6aaf2cd6fe215579e08bbe72a9ca8c750579d8 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 5 Nov 2026 10:29:56 -0500 Subject: [PATCH 4/6] Bump to minor version due to new API --- .../Runtime/Core/NetworkManager.cs | 8 ++++---- com.unity.netcode.gameobjects/package.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b5f25694ae..36bb40d760 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1354,7 +1354,7 @@ public bool StartServer() if (Log == null) { Log = new ContextualLogger(this); - Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); + Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable() is called in any derived NetworkManager classes.")); } Log.CaptureFunctionCall(); @@ -1419,7 +1419,7 @@ public bool StartClient() if (Log == null) { Log = new ContextualLogger(this); - Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); + Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable() is called in any derived NetworkManager classes.")); } Log.CaptureFunctionCall(); @@ -1477,7 +1477,7 @@ public bool StartHost() if (Log == null) { Log = new ContextualLogger(this); - Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); + Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable() is called in any derived NetworkManager classes.")); } Log.CaptureFunctionCall(); @@ -1799,7 +1799,7 @@ private void DestroyInternal() if (Log == null) { Log = new ContextualLogger(this); - Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable is called in any derived NetworkManager classes.")); + Log.Warning(new Context(LogLevel.Normal, "No logger defined. Ensure base.Awake() and base.OnEnable() is called in any derived NetworkManager classes.")); } try diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index e257255fcc..124d7ce7fa 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.13.4", + "version": "2.14.0", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", @@ -15,4 +15,4 @@ "path": "Samples~/Bootstrap" } ] -} \ No newline at end of file +} From 5fe1822cf6e7de2cc542fa14e4bd0d7fb6da2c9a Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 5 Nov 2026 11:11:54 -0500 Subject: [PATCH 5/6] dotnet-fix --- testproject/Assets/Tests/Runtime/NetworkManagerTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs index e1262bd920..5dd74a95ba 100644 --- a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs @@ -223,14 +223,14 @@ public IEnumerator ValidateDerivedNetworkManager() client.StartClient(); var timeoutHelper = new TimeoutHelper(); - yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => client.IsListening, timeoutHelper); + yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => client.IsListening, timeoutHelper); Assert.That(timeoutHelper.TimedOut, Is.False, "Client timed out waiting to connect!"); Object.Destroy(client.gameObject); server.Shutdown(); timeoutHelper = new TimeoutHelper(); - yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => !server.IsListening, timeoutHelper); + yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => !server.IsListening, timeoutHelper); Assert.That(timeoutHelper.TimedOut, Is.False, "Timed out waiting for server to shutdown!"); isListening = server.StartHost(); From b9b27abcd53057fba0b23399ebc319a69245bdc6 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 5 Nov 2026 16:27:44 -0500 Subject: [PATCH 6/6] Update CHANGELOG --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index c8306d1267..30a749353b 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed a NullReferenceException that would be thrown if a MonoBehaviour derived from NetworkManager defines it's own Awake or OnEnable. (#4237) - Issue where `NetworkTime` reported a `TickOffset` of almost a whole tick, instead of zero, for some times that land on a tick (for example 0.3 at a tick rate of 10), which put `TickWithPartial` one tick ahead or, in long sessions, `Tick` one tick behind. Times between minus one tick and zero now report tick -1 with a positive `TickOffset`, as other negative times do. (#4227) - Issue where a growable `FastBufferWriter` created with a size of 0 never returned from its first write or `TryBeginWrite` call that needed the buffer to grow. (#4223) - Issue where a `FastBufferReader` created with an offset and the default length of -1 read past the end of its source. The `ArraySegment` overload that takes an offset now applies it from the start of the segment. (#4221)