From a1fac913513adf79197ffd2075c3c20b577e3531 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Wed, 24 Dec 2025 11:11:29 +0100 Subject: [PATCH 01/18] chore: optimization --- .../Runtime/Core/NetworkObject.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 311a3df650..70631a49c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -359,7 +359,8 @@ internal bool HasParentNetworkObject(Transform transform) /// /// Gets the NetworkManager that owns this NetworkObject instance /// - public NetworkManager NetworkManager => NetworkManagerOwner ? NetworkManagerOwner : NetworkManager.Singleton; + // TODO use the correct one, here are we sure it is spawned? if it is, use NetworkManager + public NetworkManager NetworkManager = NetworkManager.Singleton; /// /// Useful to know if we should or should not send a message @@ -1114,8 +1115,11 @@ public bool HasOwnershipStatus(OwnershipStatus status) [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool InternalHasAuthority() { - var networkManager = NetworkManager; - return networkManager.DistributedAuthorityMode ? OwnerClientId == networkManager.LocalClientId : networkManager.IsServer; + if (!IsSpawned) + { + return false; + } + return NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer; } /// @@ -1166,17 +1170,17 @@ private bool InternalHasAuthority() /// /// Gets if the object is the personal clients player object /// - public bool IsLocalPlayer => NetworkManager != null && IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId; + public bool IsLocalPlayer => IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId; /// /// Gets if the object is owned by the local player or if the object is the local player object /// - public bool IsOwner => NetworkManager != null && OwnerClientId == NetworkManager.LocalClientId; + public bool IsOwner => OwnerClientId == NetworkManager.LocalClientId; /// /// Gets Whether or not the object is owned by anyone /// - public bool IsOwnedByServer => NetworkManager != null && OwnerClientId == NetworkManager.ServerClientId; + public bool IsOwnedByServer => OwnerClientId == NetworkManager.ServerClientId; /// /// Gets if the object has yet been spawned across the network From b1b85ac62ed2cc264deceeb7814f0a9a3c2fde3d Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 17:16:38 +0100 Subject: [PATCH 02/18] chore: optimize NetworkManager accessors --- .../Runtime/Core/NetworkObject.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 70631a49c3..a75acd4460 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -357,9 +357,8 @@ internal bool HasParentNetworkObject(Transform transform) } /// - /// Gets the NetworkManager that owns this NetworkObject instance + /// Gets the NetworkManager that owns this NetworkObject instance. /// - // TODO use the correct one, here are we sure it is spawned? if it is, use NetworkManager public NetworkManager NetworkManager = NetworkManager.Singleton; /// @@ -1125,7 +1124,7 @@ private bool InternalHasAuthority() /// /// The NetworkManager that owns this NetworkObject. /// This property controls where this NetworkObject belongs. - /// This property is null by default currently, which means that the above NetworkManager getter will return the Singleton. + /// This property is null by default currently. /// In the future this is the path where alternative NetworkManagers should be injected for running multi NetworkManagers /// internal NetworkManager NetworkManagerOwner; @@ -2233,7 +2232,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) return false; } - if (NetworkManager == null || !NetworkManager.IsListening) + if (!NetworkManager.IsListening) { return false; } @@ -2254,7 +2253,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent != null && (IsSpawned ^ parent.IsSpawned) && NetworkManager != null && !NetworkManager.ShutdownInProgress) + if (parent != null && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) { if (NetworkManager.LogLevel <= LogLevel.Developer) { @@ -2292,7 +2291,7 @@ private void OnTransformParentChanged() return; } - if (NetworkManager == null || !NetworkManager.IsListening) + if (!NetworkManager.IsListening) { // DANGO-TODO: Review as to whether we want to provide a better way to handle changing parenting of objects when the // object is not spawned. Really, we shouldn't care about these types of changes. @@ -3422,9 +3421,10 @@ internal void SubscribeToActiveSceneForSynch() /// private void CurrentlyActiveSceneChanged(Scene current, Scene next) { + // TODO should we remove NetworkManager null check here? // Early exit if there is no NetworkManager assigned, the NetworkManager is shutting down, the NetworkObject // is not spawned, or an in-scene placed NetworkObject - if (NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false) + if (NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false) { return; } @@ -3523,10 +3523,11 @@ private void Awake() /// internal bool UpdateForSceneChanges() { + // TODO should we remove NetworkManager null check here? // Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle - if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager == null || NetworkManager.ShutdownInProgress || + if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager.ShutdownInProgress || !NetworkManager.NetworkConfig.EnableSceneManagement || IsSceneObject != false || !gameObject) { // Stop checking for a scene migration @@ -3598,7 +3599,7 @@ internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour) { if (networkBehaviour.IsSpawned && IsSpawned) { - if (NetworkManager?.LogLevel == LogLevel.Developer) + if (NetworkManager.LogLevel == LogLevel.Developer) { NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)"); } From 606c9f73ed877c892d19f58842fcf8bd32f7f4b4 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:17:31 +0100 Subject: [PATCH 03/18] Using m_CachedNetworkObject instead of NetworkObject --- .../Runtime/Components/NetworkTransform.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7c44ba13cb..8d751c72ec 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3625,7 +3625,7 @@ private void CleanUpOnDestroyOrDespawn() #else var forUpdate = true; #endif - if (m_CachedNetworkObject != null) + if (m_CachedNetworkObject) { NetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); } @@ -3695,7 +3695,7 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() } private NetworkObject m_CachedNetworkObject; /// - /// The internal initialzation method to allow for internal API adjustments + /// The internal initialization method to allow for internal API adjustments /// /// private void InternalInitialization(bool isOwnershipChange = false) @@ -3707,7 +3707,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_CachedNetworkObject = NetworkObject; // Determine if this is the first NetworkTransform in the associated NetworkObject's list - m_IsFirstNetworkTransform = NetworkObject.NetworkTransforms[0] == this; + m_IsFirstNetworkTransform = m_CachedNetworkObject.NetworkTransforms[0] == this; if (m_CachedNetworkManager && m_CachedNetworkManager.DistributedAuthorityMode) { @@ -3719,7 +3719,7 @@ private void InternalInitialization(bool isOwnershipChange = false) { if (CanCommitToTransform) { - if (NetworkObject.HasParentNetworkObject(transform)) + if (m_CachedNetworkObject.HasParentNetworkObject(transform)) { InLocalSpace = true; } @@ -3763,7 +3763,7 @@ private void InternalInitialization(bool isOwnershipChange = false) if (CanCommitToTransform) { // Make sure authority doesn't get added to updates (no need to do this on the authority side) - m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); + m_CachedNetworkManager.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); @@ -3792,7 +3792,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_PreviousScaleLerpSmoothing = ScaleLerpSmoothing; // Non-authority needs to be added to updates for interpolation and applying state purposes - m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true); + m_CachedNetworkManager.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, true); // Remove this instance from the tick update DeregisterForTickUpdate(this); ResetInterpolatedStateToCurrentAuthoritativeState(); From 18a180590a2877cd7acb399ac86a478a2f79887a Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:18:30 +0100 Subject: [PATCH 04/18] Using m_CachedNetworkManager instead of NetworkManager --- .../Runtime/Components/NetworkTransform.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 8d751c72ec..3693a0afd4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3554,7 +3554,7 @@ protected internal override void InternalOnNetworkPostSpawn() } // Standard non-authority synchronization is handled here - if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) + if (!CanCommitToTransform && m_CachedNetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing) { NonAuthorityFinalizeSynchronization(); } @@ -3627,7 +3627,7 @@ private void CleanUpOnDestroyOrDespawn() #endif if (m_CachedNetworkObject) { - NetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); + m_CachedNetworkManager?.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); } DeregisterForTickUpdate(this); @@ -3673,7 +3673,7 @@ protected virtual void OnInitialize(ref NetworkVariable r /// private void ResetInterpolatedStateToCurrentAuthoritativeState() { - var serverTime = NetworkManager.ServerTime.Time; + var serverTime = m_CachedNetworkManager.ServerTime.Time; #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var position = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetPosition() : GetSpaceRelativePosition(); var rotation = m_UseRigidbodyForMotion ? m_NetworkRigidbodyInternal.GetRotation() : GetSpaceRelativeRotation(); @@ -3890,7 +3890,7 @@ private void DefaultParentChanged() m_RotationInterpolator.Clear(); // Always use NetworkManager here as this can be invoked prior to spawning - var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, NetworkManager.ServerTime.Tick).Time; + var tempTime = new NetworkTime(NetworkManager.NetworkConfig.TickRate, m_CachedNetworkManager.ServerTime.Tick).Time; UpdatePositionInterpolator(m_InternalCurrentPosition, tempTime, true); m_ScaleInterpolator.ResetTo(m_InternalCurrentScale, tempTime); m_RotationInterpolator.ResetTo(m_InternalCurrentRotation, tempTime); @@ -3922,7 +3922,7 @@ internal override void InternalOnNetworkObjectParentChanged(NetworkObject parent if (LastTickSync == m_LocalAuthoritativeNetworkState.GetNetworkTick()) { m_InternalCurrentPosition = m_LastStateTargetPosition = GetSpaceRelativePosition(); - m_PositionInterpolator.ResetTo(m_PositionInterpolator.Parent, m_InternalCurrentPosition, NetworkManager.ServerTime.Time); + m_PositionInterpolator.ResetTo(m_PositionInterpolator.Parent, m_InternalCurrentPosition, m_CachedNetworkManager.ServerTime.Time); if (InLocalSpace) { transform.localPosition = m_InternalCurrentPosition; @@ -3954,7 +3954,7 @@ internal override void InternalOnNetworkObjectParentChanged(NetworkObject parent { m_InternalCurrentRotation = GetSpaceRelativeRotation(); m_TargetRotation = m_InternalCurrentRotation.eulerAngles; - m_RotationInterpolator.ResetTo(m_RotationInterpolator.Parent, m_InternalCurrentRotation, NetworkManager.ServerTime.Time); + m_RotationInterpolator.ResetTo(m_RotationInterpolator.Parent, m_InternalCurrentRotation, m_CachedNetworkManager.ServerTime.Time); if (InLocalSpace) { transform.localRotation = m_InternalCurrentRotation; @@ -4638,13 +4638,13 @@ private void UpdateTransformState() { continue; } - NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, clientId); + m_CachedNetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, clientId); } } else { // Clients (owner authoritative) send messages to the server-host - NetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId); + m_CachedNetworkManager.MessageManager.SendMessage(ref m_OutboundMessage, networkDelivery, NetworkManager.ServerClientId); } m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OutboundMessage.BytesWritten; } @@ -4783,7 +4783,7 @@ public NetworkTransformTickRegistration(NetworkManager networkManager) internal void RegisterForTickSynchronization() { s_TickSynchPosition++; - m_NextTickSync = NetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate); + m_NextTickSync = m_CachedNetworkManager.ServerTime.Tick + (s_TickSynchPosition % (int)NetworkManager.NetworkConfig.TickRate); } private static void RegisterNetworkManagerForTickUpdate(NetworkManager networkManager) From 7a49a9a3ab96823fe953d2f3043910486a542939 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:26:36 +0100 Subject: [PATCH 05/18] Convert "==" to explicit Unity engine object lifetime check --- .../Runtime/Core/NetworkBehaviour.cs | 4 ++-- .../Runtime/Core/NetworkObject.cs | 22 +++++++------------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 9910668f59..4368c1e972 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -574,7 +574,7 @@ public NetworkObject NetworkObject { get { - if (m_NetworkObject != null) + if (m_NetworkObject) { return m_NetworkObject; } @@ -594,7 +594,7 @@ public NetworkObject NetworkObject // or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages // per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages. // We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy) - if (IsSpawned && m_NetworkObject == null && (m_NetworkManager == null || !m_NetworkManager.ShutdownInProgress)) + if (IsSpawned && !m_NetworkObject && (!m_NetworkManager || !m_NetworkManager.ShutdownInProgress)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index a75acd4460..adeffe9884 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -52,13 +52,7 @@ public sealed class NetworkObject : MonoBehaviour /// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0 /// [HideInInspector] - public uint PrefabIdHash - { - get - { - return GlobalObjectIdHash; - } - } + public uint PrefabIdHash => GlobalObjectIdHash; /// /// InstantiationData sent during the instantiation process. @@ -2167,7 +2161,7 @@ internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays) public bool TrySetParent(Transform parent, bool worldPositionStays = true) { // If we are removing ourself from a parent - if (parent == null) + if (!parent) { return TrySetParent((NetworkObject)null, worldPositionStays); } @@ -2253,7 +2247,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (parent != null && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) + if (!parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) { if (NetworkManager.LogLevel <= LogLevel.Developer) { @@ -2265,7 +2259,7 @@ internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays m_CachedWorldPositionStays = worldPositionStays; - if (parent == null) + if (!parent) { CurrentParent = null; transform.SetParent(null, worldPositionStays); @@ -2381,7 +2375,7 @@ private void OnTransformParentChanged() var message = new ParentSyncMessage { NetworkObjectId = NetworkObjectId, - IsLatestParentSet = m_LatestParent != null && m_LatestParent.HasValue, + IsLatestParentSet = m_LatestParent is not null, LatestParent = m_LatestParent, RemoveParent = removeParent, AuthorityApplied = authorityApplied, @@ -2458,7 +2452,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // has been set, this will not be entered into again (i.e. the later code will be invoked and // users will get notifications when the parent changes). var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value; - if (transform.parent != null && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) + if (!transform.parent && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) { var parentNetworkObject = transform.parent.GetComponent(); @@ -2466,7 +2460,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting. // Note: We only start tracking parenting if the user removes the child from the standard GameObject // parent and then re-parents the child under a GameObject with a NetworkObject component attached. - if (parentNetworkObject == null) + if (!parentNetworkObject) { // If we are parented under a GameObject, go ahead and mark the world position stays as false // so clients synchronize their transform in local space. (only for in-scene placed NetworkObjects) @@ -3176,7 +3170,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager { var obj = new SceneObject { - HasParent = transform.parent != null, + HasParent = m_CachedParent is not null, WorldPositionStays = m_CachedWorldPositionStays, NetworkObjectId = NetworkObjectId, OwnerClientId = OwnerClientId, From 7d9ffd5c786d07dedac1e8ac50f50470da4fb9b8 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:27:22 +0100 Subject: [PATCH 06/18] Fix wording --- .../Runtime/Core/NetworkObject.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index adeffe9884..03a03b4b2e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2157,7 +2157,7 @@ internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not reparenting was successful. + /// Whether or not re-parenting was successful. public bool TrySetParent(Transform parent, bool worldPositionStays = true) { // If we are removing ourself from a parent @@ -2177,7 +2177,7 @@ public bool TrySetParent(Transform parent, bool worldPositionStays = true) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not reparenting was successful. + /// Whether or not re-parenting was successful. public bool TrySetParent(GameObject parent, bool worldPositionStays = true) { // If we are removing ourself from a parent @@ -2218,7 +2218,7 @@ public bool TryRemoveParent(bool worldPositionStays = true) /// /// The new parent for this NetworkObject transform will be the child of. /// If true, the parent-relative position, scale and rotation are modified such that the object keeps the same world space position, rotation and scale as before. - /// Whether or not reparenting was successful. + /// Whether or not re-parenting was successful. public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) { if (!AutoObjectParentSync) @@ -2235,7 +2235,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // It wouldn't make sense to not allow parenting, but keeping this note here as a reminder. var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner); - // If we don't have authority and we are not shutting down, then don't allow any parenting. + // If we don't have authority, and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. if (!isAuthority && !NetworkManager.ShutdownInProgress) { @@ -2295,7 +2295,7 @@ private void OnTransformParentChanged() return; } transform.parent = m_CachedParent; - Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before reparenting")); + Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before re-parenting")); return; } var isAuthority = false; @@ -2307,18 +2307,17 @@ private void OnTransformParentChanged() // If we do not have authority and we are spawned if (!isAuthority && IsSpawned) { - - // If the cached parent has not already been set and we are in distributed authority mode, then log an exception and exit early as a non-authority instance + // If the cached parent has not already been set, and we are in distributed authority mode, then log an exception and exit early as a non-authority instance // is trying to set the parent. if (distributedAuthority) { transform.parent = m_CachedParent; - NetworkLog.LogError($"[Not Owner] Only the owner-authority of child {gameObject.name}'s {nameof(NetworkObject)} component can reparent it!"); + NetworkLog.LogError($"[Not Owner] Only the owner-authority of child {gameObject.name}'s {nameof(NetworkObject)} component can re-parent it!"); } else { transform.parent = m_CachedParent; - Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); + Debug.LogException(new NotServerException($"Only the server can re-parent {nameof(NetworkObject)}s")); } return; } @@ -2336,7 +2335,7 @@ private void OnTransformParentChanged() else { transform.parent = m_CachedParent; - Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented after being spawned")); + Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be re-parented after being spawned")); } return; } @@ -2356,7 +2355,7 @@ private void OnTransformParentChanged() { transform.parent = m_CachedParent; AuthorityAppliedParenting = false; - Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be reparented under another spawned {nameof(NetworkObject)}")); + Debug.LogException(new SpawnStateException($"{nameof(NetworkObject)} can only be re-parented under another spawned {nameof(NetworkObject)}")); return; } @@ -2457,7 +2456,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa var parentNetworkObject = transform.parent.GetComponent(); // If parentNetworkObject is null then the parent is a GameObject without a NetworkObject component - // attached. Under this case, we preserve the hierarchy but we don't keep track of the parenting. + // attached. Under this case, we preserve the hierarchy, but we don't keep track of the parenting. // Note: We only start tracking parenting if the user removes the child from the standard GameObject // parent and then re-parents the child under a GameObject with a NetworkObject component attached. if (!parentNetworkObject) @@ -2476,7 +2475,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa else { // If we made it this far, go ahead and set the network parenting values - // with the WorldPoisitonSays value set to false + // with the WorldPositionSays value set to false. // Note: Since in-scene placed NetworkObjects are parented in the scene // the default "assumption" is that children are parenting local space // relative. @@ -2489,7 +2488,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa } } - // If we are removing the parent or our latest parent is not set, then remove the parent + // If we are removing the parent or our latest parent is not set, then remove the parent. // removeParent is only set when: // - The server-side NetworkObject.OnTransformParentChanged is invoked and the parent is being removed // - The client-side when handling a ParentSyncMessage @@ -3192,7 +3191,7 @@ internal SceneObject GetMessageSceneObject(ulong targetClientId = NetworkManager // Handle Parenting if (!AlwaysReplicateAsRoot && obj.HasParent) { - var parentNetworkObject = transform.parent.GetComponent(); + var parentNetworkObject = m_CachedParent.GetComponent(); if (parentNetworkObject) { From b255749d0823ac0d2256dfc4c48bf5dcd589d287 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:28:17 +0100 Subject: [PATCH 07/18] Use m_CachedParent and Scene --- .../Runtime/Core/NetworkObject.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 03a03b4b2e..7defc1d350 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -334,17 +334,17 @@ private void CheckForInScenePlaced() internal bool HasParentNetworkObject(Transform transform) { - if (transform.parent != null) + if (m_CachedParent != null) { - var networkObject = transform.parent.GetComponent(); + var networkObject = m_CachedParent.GetComponent(); if (networkObject != null && networkObject != this) { return true; } - if (transform.parent.parent != null) + if (m_CachedParent.parent != null) { - return HasParentNetworkObject(transform.parent); + return HasParentNetworkObject(m_CachedParent); } } return false; @@ -3427,7 +3427,7 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Only dynamically spawned NetworkObjects that are not already in the newly assigned active scene will migrate // and update their scene handles - if (IsSceneObject.HasValue && !IsSceneObject.Value && gameObject.scene != next && gameObject.transform.parent == null) + if (IsSceneObject.HasValue && !IsSceneObject.Value && m_SceneOrigin != next && m_CachedParent == null) { SceneManager.MoveGameObjectToScene(gameObject, next); SceneChangedUpdate(next); @@ -3486,7 +3486,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) OnMigratedToNewScene?.Invoke(); // Only the authority side will notify clients of non-parented NetworkObject scene changes - if (isAuthority && notify && transform.parent == null) + if (isAuthority && notify && !m_CachedParent) { NetworkManager.SceneManager.NotifyNetworkObjectSceneChanged(this); } From 0ebab51c3d0ad6eedc19e21466cf66e0f07aa1ba Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 19:29:09 +0100 Subject: [PATCH 08/18] Changed parentObject scope --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 7defc1d350..512d992dd3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2341,10 +2341,9 @@ private void OnTransformParentChanged() } var removeParent = false; var parentTransform = transform.parent; - var parentObject = (NetworkObject)null; if (parentTransform != null) { - if (!transform.parent.TryGetComponent(out parentObject)) + if (!transform.parent.TryGetComponent(out NetworkObject parentObject)) { transform.parent = m_CachedParent; AuthorityAppliedParenting = false; From a1664825d6e74e3b8dd35e101b1a776ab5d3f9cd Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 20:36:36 +0100 Subject: [PATCH 09/18] Removing comments (added in PR) --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 512d992dd3..8be6d70fed 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -3413,8 +3413,7 @@ internal void SubscribeToActiveSceneForSynch() /// private void CurrentlyActiveSceneChanged(Scene current, Scene next) { - // TODO should we remove NetworkManager null check here? - // Early exit if there is no NetworkManager assigned, the NetworkManager is shutting down, the NetworkObject + // Early exit if the NetworkManager is shutting down, the NetworkObject // is not spawned, or an in-scene placed NetworkObject if (NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false) { @@ -3515,8 +3514,7 @@ private void Awake() /// internal bool UpdateForSceneChanges() { - // TODO should we remove NetworkManager null check here? - // Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned, + // Early exit if SceneMigrationSynchronization is disabled, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager.ShutdownInProgress || From f817796cd8d01375ab8887a51a9d01cd0bb12796 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 29 Dec 2025 20:37:56 +0100 Subject: [PATCH 10/18] Revert null check removal for Log --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 8be6d70fed..2a7844096e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -3589,7 +3589,7 @@ internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour) { if (networkBehaviour.IsSpawned && IsSpawned) { - if (NetworkManager.LogLevel == LogLevel.Developer) + if (NetworkManager?.LogLevel == LogLevel.Developer) { NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)"); } From e8d60537be7673361184d44446fc98e7e968e9f1 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 5 Jan 2026 16:14:33 +0100 Subject: [PATCH 11/18] Avoiding breaking change --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 2a7844096e..6390789b37 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -353,7 +353,7 @@ internal bool HasParentNetworkObject(Transform transform) /// /// Gets the NetworkManager that owns this NetworkObject instance. /// - public NetworkManager NetworkManager = NetworkManager.Singleton; + public NetworkManager NetworkManager { get; private set; } = NetworkManager.Singleton; /// /// Useful to know if we should or should not send a message From 43cc1cd0622b6c5627200f1995d9d53ea5b5400b Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Mon, 5 Jan 2026 16:57:23 +0100 Subject: [PATCH 12/18] Reverting NetworkManager changes --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 6390789b37..a363f69625 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -353,7 +353,7 @@ internal bool HasParentNetworkObject(Transform transform) /// /// Gets the NetworkManager that owns this NetworkObject instance. /// - public NetworkManager NetworkManager { get; private set; } = NetworkManager.Singleton; + public NetworkManager NetworkManager => NetworkManagerOwner ? NetworkManagerOwner : NetworkManager.Singleton; /// /// Useful to know if we should or should not send a message From 25e95773a6346791c5dffd5ffda3c014266fea96 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Tue, 6 Jan 2026 16:09:05 +0100 Subject: [PATCH 13/18] Address PR feedback --- .../Runtime/Core/NetworkObject.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index a363f69625..3b6c78ae5f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1118,7 +1118,7 @@ private bool InternalHasAuthority() /// /// The NetworkManager that owns this NetworkObject. /// This property controls where this NetworkObject belongs. - /// This property is null by default currently. + /// This property will be null when the NetworkObject is not spawned. /// In the future this is the path where alternative NetworkManagers should be injected for running multi NetworkManagers /// internal NetworkManager NetworkManagerOwner; @@ -1163,17 +1163,17 @@ private bool InternalHasAuthority() /// /// Gets if the object is the personal clients player object /// - public bool IsLocalPlayer => IsPlayerObject && OwnerClientId == NetworkManager.LocalClientId; + public bool IsLocalPlayer => IsSpawned && IsPlayerObject && OwnerClientId == NetworkManagerOwner.LocalClientId; /// /// Gets if the object is owned by the local player or if the object is the local player object /// - public bool IsOwner => OwnerClientId == NetworkManager.LocalClientId; + public bool IsOwner => IsSpawned && OwnerClientId == NetworkManager.LocalClientId; /// /// Gets Whether or not the object is owned by anyone /// - public bool IsOwnedByServer => OwnerClientId == NetworkManager.ServerClientId; + public bool IsOwnedByServer => IsSpawned && OwnerClientId == NetworkManager.ServerClientId; /// /// Gets if the object has yet been spawned across the network @@ -2275,7 +2275,7 @@ internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays private void OnTransformParentChanged() { - if (!AutoObjectParentSync || NetworkManager.ShutdownInProgress) + if (!AutoObjectParentSync) { return; } @@ -2285,8 +2285,12 @@ private void OnTransformParentChanged() return; } - if (!NetworkManager.IsListening) + if (!IsSpawned || !NetworkManager.IsListening) { + if (NetworkManager.ShutdownInProgress) + { + return; + } // DANGO-TODO: Review as to whether we want to provide a better way to handle changing parenting of objects when the // object is not spawned. Really, we shouldn't care about these types of changes. if (NetworkManager.DistributedAuthorityMode && m_CachedParent != null && transform.parent == null) @@ -2302,7 +2306,7 @@ private void OnTransformParentChanged() // With distributed authority, we need to track "valid authoritative" parenting changes. // So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change". isAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); - var distributedAuthority = NetworkManager.DistributedAuthorityMode; + var distributedAuthority = NetworkManagerOwner.DistributedAuthorityMode; // If we do not have authority and we are spawned if (!isAuthority && IsSpawned) @@ -2373,7 +2377,7 @@ private void OnTransformParentChanged() var message = new ParentSyncMessage { NetworkObjectId = NetworkObjectId, - IsLatestParentSet = m_LatestParent is not null, + IsLatestParentSet = m_LatestParent is not null && m_LatestParent.HasValue, LatestParent = m_LatestParent, RemoveParent = removeParent, AuthorityApplied = authorityApplied, @@ -3415,7 +3419,7 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Early exit if the NetworkManager is shutting down, the NetworkObject // is not spawned, or an in-scene placed NetworkObject - if (NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false) + if (!IsSpawned || IsSceneObject != false || NetworkManager.ShutdownInProgress) { return; } @@ -3425,7 +3429,7 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Only dynamically spawned NetworkObjects that are not already in the newly assigned active scene will migrate // and update their scene handles - if (IsSceneObject.HasValue && !IsSceneObject.Value && m_SceneOrigin != next && m_CachedParent == null) + if (IsSceneObject.HasValue && !IsSceneObject.Value && gameObject.scene != next && gameObject.transform.parent == null) { SceneManager.MoveGameObjectToScene(gameObject, next); SceneChangedUpdate(next); @@ -3517,8 +3521,8 @@ internal bool UpdateForSceneChanges() // Early exit if SceneMigrationSynchronization is disabled, // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle - if (!SceneMigrationSynchronization || !IsSpawned || NetworkManager.ShutdownInProgress || - !NetworkManager.NetworkConfig.EnableSceneManagement || IsSceneObject != false || !gameObject) + if (!SceneMigrationSynchronization || !IsSpawned || NetworkManagerOwner.ShutdownInProgress || + !NetworkManagerOwner.NetworkConfig.EnableSceneManagement || IsSceneObject != false || !gameObject) { // Stop checking for a scene migration return false; From 73a0bf87d3dd930ef6df476bb8cad8207f3379bf Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Wed, 7 Jan 2026 19:26:48 +0100 Subject: [PATCH 14/18] Fix opposite null check --- .../Runtime/Components/NetworkTransform.cs | 4 ++-- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 3693a0afd4..834923a33c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3763,7 +3763,7 @@ private void InternalInitialization(bool isOwnershipChange = false) if (CanCommitToTransform) { // Make sure authority doesn't get added to updates (no need to do this on the authority side) - m_CachedNetworkManager.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, false); + m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); @@ -3792,7 +3792,7 @@ private void InternalInitialization(bool isOwnershipChange = false) m_PreviousScaleLerpSmoothing = ScaleLerpSmoothing; // Non-authority needs to be added to updates for interpolation and applying state purposes - m_CachedNetworkManager.NetworkTransformRegistration(m_CachedNetworkObject, forUpdate, true); + m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, true); // Remove this instance from the tick update DeregisterForTickUpdate(this); ResetInterpolatedStateToCurrentAuthoritativeState(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 3b6c78ae5f..c8eff6a5fa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1168,7 +1168,7 @@ private bool InternalHasAuthority() /// /// Gets if the object is owned by the local player or if the object is the local player object /// - public bool IsOwner => IsSpawned && OwnerClientId == NetworkManager.LocalClientId; + public bool IsOwner => IsSpawned && OwnerClientId == NetworkManagerOwner.LocalClientId; /// /// Gets Whether or not the object is owned by anyone @@ -2247,7 +2247,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) internal bool InternalTrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (!parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) + if (parent && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) { if (NetworkManager.LogLevel <= LogLevel.Developer) { @@ -2454,7 +2454,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // has been set, this will not be entered into again (i.e. the later code will be invoked and // users will get notifications when the parent changes). var isInScenePlaced = IsSceneObject.HasValue && IsSceneObject.Value; - if (!transform.parent && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) + if (transform.parent && !removeParent && !m_LatestParent.HasValue && isInScenePlaced) { var parentNetworkObject = transform.parent.GetComponent(); From 3f17e8354db1722a7512b981c4369d1653faacb1 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 8 Jan 2026 10:24:40 +0100 Subject: [PATCH 15/18] Address PR feedback --- com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index c8eff6a5fa..97338530cf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2226,7 +2226,8 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) return false; } - if (!NetworkManager.IsListening) + var networkManager = NetworkManager; + if (!networkManager || !networkManager.IsListening) { return false; } @@ -2237,7 +2238,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) // If we don't have authority, and we are not shutting down, then don't allow any parenting. // If we are shutting down and don't have authority then allow it. - if (!isAuthority && !NetworkManager.ShutdownInProgress) + if (!isAuthority && !networkManager.ShutdownInProgress) { return false; } From e02e462924485444e30a2a00029da5af07d3f172 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 8 Jan 2026 17:21:39 +0100 Subject: [PATCH 16/18] Cleanup NetworkObject: use cached NetworkManager WIP --- .../Runtime/Core/NetworkObject.cs | 140 ++++++++++-------- 1 file changed, 80 insertions(+), 60 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 97338530cf..7f89ca3cdb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -358,7 +358,7 @@ internal bool HasParentNetworkObject(Transform transform) /// /// Useful to know if we should or should not send a message /// - internal bool HasRemoteObservers => !(Observers.Count == 0 || (Observers.Contains(NetworkManager.LocalClientId) && Observers.Count == 1)); + internal bool HasRemoteObservers => !(Observers.Count == 0 || (Observers.Contains(NetworkManagerOwner.LocalClientId) && Observers.Count == 1)); /// /// Distributed Authority Mode Only @@ -392,29 +392,29 @@ internal bool HasParentNetworkObject(Transform transform) /// Defaults to true, determines whether the will be destroyed. public void DeferDespawn(int tickOffset, bool destroy = true) { - if (!NetworkManager.DistributedAuthorityMode) + if (!IsSpawned) { - NetworkLog.LogError($"This method is only available in distributed authority mode."); + NetworkLog.LogError($"Cannot defer despawning {name} because it is not spawned!"); return; } - if (!IsSpawned) + if (!NetworkManagerOwner.DistributedAuthorityMode) { - NetworkLog.LogError($"Cannot defer despawning {name} because it is not spawned!"); + NetworkLog.LogError($"This method is only available in distributed authority mode."); return; } if (!HasAuthority) { - NetworkLog.LogError($"Only the authority can invoke {nameof(DeferDespawn)} and local Client-{NetworkManager.LocalClientId} is not the authority of {name}!"); + NetworkLog.LogError($"Only the authority can invoke {nameof(DeferDespawn)} and local Client-{NetworkManagerOwner.LocalClientId} is not the authority of {name}!"); return; } // Apply the relative tick offset for when this NetworkObject should be despawned on // non-authoritative instances. - DeferredDespawnTick = NetworkManager.ServerTime.Tick + tickOffset; + DeferredDespawnTick = NetworkManagerOwner.ServerTime.Tick + tickOffset; - var connectionManager = NetworkManager.ConnectionManager; + var connectionManager = NetworkManagerOwner.ConnectionManager; for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { @@ -426,7 +426,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true) } // DAHost handles sending updates to all clients - if (NetworkManager.DAHost) + if (NetworkManagerOwner.DAHost) { for (int i = 0; i < connectionManager.ConnectedClientsList.Count; i++) { @@ -595,17 +595,17 @@ internal void RemoveOwnershipExtended(OwnershipStatusExtended extended) /// true or false depending upon lock operation's success public bool SetOwnershipLock(bool lockOwnership = true) { - // If we are not in distributed autority mode, then exit early - if (!NetworkManager.DistributedAuthorityMode) + // If we don't have authority exit early + if (!HasAuthority) { - Debug.LogError($"[Feature Not Allowed In Client-Server Mode] Ownership flags are a distributed authority feature only!"); + NetworkLog.LogWarningServer($"[Attempted Lock Without Authority] Client-{NetworkManager.LocalClientId} is trying to lock ownership but does not have authority!"); return false; } - // If we don't have authority exit early - if (!HasAuthority) + // If we are not in distributed autority mode, then exit early + if (!NetworkManager.DistributedAuthorityMode) { - NetworkLog.LogWarningServer($"[Attempted Lock Without Authority] Client-{NetworkManager.LocalClientId} is trying to lock ownership but does not have authority!"); + Debug.LogError($"[Feature Not Allowed In Client-Server Mode] Ownership flags are a distributed authority feature only!"); return false; } @@ -730,6 +730,11 @@ public enum OwnershipRequestStatus /// This object is marked as SessionOwnerOnly and therefore cannot be requested /// SessionOwnerOnly, + + /// + /// The request is invalid (if not spawned for instance) + /// + Invalid, } /// @@ -748,8 +753,20 @@ public enum OwnershipRequestStatus /// public OwnershipRequestStatus RequestOwnership() { + // An ownership change request can only be made if spawned. + if (!IsSpawned) + { + + if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning($"[{name}] is trying to perform an ownership request but the object is not spawned!"); + } + + return OwnershipRequestStatus.Invalid; + } + // Exit early the local client is already the owner - if (OwnerClientId == NetworkManager.LocalClientId) + if (OwnerClientId == NetworkManagerOwner.LocalClientId) { return OwnershipRequestStatus.AlreadyOwner; } @@ -785,14 +802,14 @@ public OwnershipRequestStatus RequestOwnership() NetworkObjectId = NetworkObjectId, OwnerClientId = OwnerClientId, ClientIdCount = 1, - RequestClientId = NetworkManager.LocalClientId, + RequestClientId = NetworkManagerOwner.LocalClientId, ClientIds = new ulong[1] { OwnerClientId }, DistributedAuthorityMode = true, OwnershipFlags = (ushort)Ownership, }; - var sendTarget = NetworkManager.DAHost ? OwnerClientId : NetworkManager.ServerClientId; - NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, sendTarget); + var sendTarget = NetworkManagerOwner.DAHost ? OwnerClientId : NetworkManager.ServerClientId; + NetworkManagerOwner.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, sendTarget); return OwnershipRequestStatus.RequestSent; } @@ -855,9 +872,10 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) // respond to any additional ownership request with OwnershipRequestResponseStatus.RequestInProgress. AddOwnershipExtended(OwnershipStatusExtended.Requested); + // if OwnershipRequestStatus cannot be called before spawn // This action is always authorized as long as the client still has authority. // We need to pass in that this is a request approval ownership change. - NetworkManager.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true); + NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true); } else { @@ -871,15 +889,15 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) { ChangeMessageType = ChangeOwnershipMessage.ChangeType.RequestDenied, NetworkObjectId = NetworkObjectId, - OwnerClientId = NetworkManager.LocalClientId, // Always use the local clientId (see above notes) + OwnerClientId = NetworkManagerOwner.LocalClientId, // Always use the local clientId (see above notes) RequestClientId = clientRequestingOwnership, DistributedAuthorityMode = true, OwnershipRequestResponseStatus = (byte)response, OwnershipFlags = (ushort)Ownership, }; - var sendTarget = NetworkManager.DAHost ? clientRequestingOwnership : NetworkManager.ServerClientId; - NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, sendTarget); + var sendTarget = NetworkManagerOwner.DAHost ? clientRequestingOwnership : NetworkManager.ServerClientId; + NetworkManagerOwner.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, sendTarget); } } @@ -976,7 +994,7 @@ public enum OwnershipLockActions /// public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, OwnershipLockActions lockAction = OwnershipLockActions.None) { - if (status.HasFlag(OwnershipStatus.SessionOwner) && !NetworkManager.LocalClient.IsSessionOwner) + if (status.HasFlag(OwnershipStatus.SessionOwner) && !NetworkManagerOwner.LocalClient.IsSessionOwner) { NetworkLog.LogWarning("Only the session owner is allowed to set the ownership status to session owner only."); return false; @@ -1066,22 +1084,22 @@ internal void SendOwnershipStatusUpdate() OwnershipFlags = (ushort)Ownership, }; - if (NetworkManager.DAHost) + if (NetworkManagerOwner.DAHost) { foreach (var clientId in Observers) { - if (clientId == NetworkManager.LocalClientId) + if (clientId == NetworkManagerOwner.LocalClientId) { continue; } - NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, clientId); + NetworkManagerOwner.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, clientId); } } else { changeOwnership.ClientIdCount = Observers.Count; changeOwnership.ClientIds = Observers.ToArray(); - NetworkManager.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); + NetworkManagerOwner.ConnectionManager.SendMessage(ref changeOwnership, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); } } @@ -1171,7 +1189,7 @@ private bool InternalHasAuthority() public bool IsOwner => IsSpawned && OwnerClientId == NetworkManagerOwner.LocalClientId; /// - /// Gets Whether or not the object is owned by anyone + /// Gets whether or not the object is owned by anyone /// public bool IsOwnedByServer => IsSpawned && OwnerClientId == NetworkManager.ServerClientId; @@ -1434,7 +1452,7 @@ public void NetworkShow(ulong clientId) if (!HasAuthority) { - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!"); } @@ -1446,7 +1464,7 @@ public void NetworkShow(ulong clientId) if (Observers.Contains(clientId)) { - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { Debug.LogError($"The object {name} is already visible to Client-{clientId}!"); return; @@ -1459,13 +1477,13 @@ public void NetworkShow(ulong clientId) if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId)) { - if (NetworkManager.LogLevel <= LogLevel.Normal) + if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"[NetworkShow] Trying to make {nameof(NetworkObject)} {gameObject.name} visible to client ({clientId}) but {nameof(CheckObjectVisibility)} returned false!"); } return; } - NetworkManager.SpawnManager.MarkObjectForShowingTo(this, clientId); + NetworkManagerOwner.SpawnManager.MarkObjectForShowingTo(this, clientId); Observers.Add(clientId); } @@ -1496,8 +1514,13 @@ public static void NetworkShow(List networkObjects, ulong clientI // Do the safety loop first to prevent putting the netcode in an invalid state. for (int i = 0; i < networkObjects.Count; i++) { + if (!networkObjects[i].IsSpawned) + { + throw new SpawnStateException("Object is not spawned"); + } + var networkObject = networkObjects[i]; - var networkManager = networkObject.NetworkManager; + var networkManager = networkObject.NetworkManagerOwner; if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) { @@ -1513,7 +1536,7 @@ public static void NetworkShow(List networkObjects, ulong clientI // that the local instance does not own if (!networkObjects[i].HasAuthority) { - if (networkObjects[i].NetworkManager.DistributedAuthorityMode) + if (networkObjects[i].NetworkManagerOwner.DistributedAuthorityMode) { // It will log locally and to the "master-host". NetworkLog.LogErrorServer("Only the owner-authority can change visibility when distributed authority mode is enabled!"); @@ -1525,19 +1548,15 @@ public static void NetworkShow(List networkObjects, ulong clientI } } - if (!networkObjects[i].IsSpawned) - { - throw new SpawnStateException("Object is not spawned"); - } if (networkObjects[i].Observers.Contains(clientId)) { throw new VisibilityChangeException($"{nameof(NetworkObject)} with NetworkId: {networkObjects[i].NetworkObjectId} is already visible"); } - if (networkObjects[i].NetworkManager != networkManager) + if (networkObjects[i].NetworkManagerOwner != networkManager) { - throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManager)); + throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManagerOwner)); } } @@ -1568,9 +1587,9 @@ public void NetworkHide(ulong clientId) throw new SpawnStateException("Object is not spawned"); } - if (!HasAuthority && !NetworkManager.DAHost) + if (!HasAuthority && !NetworkManagerOwner.DAHost) { - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { throw new NotServerException($"Only the owner-authority can change visibility when distributed authority mode is enabled!"); } @@ -1580,11 +1599,11 @@ public void NetworkHide(ulong clientId) } } - if (!NetworkManager.SpawnManager.RemoveObjectFromShowingTo(this, clientId)) + if (!NetworkManagerOwner.SpawnManager.RemoveObjectFromShowingTo(this, clientId)) { if (!Observers.Contains(clientId)) { - if (NetworkManager.LogLevel <= LogLevel.Developer) + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { Debug.LogWarning($"{name} is already hidden from Client-{clientId}! (ignoring)"); return; @@ -1596,41 +1615,41 @@ public void NetworkHide(ulong clientId) { NetworkObjectId = NetworkObjectId, DestroyGameObject = !IsSceneObject.Value, - IsDistributedAuthority = NetworkManager.DistributedAuthorityMode, - IsTargetedDestroy = NetworkManager.DistributedAuthorityMode, + IsDistributedAuthority = NetworkManagerOwner.DistributedAuthorityMode, + IsTargetedDestroy = NetworkManagerOwner.DistributedAuthorityMode, TargetClientId = clientId, // Just always populate this value whether we write it or not DeferredDespawnTick = DeferredDespawnTick, }; var size = 0; - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { - if (!NetworkManager.DAHost) + if (!NetworkManagerOwner.DAHost) { // Send destroy call to service or DAHost - size = NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); + size = NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); } else // DAHost mocking service { // Send destroy call - size = NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); + size = NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); // Broadcast the destroy to all clients so they can update their observers list - foreach (var client in NetworkManager.ConnectionManager.ConnectedClientIds) + foreach (var client in NetworkManagerOwner.ConnectionManager.ConnectedClientIds) { - if (client == clientId || client == NetworkManager.LocalClientId) + if (client == clientId || client == NetworkManagerOwner.LocalClientId) { continue; } - size += NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, client); + size += NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, client); } } } else { // Send destroy call - size = NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); + size = NetworkManagerOwner.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); } - NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size); + NetworkManagerOwner.NetworkMetrics.TrackObjectDestroySent(clientId, this, size); } } @@ -1661,7 +1680,7 @@ public static void NetworkHide(List networkObjects, ulong clientI for (int i = 0; i < networkObjects.Count; i++) { var networkObject = networkObjects[i]; - var networkManager = networkObject.NetworkManager; + var networkManager = networkObject.NetworkManagerOwner; if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) { @@ -1677,7 +1696,7 @@ public static void NetworkHide(List networkObjects, ulong clientI // that the local instance does not own if (!networkObjects[i].HasAuthority) { - if (networkObjects[i].NetworkManager.DistributedAuthorityMode) + if (networkObjects[i].NetworkManagerOwner.DistributedAuthorityMode) { // It will log locally and to the "master-host". NetworkLog.LogErrorServer($"Only the owner-authority can change hide a {nameof(NetworkObject)} when distributed authority mode is enabled!"); @@ -1700,9 +1719,9 @@ public static void NetworkHide(List networkObjects, ulong clientI throw new VisibilityChangeException($"{nameof(NetworkObject)} with {nameof(NetworkObjectId)}: {networkObjects[i].NetworkObjectId} is already hidden"); } - if (networkObjects[i].NetworkManager != networkManager) + if (networkObjects[i].NetworkManagerOwner != networkManager) { - throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManager)); + throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManagerOwner)); } } @@ -1714,6 +1733,7 @@ public static void NetworkHide(List networkObjects, ulong clientI private void OnDestroy() { + // TODO Can we destroy an object before it is spawned? // If no NetworkManager is assigned, then just exit early if (!NetworkManager) { From 690cabb89d6115c78ac3d564fea538359948e927 Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Thu, 8 Jan 2026 17:25:16 +0100 Subject: [PATCH 17/18] Cached NetworkManager instead of lazy instantiation --- .../Runtime/Components/NetworkAnimator.cs | 3 +- .../Components/NetworkRigidBodyBase.cs | 3 ++ .../Runtime/Components/NetworkTransform.cs | 41 +++++++++++-------- .../Runtime/Core/NetworkBehaviour.cs | 6 ++- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 2cde6a1be6..3de004b053 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -194,6 +194,7 @@ internal void DeregisterUpdate() internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator) { m_NetworkAnimator = networkAnimator; + // TODO can we use m_LocalNetworkManager here or other cached var? m_IsServer = networkAnimator.NetworkManager.IsServer; NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate); } @@ -1505,7 +1506,7 @@ internal void UpdateAnimationState(AnimationState animationState) } } // For reference, it is valid to have no transition information - //else if (NetworkManager.LogLevel == LogLevel.Developer) + //else if (m_LocalNetworkManager.LogLevel == LogLevel.Developer) //{ // NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!"); //} diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index 9fbf8a26c9..d560b14952 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -971,6 +971,7 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) /// internal void UpdateOwnershipAuthority() { + // TODO do we have a cached NetworkManager here? Should we create one? if (NetworkManager.DistributedAuthorityMode) { // When in distributed authority mode, always use HasAuthority @@ -980,6 +981,7 @@ internal void UpdateOwnershipAuthority() { if (NetworkTransform.IsServerAuthoritative()) { + // TODO do we have a cached NetworkManager here? Should we create one m_IsAuthority = NetworkManager.IsServer; } else @@ -997,6 +999,7 @@ internal void UpdateOwnershipAuthority() /// public override void OnNetworkSpawn() { + // TODO do we have a cached NetworkManager here? Should we create one m_TickFrequency = 1.0f / NetworkManager.NetworkConfig.TickRate; m_TickRate = NetworkManager.NetworkConfig.TickRate; UpdateOwnershipAuthority(); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 834923a33c..e7cc2befe2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1619,7 +1619,7 @@ internal bool SynchronizeScale public bool CanCommitToTransform { get; protected set; } /// - /// Internally used by to keep track of the instance assigned to this + /// Internally used by to keep track of the instance assigned to /// this derived class instance. /// protected NetworkManager m_CachedNetworkManager; @@ -1850,6 +1850,7 @@ private bool ShouldSynchronizeHalfFloat(ulong targetClientId) if (!IsServerAuthoritative() && NetworkObject.OwnerClientId == targetClientId) { // In distributed authority mode we want to synchronize the half float if we are the owner. + // TODO do we have a cached NetworkManager here? Should we create one? return (!NetworkManager.DistributedAuthorityMode && NetworkObject.IsOwnedByServer) || (NetworkManager.DistributedAuthorityMode); } return true; @@ -2092,12 +2093,14 @@ internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkStat return CheckForStateChange(ref networkState); } + //private int m_CachedTickRateValue; /// /// Applies the transform to the specified. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool CheckForStateChange(ref NetworkTransformState networkState, bool isSynchronization = false, ulong targetClientId = 0, bool forceState = false) { + var cachedTickRateValue = (int)m_CachedNetworkManager.NetworkConfig.TickRate; var flagStates = networkState.FlagStates; // As long as we are not doing our first synchronization and we are sending unreliable deltas, each @@ -2112,9 +2115,8 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is // We compare against the NetworkTickSystem version since ServerTime is set when updating ticks if (UseUnreliableDeltas && !isSynchronization && m_DeltaSynch && m_NextTickSync <= CurrentTick) { - // TODO-CACHE: m_CachedNetworkManager.NetworkConfig.TickRate value // Increment to the next frame synch tick position for this instance - m_NextTickSync += (int)m_CachedNetworkManager.NetworkConfig.TickRate; + m_NextTickSync += cachedTickRateValue; // If we are teleporting, we do not need to send a frame synch for this tick slot // as a "frame synch" really is effectively just a teleport. isAxisSync = !flagStates.IsTeleportingNextFrame; @@ -3297,7 +3299,7 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf } // Get the time when this new state was sent - newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkTickSystem.TickRate, newState.NetworkTick).Time; + newState.SentTime = new NetworkTime(m_CachedNetworkTickRate, newState.NetworkTick).Time; if (LogStateUpdate) { @@ -3526,7 +3528,7 @@ protected internal override void InternalOnNetworkPostSpawn() // Then we want to: // - Force the "IsSynchronizing" flag so the NetworkTransform has its state updated properly and runs through the initialization again. // - Make sure the SynchronizingState is updated to the instantiated prefab's default flags/settings. - if (NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode && !IsOwner && !OnIsServerAuthoritative() && !SynchronizeState.IsSynchronizing) + if (m_CachedNetworkManager.IsServer && !m_CachedNetworkManager.DistributedAuthorityMode && !IsOwner && !OnIsServerAuthoritative() && !SynchronizeState.IsSynchronizing) { // Handle the first/root NetworkTransform slightly differently to have a sequenced synchronization of like authority nested NetworkTransform components if (m_IsFirstNetworkTransform) @@ -3569,6 +3571,7 @@ protected internal override void InternalOnNetworkPostSpawn() internal static InterpolationTypes DefaultInterpolationType; internal Transform CachedTransform; + private uint m_CachedNetworkTickRate; /// /// Create interpolators when first instantiated to avoid memory allocations if the @@ -3599,6 +3602,7 @@ protected virtual void Awake() internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManager) { m_CachedNetworkManager = networkManager; + m_CachedNetworkTickRate = m_CachedNetworkManager.NetworkTickSystem.TickRate; CachedTransform = transform; base.InternalOnNetworkPreSpawn(ref networkManager); } @@ -3607,7 +3611,6 @@ internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManag public override void OnNetworkSpawn() { m_ParentedChildren.Clear(); - m_CachedNetworkManager = NetworkManager; Initialize(); @@ -3736,9 +3739,9 @@ private void InternalInitialization(bool isOwnershipChange = false) var currentPosition = GetSpaceRelativePosition(); var currentRotation = GetSpaceRelativeRotation(); - if (NetworkManager.DistributedAuthorityMode) + if (m_CachedNetworkManager.DistributedAuthorityMode) { - RegisterNetworkManagerForTickUpdate(NetworkManager); + RegisterNetworkManagerForTickUpdate(m_CachedNetworkManager); } #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D @@ -4577,7 +4580,7 @@ internal void TransformStateUpdate() { // TODO: Investigate where this state should be applied or just discarded. // For now, discard the state if we assumed ownership. - // Debug.Log($"[Client-{NetworkManager.LocalClientId}] Ignoring inbound update from Client-{0} and parentUpdated:{isParentingDirective}!"); + // Debug.Log($"[Client-{m_CachedNetworkManager.LocalClientId}] Ignoring inbound update from Client-{0} and parentUpdated:{isParentingDirective}!"); return; } // Store the previous/old state @@ -4690,6 +4693,7 @@ internal static float GetTickLatency(NetworkManager networkManager) /// Only valid on clients. /// /// Returns the tick latency and local offset in seconds and as a float value. + /// Can this be called before spawn? public static float GetTickLatency() { return GetTickLatency(NetworkManager.Singleton); @@ -4802,14 +4806,14 @@ private static void RegisterNetworkManagerForTickUpdate(NetworkManager networkMa /// private static void RegisterForTickUpdate(NetworkTransform networkTransform) { - - if (!networkTransform.NetworkManager.DistributedAuthorityMode && !s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) + var networkManager = networkTransform.NetworkManager; + if (!networkManager.DistributedAuthorityMode && !s_NetworkTickRegistration.ContainsKey(networkManager)) { - s_NetworkTickRegistration.Add(networkTransform.NetworkManager, new NetworkTransformTickRegistration(networkTransform.NetworkManager)); + s_NetworkTickRegistration.Add(networkManager, new NetworkTransformTickRegistration(networkManager)); } networkTransform.RegisterForTickSynchronization(); - s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Add(networkTransform); + s_NetworkTickRegistration[networkManager].NetworkTransforms.Add(networkTransform); } /// @@ -4819,16 +4823,17 @@ private static void RegisterForTickUpdate(NetworkTransform networkTransform) /// private static void DeregisterForTickUpdate(NetworkTransform networkTransform) { - if (networkTransform.NetworkManager == null) + var networkManager = networkTransform.NetworkManager; + if (!networkManager) { return; } - if (s_NetworkTickRegistration.ContainsKey(networkTransform.NetworkManager)) + if (s_NetworkTickRegistration.ContainsKey(networkManager)) { - s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Remove(networkTransform); - if (!networkTransform.NetworkManager.DistributedAuthorityMode && s_NetworkTickRegistration[networkTransform.NetworkManager].NetworkTransforms.Count == 0) + s_NetworkTickRegistration[networkManager].NetworkTransforms.Remove(networkTransform); + if (!networkManager.DistributedAuthorityMode && s_NetworkTickRegistration[networkManager].NetworkTransforms.Count == 0) { - var registrationEntry = s_NetworkTickRegistration[networkTransform.NetworkManager]; + var registrationEntry = s_NetworkTickRegistration[networkManager]; registrationEntry.Remove(); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 4368c1e972..aba00cddd4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -88,6 +88,7 @@ internal FastBufferWriter __beginSendServerRpc(uint rpcMethodId, ServerRpcParams internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery) #pragma warning restore IDE1006 // restore naming rule violation check { + // Getting this ahead of time actually improves performance var networkManager = m_NetworkManager; var serverRpcMessage = new ServerRpcMessage { @@ -167,6 +168,7 @@ internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery) #pragma warning restore IDE1006 // restore naming rule violation check { + // Getting this ahead of time actually improves performance var networkManager = m_NetworkManager; var clientRpcMessage = new ClientRpcMessage { @@ -215,7 +217,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth NetworkLog.LogError(GenerateObserverErrorMessage(clientRpcParams, targetClientId)); } } - rpcWriteSize = m_NetworkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds); + rpcWriteSize = networkManager.ConnectionManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds); } else if (clientRpcParams.Send.TargetClientIdsNativeArray != null) { @@ -349,6 +351,7 @@ internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams, internal void __endSendRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, RpcParams rpcParams, RpcAttribute.RpcAttributeParams attributeParams, SendTo defaultTarget, RpcDelivery rpcDelivery) #pragma warning restore IDE1006 // restore naming rule violation check { + // Sould we create a local networkManager var to improve performance instead of using 2 times m_NetworkManager? var rpcMessage = new RpcMessage { Metadata = new RpcMetadata @@ -650,6 +653,7 @@ protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId) /// internal void UpdateNetworkProperties() { + // Getting these ahead of time actually improves performance var networkObject = m_NetworkObject; var networkManager = m_NetworkManager; From 815d310a79567761c531b2866dcf61cea8afb3ca Mon Sep 17 00:00:00 2001 From: Noellie Velez Date: Fri, 9 Jan 2026 22:48:53 +0100 Subject: [PATCH 18/18] Comments --- .../Runtime/Core/NetworkObject.cs | 8 +++++++- .../Runtime/Spawning/NetworkSpawnManager.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 7f89ca3cdb..98f506662e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -52,7 +52,13 @@ public sealed class NetworkObject : MonoBehaviour /// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0 /// [HideInInspector] - public uint PrefabIdHash => GlobalObjectIdHash; + public uint PrefabIdHash + { + get + { + return GlobalObjectIdHash; + } + } /// /// InstantiationData sent during the instantiation process. diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 14ea2bc41d..e91e119c17 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -521,6 +521,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool throw new NotServerException("Only the server can change ownership"); } + //Should this go at the beginning of the function? if (!networkObject.IsSpawned) { throw new SpawnStateException("Object is not spawned");