From c319570f4905fa19ca3e9e08052bc6beeeda0beb Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 15 Sep 2026 18:17:34 -0400 Subject: [PATCH 1/2] fix: Harden error handling --- .../Components/Helpers/ComponentController.cs | 12 +- .../Components/Helpers/UnifiedBootstrap.cs | 11 +- .../Helpers/UnifiedUpdateConnections.cs | 41 ++- .../Connection/NetworkConnectionManager.cs | 54 +++- .../Runtime/Core/NetworkBehaviour.cs | 48 +++- .../Runtime/Core/NetworkManager.cs | 90 ++++-- .../Runtime/Core/NetworkObject.cs | 95 ++++++- .../Messages/ConnectionApprovedMessage.cs | 2 +- .../AnticipatedNetworkVariable.cs | 9 +- .../Collections/NetworkList.cs | 10 +- .../NetworkVariable/NetworkVariable.cs | 27 +- .../NetworkVariable/NetworkVariableBase.cs | 9 +- .../DefaultSceneManagerHandler.cs | 36 ++- .../SceneManagement/NetworkSceneManager.cs | 266 ++++++++++++------ .../Runtime/Spawning/NetworkPrefabHandler.cs | 18 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 25 +- .../Runtime/Timing/AnticipationSystem.cs | 19 +- .../Runtime/Transports/NetworkTransport.cs | 9 +- 18 files changed, 607 insertions(+), 174 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs index d3d6e678b9..710c7be745 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/ComponentController.cs @@ -396,17 +396,21 @@ protected override void OnNetworkPostSpawn() } /// - /// - /// If overriding this method, it is required that you invoke this base method. - /// + // TODO: Not used anymore public override void OnDestroy() + { + base.OnDestroy(); + } + + + internal override void InternalOnDestroy() { if (m_CoroutineObject.IsRunning) { StopCoroutine(m_CoroutineObject.Coroutine); m_CoroutineObject.IsRunning = false; } - base.OnDestroy(); + base.InternalOnDestroy(); } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs index 3a41f7b3a9..e8653687b9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs @@ -75,7 +75,16 @@ public override bool Initialize(string defaultWorldName) LastCreatedWorld = CreateLocalWorld("LocalWorld"); } - OnInitialized?.Invoke(); + // Always wrap events that can invoke user script in a try-catch to assure any + // proceeding script is still executed. + try + { + OnInitialized?.Invoke(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs index db3988b0fd..76bfc095cd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs @@ -54,7 +54,14 @@ protected override void OnUpdate() foreach (var con in m_TempConnections) { - NetworkManager.OnNetCodeDisconnect?.Invoke(con); + try + { + NetworkManager.OnNetCodeDisconnect?.Invoke(con); + } + catch (System.Exception ex) + { + Debug.LogException(ex); + } } m_TempConnections.Clear(); @@ -83,7 +90,15 @@ protected override void OnUpdate() // Set the connection in-game commandBuffer.AddComponent(entry.Value.Entity); commandBuffer.AddComponent(entry.Value.Entity, default(ConnectionState)); - NetworkManager.OnNetCodeConnect?.Invoke(entry.Value); + + try + { + NetworkManager.OnNetCodeConnect?.Invoke(entry.Value); + } + catch (System.Exception ex) + { + Debug.LogException(ex); + } m_TempConnections.Add(entry.Value); } } @@ -104,8 +119,16 @@ protected override void OnUpdate() foreach (var (networkId, entity) in SystemAPI.Query().WithEntityAccess()) { commandBuffer.RemoveComponent(entity); - NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection - { World = World, Entity = entity, NetworkId = networkId.Value }); + + try + { + NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection + { World = World, Entity = entity, NetworkId = networkId.Value }); + } + catch (System.Exception ex) + { + Debug.LogException(ex); + } } } } @@ -121,7 +144,15 @@ protected override void OnDestroy() foreach (var (networkId, entity) in SystemAPI.Query().WithEntityAccess()) { commandBuffer.RemoveComponent(entity); - NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }); + + try + { + NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value }); + } + catch (System.Exception ex) + { + Debug.LogException(ex); + } } commandBuffer.Playback(EntityManager); base.OnDestroy(); diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index f614843cef..4c20edd258 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -711,7 +711,15 @@ internal void TransportFailureEventHandler(bool duringStart = false) var clientSeverOrHost = LocalClient.IsServer ? LocalClient.IsHost ? "Host" : "Server" : "Client"; var whenFailed = duringStart ? "start failure" : "failure"; NetworkLog.LogError($"{clientSeverOrHost} is shutting down due to network transport {whenFailed} of {NetworkManager.NetworkConfig.NetworkTransport.GetType().Name}!"); - OnTransportFailure?.Invoke(); + + try + { + OnTransportFailure?.Invoke(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } // If we had a transport failure when trying to start, reset the local client roles and directly invoke the internal shutdown. if (duringStart) @@ -854,12 +862,25 @@ internal void ApproveConnection(ref ConnectionRequestMessage connectionRequestMe // Note: ToArray() also allocates. :( var response = new NetworkManager.ConnectionApprovalResponse(); ClientsToApprove[context.SenderId] = response; - ConnectionApprovalCallback?.Invoke( - new NetworkManager.ConnectionApprovalRequest - { - Payload = connectionRequestMessage.ConnectionData, - ClientNetworkId = context.SenderId - }, response); + try + { + ConnectionApprovalCallback?.Invoke( + new NetworkManager.ConnectionApprovalRequest + { + Payload = connectionRequestMessage.ConnectionData, + ClientNetworkId = context.SenderId + }, response); + } + catch (Exception ex) + { + // A throwing approval handler would otherwise leave a Pending response stranded in + // ClientsToApprove, hanging the connecting client until it times out. Deny instead. + Debug.LogException(ex); + response.Approved = false; + response.Pending = false; + response.CreatePlayerObject = false; + response.Reason = "Connection approval handler threw an exception."; + } } /// @@ -1748,13 +1769,22 @@ internal void Shutdown() { //The Transport is set during initialization, thus it is possible for the Transport to be null var transport = NetworkManager.NetworkConfig?.NetworkTransport; - if (transport != null) + if (transport == null) + { + return; + } + // if the transport throws we need to ensure we finish the shutdown sequence. + try { transport.Shutdown(); - if (NetworkManager.LogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfo($"{nameof(NetworkConnectionManager)}.{nameof(Shutdown)}() -> {nameof(IsListening)} && {nameof(NetworkManager.NetworkConfig.NetworkTransport)} != null -> {nameof(NetworkTransport)}.{nameof(NetworkTransport.Shutdown)}()"); - } + } + catch (Exception ex) + { + Debug.LogException(ex); + } + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogInfo($"{nameof(NetworkConnectionManager)}.{nameof(Shutdown)}() -> {nameof(IsListening)} && {nameof(NetworkManager.NetworkConfig.NetworkTransport)} != null -> {nameof(NetworkTransport)}.{nameof(NetworkTransport.Shutdown)}()"); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 5d1a04537b..8d79f0f6e4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -137,7 +137,14 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth MessageSize = 0 }; serverRpcMessage.ReadBuffer = tempBuffer; - serverRpcMessage.Handle(ref context); + try + { + serverRpcMessage.Handle(ref context); + } + catch (Exception e) + { + Debug.LogException(e); + } rpcWriteSize = tempBuffer.Length; } else @@ -267,7 +274,14 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth MessageSize = 0 }; clientRpcMessage.ReadBuffer = tempBuffer; - clientRpcMessage.Handle(ref context); + try + { + clientRpcMessage.Handle(ref context); + } + catch (Exception e) + { + Debug.LogException(e); + } } bufferWriter.Dispose(); @@ -640,8 +654,16 @@ protected internal virtual void OnIsDestroying() /// internal void SetIsDestroying() { - // We intentionally invoke this before setting the IsDestroying flag. - OnIsDestroying(); + try + { + // We intentionally invoke this before setting the IsDestroying flag. + OnIsDestroying(); + } + catch (Exception e) + { + Debug.LogException(e); + } + // Set outside of the try-catch: a throwing override must not leave this flag false. IsDestroying = true; } @@ -931,7 +953,14 @@ internal void InternalOnGainedOwnership() { UpdateNetworkVariableOnOwnershipChanged(); } - OnGainedOwnership(); + try + { + OnGainedOwnership(); + } + catch (Exception e) + { + Debug.LogException(e); + } } /// @@ -948,7 +977,14 @@ protected virtual void OnOwnershipChanged(ulong previous, ulong current) internal void InternalOnOwnershipChanged(ulong previous, ulong current) { - OnOwnershipChanged(previous, current); + try + { + OnOwnershipChanged(previous, current); + } + catch (Exception e) + { + Debug.LogException(e); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 185d682f66..daa3292da9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -252,7 +252,14 @@ internal void SetSessionOwner(ulong sessionOwner) networkObject.InvokeSessionOwnerPromoted(isSessionOwner); } - OnSessionOwnerPromoted?.Invoke(sessionOwner); + try + { + OnSessionOwnerPromoted?.Invoke(sessionOwner); + } + catch (Exception ex) + { + Log.Exception(ex); + } } #if ENABLE_SESSIONOWNER_PROMOTION_NOTIFICATION @@ -1103,7 +1110,14 @@ private void Awake() EditorApplication.playModeStateChanged += ModeChanged; #endif // Notify we have instantiated a new instance of NetworkManager. - OnInstantiated?.Invoke(this); + try + { + OnInstantiated?.Invoke(this); + } + catch (Exception ex) + { + Log.Exception(ex); + } } private void OnEnable() @@ -1479,8 +1493,15 @@ internal bool InternalStartServer() // Notify the server that everything should be synchronized/spawned at this time. SpawnManager.NotifyNetworkObjectsSynchronized(); - OnServerStarted?.Invoke(); - OnStarted?.Invoke(); + try + { + OnServerStarted?.Invoke(); + OnStarted?.Invoke(); + } + catch (Exception ex) + { + Log.Exception(ex); + } ConnectionManager.LocalClient.IsApproved = true; return true; } @@ -1555,8 +1576,15 @@ internal bool InternalStartClient() } else { - OnClientStarted?.Invoke(); - OnStarted?.Invoke(); + try + { + OnClientStarted?.Invoke(); + OnStarted?.Invoke(); + } + catch (Exception ex) + { + Log.Exception(ex); + } } } catch (Exception ex) @@ -1677,9 +1705,16 @@ private void HostServerInitialize() // Notify the host that everything should be synchronized/spawned at this time. SpawnManager.NotifyNetworkObjectsSynchronized(); - OnServerStarted?.Invoke(); - OnClientStarted?.Invoke(); - OnStarted?.Invoke(); + try + { + OnServerStarted?.Invoke(); + OnClientStarted?.Invoke(); + OnStarted?.Invoke(); + } + catch (Exception ex) + { + Log.Exception(ex); + } // This assures that any in-scene placed NetworkObject is spawned and // any associated NetworkBehaviours' netcode related properties are @@ -1874,21 +1909,28 @@ internal void ShutdownInternal() NetworkTimeSystem?.Shutdown(); NetworkTickSystem = null; - if (localClient.IsClient) + + try { - // If we were a client, we want to know if we were a host - // client or not. (why we pass in "IsServer") - OnClientStopped?.Invoke(localClient.IsServer); - } + if (localClient.IsClient) + { + // If we were a client, we want to know if we were a host + // client or not. (why we pass in "IsServer") + OnClientStopped?.Invoke(localClient.IsServer); + } + if (localClient.IsServer) + { + // If we were a server, we want to know if we were a host + // or not. (why we pass in "IsClient") + OnServerStopped?.Invoke(localClient.IsClient); + } - if (localClient.IsServer) + OnStopped?.Invoke(); + } + catch (Exception ex) { - // If we were a server, we want to know if we were a host - // or not. (why we pass in "IsClient") - OnServerStopped?.Invoke(localClient.IsClient); + Log.Exception(ex); } - - OnStopped?.Invoke(); } // Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when quitting the application. @@ -1914,9 +1956,17 @@ private void OnApplicationQuit() #endif } + private bool m_IsDestroyed = false; + // Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit private void OnDestroy() { + if (m_IsDestroyed) + { + return; + } + m_IsDestroyed = true; + try { ShutdownInternal(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 0b39c84f6f..49a7088e56 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -523,7 +523,14 @@ public void DeferDespawn(int tickOffset, bool destroy = true) // Notify all NetworkBehaviours that the authority is performing a deferred despawn. // This is when user script would update NetworkVariable states that might be needed // for the deferred despawn sequence on non-authoritative instances. - behaviour.OnDeferringDespawn(DeferredDespawnTick); + try + { + behaviour.OnDeferringDespawn(DeferredDespawnTick); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } // DAHost handles sending updates to all clients @@ -810,6 +817,19 @@ public enum OwnershipPermissionsFailureStatus /// public OnOwnershipPermissionsFailureDelegateHandler OnOwnershipPermissionsFailure; + + internal void InvokeOwnershipPermissionsFailure() + { + try + { + OnOwnershipPermissionsFailure?.Invoke(OwnershipPermissionsFailureStatus.SessionOwnerOnly); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + } + /// /// Returned by to signify w /// : The request for ownership was sent (does not mean it will be granted, but the request was sent). @@ -978,8 +998,16 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) // Finally, check to see if OnOwnershipRequested is registered and if user script is allowing // this transfer of ownership - if (OnOwnershipRequested != null && !OnOwnershipRequested.Invoke(clientRequestingOwnership)) + try { + if (OnOwnershipRequested != null && !OnOwnershipRequested.Invoke(clientRequestingOwnership)) + { + response = OwnershipRequestResponseStatus.Denied; + } + } + catch (Exception ex) + { + Debug.LogException(ex); response = OwnershipRequestResponseStatus.Denied; } @@ -1072,7 +1100,14 @@ public enum OwnershipRequestResponseStatus /// internal void OwnershipRequestResponse(OwnershipRequestResponseStatus ownershipRequestResponse) { - OnOwnershipRequestResponse?.Invoke(ownershipRequestResponse); + try + { + OnOwnershipRequestResponse?.Invoke(ownershipRequestResponse); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } /// @@ -1434,6 +1469,27 @@ public void SetSceneObjectStatus(bool isSceneObject = false) /// public VisibilityDelegate CheckObjectVisibility = null; + /// + /// Returns true if the object should be visible to the specified client, false otherwise + /// Defaults to the object being visible. + /// + internal bool InvokeCheckObjectVisibility(ulong clientId) + { + if (CheckObjectVisibility == null) + { + return true; + } + try + { + return CheckObjectVisibility(clientId); + } + catch (Exception ex) + { + Debug.LogException(ex); + return true; + } + } + /// /// Delegate type for checking spawn options /// @@ -1637,7 +1693,7 @@ public void NetworkShow(ulong clientId) return; } - if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId)) + if (!InvokeCheckObjectVisibility(clientId)) { if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { @@ -2295,8 +2351,8 @@ internal void SetupObservers() // then add all connected clients as observers foreach (var clientId in NetworkManagerOwner.ConnectedClientsIds) { - // If CheckObjectVisibility has a callback, then allow that method determine who the observers are. - if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId)) + // If CheckObjectVisibility marks this object as not visible to the client, then skip adding it as an observer + if (!InvokeCheckObjectVisibility(clientId)) { continue; } @@ -2386,7 +2442,14 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo childBehaviour.UpdateNetworkProperties(); if (distributedAuthorityMode || isServer || isPreviousOwner) { - childBehaviour.OnLostOwnership(); + try + { + childBehaviour.OnLostOwnership(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } } @@ -2457,7 +2520,14 @@ internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNe // Invoke internal notification child.InternalOnNetworkObjectParentChanged(parentNetworkObject); // Invoke public notification - child.OnNetworkObjectParentChanged(parentNetworkObject); + try + { + child.OnNetworkObjectParentChanged(parentNetworkObject); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } } @@ -3904,7 +3974,14 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" + $"has no associated server side (network) scene handle!"); } - OnMigratedToNewScene?.Invoke(); + try + { + OnMigratedToNewScene?.Invoke(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } // Only the authority side will notify clients of non-parented NetworkObject scene changes if (m_HasAuthority && notify && !transform.parent) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 62c3391b46..7adcf969db 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -157,7 +157,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) // Serialize NetworkVariable data foreach (var sobj in SpawnedObjectsList) { - if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))) + if (sobj.SpawnWithObservers && sobj.InvokeCheckObjectVisibility(OwnerClientId)) { sobj.AddObserver(OwnerClientId); // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs index 1cfab08e12..2ebb33553b 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/AnticipatedNetworkVariable.cs @@ -372,7 +372,14 @@ private void OnValueChangedInternal(T previousValue, T newValue) m_SmoothDuration = 0; m_CurrentSmoothTime = 0; - OnAuthoritativeValueChanged?.Invoke(this, previousValue, newValue); + try + { + OnAuthoritativeValueChanged?.Invoke(this, previousValue, newValue); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 5bcd5cead3..a241e8463b 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -681,7 +681,15 @@ private void HandleAddListEvent(NetworkListEvent listEvent) { m_DirtyEvents.Add(listEvent); MarkNetworkObjectDirty(); - OnListChanged?.Invoke(listEvent); + + try + { + OnListChanged?.Invoke(listEvent); + } + catch (Exception ex) + { + UnityEngine.Debug.LogException(ex); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs index e8604a66aa..688efa454a 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariable.cs @@ -162,7 +162,14 @@ public virtual T Value NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_LastInternalValue); SetDirty(true); m_IsDisposed = false; - OnValueChanged?.Invoke(previousValue, m_InternalValue); + try + { + OnValueChanged?.Invoke(previousValue, m_InternalValue); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } } } @@ -196,7 +203,14 @@ public bool CheckDirtyState(bool forceCheck = false) if ((!isDirty || forceCheck) && !NetworkVariableSerialization.AreEqual(ref m_LastInternalValue, ref m_InternalValue)) { SetDirty(true); - OnValueChanged?.Invoke(m_LastInternalValue, m_InternalValue); + try + { + OnValueChanged?.Invoke(m_LastInternalValue, m_InternalValue); + } + catch (Exception ex) + { + Debug.LogException(ex); + } m_IsDisposed = false; isDirty = true; NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_LastInternalValue); @@ -345,7 +359,14 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) SetDirty(true); } - OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue); + try + { + OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs index dd52c55782..ba44e8f0e4 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -125,7 +125,14 @@ public void Initialize(NetworkBehaviour networkBehaviour) // When in distributed authority mode, there is no such thing as server write permissions InternalWritePerm = m_NetworkManager.DistributedAuthorityMode ? NetworkVariableWritePermission.Owner : InternalWritePerm; - OnInitialize(); + try + { + OnInitialize(); + } + catch (Exception ex) + { + Debug.LogException(ex); + } // Some unit tests don't operate with a running NetworkManager. // Only update the last time if there is a NetworkTimeSystem. diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index f7ce87d521..972c3a8ccd 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -215,13 +215,25 @@ public void UnloadUnassignedScenes(NetworkManager networkManager) var scenHandleEntries = SceneNameToSceneHandles[sceneEntry.Key]; foreach (var sceneHandleEntry in scenHandleEntries) { - if (!sceneHandleEntry.Value.IsAssigned) + if (sceneHandleEntry.Value.IsAssigned) + { + continue; + } + + try { - if (sceneManager.VerifySceneBeforeUnloading == null || sceneManager.VerifySceneBeforeUnloading.Invoke(sceneHandleEntry.Value.Scene)) + // Don't unload the scene if the user-configured handler says to keep it loaded + if (sceneManager.VerifySceneBeforeUnloading != null && !sceneManager.VerifySceneBeforeUnloading.Invoke(sceneHandleEntry.Value.Scene)) { - m_ScenesToUnload.Add(sceneHandleEntry.Value.Scene); + continue; } } + catch (Exception ex) + { + Debug.LogException(ex); + } + + m_ScenesToUnload.Add(sceneHandleEntry.Value.Scene); } } foreach (var sceneToUnload in m_ScenesToUnload) @@ -379,18 +391,22 @@ public void SetClientSynchronizationMode(ref NetworkManager networkManager, Load // If using scene verification if (sceneManager.VerifySceneBeforeLoading != null) { - // Determine if we should take this scene into consideration - if (!sceneManager.VerifySceneBeforeLoading.Invoke(scene.buildIndex, scene.name, LoadSceneMode.Additive)) + try { - continue; + // Determine if we should take this scene into consideration + if (!sceneManager.VerifySceneBeforeLoading.Invoke(scene.buildIndex, scene.name, LoadSceneMode.Additive)) + { + continue; + } + } + catch (Exception ex) + { + Debug.LogException(ex); } } // If the scene is not already in the ScenesLoaded list, then add it - if (!sceneManager.ScenesLoaded.ContainsKey(scene.handle)) - { - sceneManager.ScenesLoaded.Add(scene.handle, scene); - } + sceneManager.ScenesLoaded.TryAdd(scene.handle, scene); } } // Set the client synchronization mode diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index cafce0f7c1..f51d7151e0 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -598,11 +598,18 @@ internal bool HasSceneAuthority() return (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer) || (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner); } + private bool m_IsDisposed; + /// /// Handle NetworkSceneManager clean up /// public void Dispose() { + if (m_IsDisposed) + { + return; + } + m_IsDisposed = true; // Always assure we no longer listen to scene changes when disposed. SceneManager.activeSceneChanged -= SceneManager_ActiveSceneChanged; SceneUnloadEventHandler.Shutdown(); @@ -940,7 +947,16 @@ internal bool ValidateSceneBeforeLoading(int sceneIndex, string sceneName, LoadS var validated = true; if (VerifySceneBeforeLoading != null) { - validated = VerifySceneBeforeLoading.Invoke(sceneIndex, sceneName, loadSceneMode); + try + { + validated = VerifySceneBeforeLoading.Invoke(sceneIndex, sceneName, loadSceneMode); + } + catch (Exception ex) + { + Debug.LogException(ex); + // Fallback to the no-handler default + validated = true; + } } if (!validated && !m_DisableValidationWarningMessages) { @@ -1646,16 +1662,23 @@ private void SceneUnloaded(Scene scene) { if (m_NetworkSceneManager != null && m_NetworkSceneManager.NetworkManager != null) { - m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent() + try + { + m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent() + { + AsyncOperation = m_AsyncOperation, + SceneEventType = SceneEventType.UnloadComplete, + SceneName = m_Scene.name, + ScenePath = m_Scene.path, + LoadSceneMode = m_LoadSceneMode, + ClientId = m_ClientId + }); + m_NetworkSceneManager.OnUnloadComplete?.Invoke(m_ClientId, m_Scene.name); + } + catch (Exception ex) { - AsyncOperation = m_AsyncOperation, - SceneEventType = SceneEventType.UnloadComplete, - SceneName = m_Scene.name, - ScenePath = m_Scene.path, - LoadSceneMode = m_LoadSceneMode, - ClientId = m_ClientId - }); - m_NetworkSceneManager.OnUnloadComplete?.Invoke(m_ClientId, m_Scene.name); + Debug.LogException(ex); + } } SceneManager.sceneUnloaded -= SceneUnloaded; SceneUnloadComplete(this); @@ -1671,17 +1694,25 @@ private SceneUnloadEventHandler(NetworkSceneManager networkSceneManager, Scene s m_Scene = scene; SceneManager.sceneUnloaded += SceneUnloaded; // Send the initial unload event notification - m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent() - { - AsyncOperation = m_AsyncOperation, - SceneEventType = SceneEventType.Unload, - SceneName = m_Scene.name, - ScenePath = m_Scene.path, - LoadSceneMode = m_LoadSceneMode, - ClientId = clientId - }); + try + { + m_NetworkSceneManager.OnSceneEvent?.Invoke(new SceneEvent() + { + AsyncOperation = m_AsyncOperation, + SceneEventType = SceneEventType.Unload, + SceneName = m_Scene.name, + ScenePath = m_Scene.path, + LoadSceneMode = m_LoadSceneMode, + ClientId = clientId + }); - m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.NetworkManager.LocalClientId, m_Scene.name, null); + m_NetworkSceneManager.OnUnload?.Invoke(networkSceneManager.NetworkManager.LocalClientId, m_Scene.name, null); + + } + catch (Exception ex) + { + Debug.LogException(ex); + } } } @@ -2092,13 +2123,20 @@ internal void SynchronizeNetworkObjects(ulong clientId, bool synchronizingServic // Notify the local server that the client has been sent the synchronize event if (!synchronizingService) { - OnSceneEvent?.Invoke(new SceneEvent() + try { - SceneEventType = SceneEventType.Synchronize, - ClientId = clientId - }); + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = SceneEventType.Synchronize, + ClientId = clientId + }); + OnSynchronize?.Invoke(clientId); - OnSynchronize?.Invoke(clientId); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } EndSceneEvent(sceneEventData.SceneEventId); @@ -2157,17 +2195,25 @@ private void OnClientBeginSync(uint sceneEventId) sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress); // Notify local client that a scene load has begun - OnSceneEvent?.Invoke(new SceneEvent() + try { - AsyncOperation = sceneLoad, - SceneEventType = SceneEventType.Load, - LoadSceneMode = loadSceneMode, - SceneName = sceneName, - ScenePath = ScenePathFromHash(sceneHash), - ClientId = NetworkManager.LocalClientId, - }); + OnSceneEvent?.Invoke(new SceneEvent() + { + AsyncOperation = sceneLoad, + SceneEventType = SceneEventType.Load, + LoadSceneMode = loadSceneMode, + SceneName = sceneName, + ScenePath = ScenePathFromHash(sceneHash), + ClientId = NetworkManager.LocalClientId, + }); + + OnLoad?.Invoke(NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad); - OnLoad?.Invoke(NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } else { @@ -2323,13 +2369,21 @@ private void HandleClientSceneEvent(uint sceneEventId) { sceneEventData.IsStartingSynchronization = false; - OnSceneEvent?.Invoke(new SceneEvent() + try { - SceneEventType = SceneEventType.Synchronize, - ClientId = NetworkManager.LocalClientId, - }); + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = SceneEventType.Synchronize, + ClientId = NetworkManager.LocalClientId, + }); + + OnSynchronize?.Invoke(NetworkManager.LocalClientId); - OnSynchronize?.Invoke(NetworkManager.LocalClientId); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } if (!sceneEventData.IsDoneWithSynchronization()) @@ -2406,13 +2460,21 @@ private void HandleClientSceneEvent(uint sceneEventId) NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId); // Notify the client that they have finished synchronizing - OnSceneEvent?.Invoke(new SceneEvent() + try { - SceneEventType = sceneEventData.SceneEventType, - ClientId = NetworkManager.LocalClientId, // Client sent this to the server - }); + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = sceneEventData.SceneEventType, + ClientId = NetworkManager.LocalClientId, // Client sent this to the server + }); + + OnSynchronizeComplete?.Invoke(NetworkManager.LocalClientId); - OnSynchronizeComplete?.Invoke(NetworkManager.LocalClientId); + } + catch (Exception ex) + { + Debug.LogException(ex); + } if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) { @@ -2434,11 +2496,18 @@ private void HandleClientSceneEvent(uint sceneEventId) case SceneEventType.ReSynchronize: { // Notify the local client that they have been re-synchronized after being synchronized with an in progress game session - OnSceneEvent?.Invoke(new SceneEvent() + try + { + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = sceneEventData.SceneEventType, + ClientId = NetworkManager.ServerClientId, // Server sent this to client + }); + } + catch (Exception ex) { - SceneEventType = sceneEventData.SceneEventType, - ClientId = NetworkManager.ServerClientId, // Server sent this to client - }); + Debug.LogException(ex); + } EndSceneEvent(sceneEventId); break; @@ -2495,12 +2564,19 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) NetworkManager.ConnectedClients[clientId].IsConnected = true; // Notify that a client has finished synchronizing - OnSceneEvent?.Invoke(new SceneEvent() + try + { + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = sceneEventData.SceneEventType, + ClientId = clientId + }); + OnSynchronizeComplete?.Invoke(clientId); + } + catch (Exception ex) { - SceneEventType = sceneEventData.SceneEventType, - ClientId = clientId - }); - OnSynchronizeComplete?.Invoke(clientId); + Debug.LogException(ex); + } // For non-authority clients in a distributed authority session, we show hidden objects, // we distribute NetworkObjects, and then we end the scene event. @@ -2537,12 +2613,19 @@ private void HandleSessionOwnerEvent(uint sceneEventId, ulong clientId) sceneEventData.SceneEventType = SceneEventType.ReSynchronize; SendSceneEventData(sceneEventId, new ulong[] { clientId }); - OnSceneEvent?.Invoke(new SceneEvent() + try { - SceneEventType = sceneEventData.SceneEventType, - SceneName = string.Empty, - ClientId = clientId - }); + OnSceneEvent?.Invoke(new SceneEvent() + { + SceneEventType = sceneEventData.SceneEventType, + SceneName = string.Empty, + ClientId = clientId + }); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } // DANGO-EXP TODO: Remove this once service distributes objects NetworkManager.SpawnManager.DistributeNetworkObjects(clientId); @@ -3349,39 +3432,46 @@ public List GetSceneMapping(MapTypes mapType) private void InvokeSceneEvents(ulong clientId, SceneEventData eventData, AsyncOperation asyncOperation = null, Scene scene = default) { var sceneName = SceneNameFromHash(eventData.SceneHash); - OnSceneEvent?.Invoke(new SceneEvent() + try { - AsyncOperation = asyncOperation, - SceneEventType = eventData.SceneEventType, - SceneName = sceneName, - ScenePath = ScenePathFromHash(eventData.SceneHash), - ClientId = clientId, - LoadSceneMode = eventData.LoadSceneMode, - ClientsThatCompleted = eventData.ClientsCompleted, - ClientsThatTimedOut = eventData.ClientsTimedOut, - Scene = scene, - }); + OnSceneEvent?.Invoke(new SceneEvent() + { + AsyncOperation = asyncOperation, + SceneEventType = eventData.SceneEventType, + SceneName = sceneName, + ScenePath = ScenePathFromHash(eventData.SceneHash), + ClientId = clientId, + LoadSceneMode = eventData.LoadSceneMode, + ClientsThatCompleted = eventData.ClientsCompleted, + ClientsThatTimedOut = eventData.ClientsTimedOut, + Scene = scene, + }); - switch (eventData.SceneEventType) + switch (eventData.SceneEventType) + { + case SceneEventType.Load: + OnLoad?.Invoke(clientId, sceneName, eventData.LoadSceneMode, asyncOperation); + break; + case SceneEventType.Unload: + OnUnload?.Invoke(clientId, sceneName, asyncOperation); + break; + case SceneEventType.LoadComplete: + OnLoadComplete?.Invoke(clientId, sceneName, eventData.LoadSceneMode); + break; + case SceneEventType.UnloadComplete: + OnUnloadComplete?.Invoke(clientId, sceneName); + break; + case SceneEventType.LoadEventCompleted: + OnLoadEventCompleted?.Invoke(SceneNameFromHash(eventData.SceneHash), eventData.LoadSceneMode, eventData.ClientsCompleted, eventData.ClientsTimedOut); + break; + case SceneEventType.UnloadEventCompleted: + OnUnloadEventCompleted?.Invoke(SceneNameFromHash(eventData.SceneHash), eventData.LoadSceneMode, eventData.ClientsCompleted, eventData.ClientsTimedOut); + break; + } + } + catch (Exception ex) { - case SceneEventType.Load: - OnLoad?.Invoke(clientId, sceneName, eventData.LoadSceneMode, asyncOperation); - break; - case SceneEventType.Unload: - OnUnload?.Invoke(clientId, sceneName, asyncOperation); - break; - case SceneEventType.LoadComplete: - OnLoadComplete?.Invoke(clientId, sceneName, eventData.LoadSceneMode); - break; - case SceneEventType.UnloadComplete: - OnUnloadComplete?.Invoke(clientId, sceneName); - break; - case SceneEventType.LoadEventCompleted: - OnLoadEventCompleted?.Invoke(SceneNameFromHash(eventData.SceneHash), eventData.LoadSceneMode, eventData.ClientsCompleted, eventData.ClientsTimedOut); - break; - case SceneEventType.UnloadEventCompleted: - OnUnloadEventCompleted?.Invoke(SceneNameFromHash(eventData.SceneHash), eventData.LoadSceneMode, eventData.ClientsCompleted, eventData.ClientsTimedOut); - break; + Debug.LogException(ex); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index daf8d153c0..9ca4ee19da 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -341,13 +341,27 @@ internal void HandleNetworkPrefabDestroy(NetworkObject networkObjectInstance) { if (m_PrefabAssetToPrefabHandler.TryGetValue(networkPrefabAssetHash, out var prefabInstanceHandler)) { - prefabInstanceHandler.Destroy(networkObjectInstance); + try + { + prefabInstanceHandler.Destroy(networkObjectInstance); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } } // Otherwise the NetworkObject is the source NetworkPrefab else if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler)) { - prefabInstanceHandler.Destroy(networkObjectInstance); + try + { + prefabInstanceHandler.Destroy(networkObjectInstance); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 28803104f4..e9f138cf84 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -522,7 +522,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Session Owner Only] You cannot change ownership of a {nameof(NetworkObject)} that has the {NetworkObject.OwnershipStatus.SessionOwner} flag set!"); } - networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly); + networkObject.InvokeOwnershipPermissionsFailure(); return; } @@ -535,7 +535,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Locked] You cannot change ownership while a {nameof(NetworkObject)} is locked!"); } - networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.Locked); + networkObject.InvokeOwnershipPermissionsFailure(); return; } if (networkObject.IsRequestInProgress) @@ -544,7 +544,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Request Pending] You cannot change ownership while a {nameof(NetworkObject)} has a pending ownership request!"); } - networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.RequestInProgress); + networkObject.InvokeOwnershipPermissionsFailure(); return; } if (networkObject.IsOwnershipRequestRequired) @@ -553,7 +553,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Request Required] You cannot change ownership directly if a {nameof(NetworkObject)} has the {NetworkObject.OwnershipStatus.RequestRequired} flag set!"); } - networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.RequestRequired); + networkObject.InvokeOwnershipPermissionsFailure(); return; } if (!networkObject.IsOwnershipTransferable) @@ -562,7 +562,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Not transferrable] You cannot change ownership of a {nameof(NetworkObject)} that does not have the {NetworkObject.OwnershipStatus.Transferable} flag set!"); } - networkObject.OnOwnershipPermissionsFailure?.Invoke(NetworkObject.OwnershipPermissionsFailureStatus.NotTransferrable); + networkObject.InvokeOwnershipPermissionsFailure(); return; } } @@ -1915,7 +1915,7 @@ internal void UpdateObservedNetworkObjects(ulong clientId) else { // CheckObject visibility overrides SpawnWithObservers under this condition - if (sobj.CheckObjectVisibility(clientId)) + if (sobj.InvokeCheckObjectVisibility(clientId)) { sobj.AddObserver(clientId); } @@ -2312,8 +2312,19 @@ internal void DeferredDespawnUpdate(NetworkTime serverTime) // Double check to make sure user did not remove the callback if (networkObject.OnDeferredDespawnComplete != null) { + var despawnThisTick = false; + try + { + despawnThisTick = networkObject.OnDeferredDespawnComplete.Invoke(); + } + catch (Exception ex) + { + Debug.LogException(ex); + // If the user callback throws, despawn immediately to avoid throwing every tick. + despawnThisTick = true; + } // If the user callback returns true, then we despawn it this tick - if (networkObject.OnDeferredDespawnComplete.Invoke()) + if (despawnThisTick) { deferredObjectEntry.TickToDespawn = currentTick; } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs index 0e86fd24fc..33bbaa3f12 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/AnticipationSystem.cs @@ -69,13 +69,28 @@ public void ProcessReanticipation() { foreach (var behaviour in item.OwnerObject.ChildNetworkBehaviours.Values) { - behaviour.OnReanticipate(lastRoundTripTime); + try + { + behaviour.OnReanticipate(lastRoundTripTime); + } + catch (System.Exception ex) + { + UnityEngine.Debug.LogException(ex); + } } item.ResetAnticipation(); } ObjectsToReanticipate.Clear(); - OnReanticipate?.Invoke(lastRoundTripTime); + + try + { + OnReanticipate?.Invoke(lastRoundTripTime); + } + catch (System.Exception ex) + { + UnityEngine.Debug.LogException(ex); + } } public void Update() diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs index 13bf8784b3..1b9a484bdc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/NetworkTransport.cs @@ -50,7 +50,14 @@ public abstract class NetworkTransport : MonoBehaviour /// The time the event was received, as reported by Time.realtimeSinceStartup. protected void InvokeOnTransportEvent(NetworkEvent eventType, ulong clientId, ArraySegment payload, float receiveTime) { - OnTransportEvent?.Invoke(eventType, clientId, payload, receiveTime); + try + { + OnTransportEvent?.Invoke(eventType, clientId, payload, receiveTime); + } + catch (Exception ex) + { + Debug.LogException(ex); + } } /// From 7ca8f19fdfd79c915daeebfca0e08899eef63db2 Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 21 Sep 2026 15:58:37 -0400 Subject: [PATCH 2/2] Code review comments --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ .../Documentation~/upgrade-guide.md | 2 +- .../Runtime/Core/NetworkManager.cs | 20 +++++++++---------- .../Runtime/Core/NetworkObject.cs | 4 ++-- .../Runtime/Spawning/NetworkSpawnManager.cs | 10 +++++----- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 7b75f45d30..a82cf612f0 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -18,6 +18,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Ensured all callbacks are wrapped with exception handling to avoid silent errors. (#4161) + ### Security ### Obsolete diff --git a/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md b/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md index c1e0a62f11..b3762eb3b2 100644 --- a/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md +++ b/com.unity.netcode.gameobjects/Documentation~/upgrade-guide.md @@ -26,7 +26,7 @@ To upgrade an existing project from version 2.x to version 3.x, follow these ste 4. The API updater should catch any issues and ask if you want to allow it to make changes to your script(s). 5. If you allow the API updater to make changes for you, then it should auto-update your project's scripts with the correct namespace changes. 6. If you do not allow the API updater to make changes for you, then the editor will enter safe mode. Open the **Console** window to review the remaining compile errors and resolve the errors. (_[Review the Update Editor assembly definition references section below.](#update-editor-assembly-definition-references)_). - + After the API updater finishes and you resolve the compile errors, your project compiles against version 3.x. If the API updater doesn't resolve every reference, refer to [Continue an incomplete API update](#continue-an-incomplete-api-update). _** If, at any point, you decide to downgrade to the editor version you were using prior to updating to 6.7, then make sure to restore or delete the packages-lock.json file (_assures you are not referencing 6.7 specific packages_), restore your backed up version, and delete your Library folder prior to opening your project with the editor version you were using prior to upgrading to 6.7._ diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index daa3292da9..3466aafe26 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1501,6 +1501,9 @@ internal bool InternalStartServer() catch (Exception ex) { Log.Exception(ex); + // Shutdown on exception to assure everything is cleaned up correctly + ShutdownInternal(); + return false; } ConnectionManager.LocalClient.IsApproved = true; return true; @@ -1584,6 +1587,9 @@ internal bool InternalStartClient() catch (Exception ex) { Log.Exception(ex); + // Shutdown on exception to assure everything is cleaned up correctly + ShutdownInternal(); + return false; } } } @@ -1705,16 +1711,10 @@ private void HostServerInitialize() // Notify the host that everything should be synchronized/spawned at this time. SpawnManager.NotifyNetworkObjectsSynchronized(); - try - { - OnServerStarted?.Invoke(); - OnClientStarted?.Invoke(); - OnStarted?.Invoke(); - } - catch (Exception ex) - { - Log.Exception(ex); - } + // No need to try/catch these callbacks because this function is already wrapped. + OnServerStarted?.Invoke(); + OnClientStarted?.Invoke(); + OnStarted?.Invoke(); // This assures that any in-scene placed NetworkObject is spawned and // any associated NetworkBehaviours' netcode related properties are diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 49a7088e56..23de3a73a9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -818,11 +818,11 @@ public enum OwnershipPermissionsFailureStatus public OnOwnershipPermissionsFailureDelegateHandler OnOwnershipPermissionsFailure; - internal void InvokeOwnershipPermissionsFailure() + internal void InvokeOwnershipPermissionsFailure(OwnershipPermissionsFailureStatus failureStatus) { try { - OnOwnershipPermissionsFailure?.Invoke(OwnershipPermissionsFailureStatus.SessionOwnerOnly); + OnOwnershipPermissionsFailure?.Invoke(failureStatus); } catch (Exception ex) { diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index e9f138cf84..13c7bc6ffd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -522,7 +522,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Session Owner Only] You cannot change ownership of a {nameof(NetworkObject)} that has the {NetworkObject.OwnershipStatus.SessionOwner} flag set!"); } - networkObject.InvokeOwnershipPermissionsFailure(); + networkObject.InvokeOwnershipPermissionsFailure(NetworkObject.OwnershipPermissionsFailureStatus.SessionOwnerOnly); return; } @@ -535,7 +535,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Locked] You cannot change ownership while a {nameof(NetworkObject)} is locked!"); } - networkObject.InvokeOwnershipPermissionsFailure(); + networkObject.InvokeOwnershipPermissionsFailure(NetworkObject.OwnershipPermissionsFailureStatus.Locked); return; } if (networkObject.IsRequestInProgress) @@ -544,7 +544,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Request Pending] You cannot change ownership while a {nameof(NetworkObject)} has a pending ownership request!"); } - networkObject.InvokeOwnershipPermissionsFailure(); + networkObject.InvokeOwnershipPermissionsFailure(NetworkObject.OwnershipPermissionsFailureStatus.RequestInProgress); return; } if (networkObject.IsOwnershipRequestRequired) @@ -553,7 +553,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Request Required] You cannot change ownership directly if a {nameof(NetworkObject)} has the {NetworkObject.OwnershipStatus.RequestRequired} flag set!"); } - networkObject.InvokeOwnershipPermissionsFailure(); + networkObject.InvokeOwnershipPermissionsFailure(NetworkObject.OwnershipPermissionsFailureStatus.RequestRequired); return; } if (!networkObject.IsOwnershipTransferable) @@ -562,7 +562,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool { NetworkLog.LogErrorServer($"[{networkObject.name}][Not transferrable] You cannot change ownership of a {nameof(NetworkObject)} that does not have the {NetworkObject.OwnershipStatus.Transferable} flag set!"); } - networkObject.InvokeOwnershipPermissionsFailure(); + networkObject.InvokeOwnershipPermissionsFailure(NetworkObject.OwnershipPermissionsFailureStatus.NotTransferrable); return; } }