diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 046482c8bc..5ce566967d 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,11 +10,14 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added stricter checks on `InSpawned` within `NetworkObject`. (#3831) +- Added a new `InvalidOperation` status to `OwnershipRequestStatus`. (#3831) ### Changed +- Ensure logs in `NetworkObject` log the `NetworkObject.name` wherever possible. (#3831) - Improved performance of NetworkBehaviour ILPostProcessor by omitting unnecessary type and assembly resolutions. (#3827) -- Improve performance of `NetworkObject`. (#3820) +- Improve performance of `NetworkObject`. (#3820, #3831) - If the Unity Transport Disconnect Timeout is set to 0 in the Editor, the timeout will be entirely disabled. (#3810) ### Deprecated diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 7c44ba13cb..15109c93db 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3719,14 +3719,9 @@ private void InternalInitialization(bool isOwnershipChange = false) { if (CanCommitToTransform) { - if (NetworkObject.HasParentNetworkObject(transform)) - { - InLocalSpace = true; - } - else - { - InLocalSpace = false; - } + // If our NetworkObject has a parent NetworkObject, then we are in netcode local space. + // GetComponentsInParent will get the networkObject on cachedNetworkObject as well, so we have a parent if we have more than one returned NetworkObject. + InLocalSpace = m_CachedNetworkObject.transform.GetComponentsInParent().Length > 1; } // Always apply this if SwitchTransformSpaceWhenParented is set. diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 45d4b3311e..d43420f3a7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -348,24 +348,6 @@ private void CheckForInScenePlaced() } #endif // UNITY_EDITOR - internal bool HasParentNetworkObject(Transform transform) - { - if (transform.parent != null) - { - var networkObject = transform.parent.GetComponent(); - if (networkObject != null && networkObject != this) - { - return true; - } - - if (transform.parent.parent != null) - { - return HasParentNetworkObject(transform.parent); - } - } - return false; - } - /// /// Gets the NetworkManager that owns this NetworkObject instance /// @@ -374,7 +356,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 @@ -408,29 +390,41 @@ internal bool HasParentNetworkObject(Transform transform) /// Defaults to true, determines whether the will be destroyed. public void DeferDespawn(int tickOffset, bool destroy = true) { + // Ensure we log the DAMode message first as locking ownership is not allowed if not DA so the DA message is the most relevant. if (!NetworkManager.DistributedAuthorityMode) { - NetworkLog.LogError($"This method is only available in distributed authority mode."); + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}] This method is only available in distributed authority mode."); + } + return; } if (!IsSpawned) { - NetworkLog.LogError($"Cannot defer despawning {name} because it is not spawned!"); + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}] Cannot defer despawn while not spawned."); + } + return; } if (!HasAuthority) { - NetworkLog.LogError($"Only the authority can invoke {nameof(DeferDespawn)} and local Client-{NetworkManager.LocalClientId} is not the authority of {name}!"); + if (NetworkManagerOwner.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}] 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++) { @@ -442,7 +436,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++) { @@ -611,24 +605,43 @@ 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 + // Ensure we log the DAMode message first as locking ownership is not allowed if not DA so the DA message is the most relevant. if (!NetworkManager.DistributedAuthorityMode) { - Debug.LogError($"[Feature Not Allowed In Client-Server Mode] Ownership flags are a distributed authority feature only!"); + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}][Feature Not Allowed In Client-Server Mode] Ownership flags are a distributed authority feature only!"); + } + return false; + } + + if (!IsSpawned) + { + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}][Attempted Lock While not spawned]"); + } + return false; } // If we don't have authority exit early if (!HasAuthority) { - NetworkLog.LogWarningServer($"[Attempted Lock Without Authority] Client-{NetworkManager.LocalClientId} is trying to lock ownership but does not have authority!"); + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogWarningServer($"[{name}][Attempted Lock Without Authority] Client-{NetworkManagerOwner.LocalClientId} is trying to lock ownership but does not have authority!"); + } return false; } // If we don't have the Transferable flag set and it is not a player object, then it is the same as having a static lock on ownership if (!(IsOwnershipTransferable || IsPlayerObject) || IsOwnershipSessionOwner) { - NetworkLog.LogWarning($"Trying to add or remove ownership lock on [{name}] which does not have the {nameof(OwnershipStatus.Transferable)} flag set!"); + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogWarning($"[{name}] Trying to add or remove ownership lock on [{name}] which does not have the {nameof(OwnershipStatus.Transferable)} flag set!"); + } return false; } @@ -746,6 +759,11 @@ public enum OwnershipRequestStatus /// This object is marked as SessionOwnerOnly and therefore cannot be requested /// SessionOwnerOnly, + + /// + /// It is invalid to request ownership at this time + /// + InvalidOperation, } /// @@ -757,15 +775,26 @@ public enum OwnershipRequestStatus /// : The current client is already the owner (no need to request ownership). /// : The flag is not set on this /// : The current owner has locked ownership which means requests are not available at this time. - /// : There is already a known request in progress. You can scan for ownership changes and try upon - /// : This object can only belong the the session owner and so cannot be requested + /// : There is already a known request in progress. You can scan for ownership changes and try upon. + /// : This object can only belong the the session owner and so cannot be requested. + /// : It is invalid to request ownership of this object at this time. /// a change in ownership or just try again after a specific period of time or no longer attempt to request ownership. /// /// public OwnershipRequestStatus RequestOwnership() { + // An ownership change request can only be made if spawned. + if (!IsSpawned) + { + if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogErrorServer($"[{name}][Invalid Operation] Cannot request ownership of an NetworkObject before it is spawned."); + } + + return OwnershipRequestStatus.InvalidOperation; + } // Exit early the local client is already the owner - if (OwnerClientId == NetworkManager.LocalClientId) + if (OwnerClientId == NetworkManagerOwner.LocalClientId) { return OwnershipRequestStatus.AlreadyOwner; } @@ -801,14 +830,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; } @@ -873,7 +902,7 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) // 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 { @@ -887,15 +916,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); } } @@ -992,9 +1021,9 @@ 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."); + NetworkLog.LogWarning($"[{name}] Only the session owner is allowed to set the ownership status to session owner only."); return false; } @@ -1015,7 +1044,7 @@ public bool SetOwnershipStatus(OwnershipStatus status, bool clearAndSet = false, } else if (Ownership.HasFlag(OwnershipStatus.SessionOwner)) { - NetworkLog.LogWarning("No other ownership statuses may be set while SessionOwner is set."); + NetworkLog.LogWarning($"[{name}] No other ownership statuses may be set while SessionOwner is set."); return false; } else @@ -1082,22 +1111,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); } } @@ -1131,7 +1160,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 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; @@ -1176,17 +1205,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 => 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 => NetworkManager != null && OwnerClientId == NetworkManager.LocalClientId; + 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 => NetworkManager != null && OwnerClientId == NetworkManager.ServerClientId; + public bool IsOwnedByServer => IsSpawned && OwnerClientId == NetworkManager.ServerClientId; /// /// Gets if the object has yet been spawned across the network @@ -1456,7 +1485,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!"); } @@ -1468,7 +1497,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; @@ -1481,13 +1510,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!"); + NetworkLog.LogWarning($"[NetworkShow] Trying to make {nameof(NetworkObject)} {name} visible to client ({clientId}) but {nameof(CheckObjectVisibility)} returned false!"); } return; } - NetworkManager.SpawnManager.MarkObjectForShowingTo(this, clientId); + NetworkManagerOwner.SpawnManager.MarkObjectForShowingTo(this, clientId); AddObserver(clientId); } @@ -1514,55 +1543,6 @@ public static void NetworkShow(List networkObjects, ulong clientI NetworkLog.LogErrorServer($"At least one {nameof(NetworkObject)} has to be provided when showing a list of {nameof(NetworkObject)}s!"); return; } - - // Do the safety loop first to prevent putting the netcode in an invalid state. - for (int i = 0; i < networkObjects.Count; i++) - { - var networkObject = networkObjects[i]; - var networkManager = networkObject.NetworkManager; - - if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) - { - NetworkLog.LogErrorServer($"Cannot hide an object from the owner when distributed authority mode is enabled! (Skipping {networkObject.gameObject.name})"); - } - else if (!networkManager.DistributedAuthorityMode && clientId == NetworkManager.ServerClientId) - { - NetworkLog.LogErrorServer("Cannot hide an object from the server!"); - continue; - } - - // Distributed authority mode adjustments to log a network error and continue when trying to show a NetworkObject - // that the local instance does not own - if (!networkObjects[i].HasAuthority) - { - if (networkObjects[i].NetworkManager.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!"); - continue; - } - else - { - throw new NotServerException("Only server can change visibility"); - } - } - - 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) - { - throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManager)); - } - } - foreach (var networkObject in networkObjects) { networkObject.NetworkShow(clientId); @@ -1590,9 +1570,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!"); } @@ -1602,11 +1582,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; @@ -1618,41 +1598,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); } } @@ -1678,56 +1658,6 @@ public static void NetworkHide(List networkObjects, ulong clientI NetworkLog.LogErrorServer($"At least one {nameof(NetworkObject)} has to be provided when hiding a list of {nameof(NetworkObject)}s!"); return; } - - // Do the safety loop first to prevent putting the netcode in an invalid state. - for (int i = 0; i < networkObjects.Count; i++) - { - var networkObject = networkObjects[i]; - var networkManager = networkObject.NetworkManager; - - if (networkManager.DistributedAuthorityMode && clientId == networkObject.OwnerClientId) - { - NetworkLog.LogErrorServer($"Cannot hide an object from the owner when distributed authority mode is enabled! (Skipping {networkObject.gameObject.name})"); - } - else if (!networkManager.DistributedAuthorityMode && clientId == NetworkManager.ServerClientId) - { - NetworkLog.LogErrorServer("Cannot hide an object from the server!"); - continue; - } - - // Distributed authority mode adjustments to log a network error and continue when trying to show a NetworkObject - // that the local instance does not own - if (!networkObjects[i].HasAuthority) - { - if (networkObjects[i].NetworkManager.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!"); - continue; - } - else - { - throw new NotServerException("Only server can change visibility!"); - } - } - - // CLIENT SPAWNING TODO: Log error and continue as opposed to throwing an exception - if (!networkObjects[i].IsSpawned) - { - throw new SpawnStateException("Object is not spawned"); - } - - if (!networkObjects[i].Observers.Contains(clientId)) - { - throw new VisibilityChangeException($"{nameof(NetworkObject)} with {nameof(NetworkObjectId)}: {networkObjects[i].NetworkObjectId} is already hidden"); - } - - if (networkObjects[i].NetworkManager != networkManager) - { - throw new ArgumentNullException("All " + nameof(NetworkObject) + "s must belong to the same " + nameof(NetworkManager)); - } - } - foreach (var networkObject in networkObjects) { networkObject.NetworkHide(clientId); @@ -1736,61 +1666,49 @@ public static void NetworkHide(List networkObjects, ulong clientI private void OnDestroy() { + var networkManager = NetworkManager; // If no NetworkManager is assigned, then just exit early - if (!NetworkManager) + if (!networkManager) { return; } - // An authorized destroy is when done by the authority instance or done due to a scene event and the NetworkObject - // was marked as destroy pending scene event (which means the destroy with scene property was set). - var isAuthorityDestroy = HasAuthority || NetworkManager.DAHost || DestroyPendingSceneEvent; + // Always attempt to remove from scene changed updates + networkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); - if (NetworkManager.IsListening && !isAuthorityDestroy && IsSpawned && - (IsSceneObject == null || (IsSceneObject.Value != true))) + if (IsSpawned && !networkManager.ShutdownInProgress) { - // If we destroyed a GameObject with a NetworkObject component on the non-authority side, handle cleaning up the SceneMigrationSynchronization. - NetworkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); + // An authorized destroy is when done by the authority instance or done due to a scene event and the NetworkObject + // was marked as destroy pending scene event (which means the destroy with scene property was set). + var isAuthorityDestroy = HasAuthority || NetworkManager.DAHost || DestroyPendingSceneEvent; - // Clients should not despawn NetworkObjects while connected to a session, but we don't want to destroy the current call stack - // if this happens. Instead, we should just generate a network log error and exit early (as long as we are not shutting down). - if (!NetworkManager.ShutdownInProgress) + // If the NetworkObject's GameObject is still valid and the scene is still valid and loaded, then we are still valid + var isStillValid = gameObject != null && gameObject.scene.IsValid() && gameObject.scene.isLoaded; + + // If we're not the authority and everything is valid and dynamically spawned, then the destroy is not valid. + if (!isAuthorityDestroy && IsSceneObject == false && isStillValid) { - // Since we still have a session connection, log locally and on the server to inform user of this issue. - // If the NetworkObject's GameObject is not valid or the scene is no longer valid or loaded, then this was due to the - // unloading of a scene which is done by the authority... - if (gameObject != null && gameObject.scene.IsValid() && gameObject.scene.isLoaded) + if (networkManager.LogLevel <= LogLevel.Error) { - if (NetworkManager.LogLevel <= LogLevel.Error) + if (networkManager.DistributedAuthorityMode) { - if (NetworkManager.DistributedAuthorityMode) - { - NetworkLog.LogError($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-owner client is not valid during a distributed authority session. Call {nameof(Destroy)} or {nameof(Despawn)} on the client-owner instead."); - } - else - { - NetworkLog.LogErrorServer($"[Invalid Destroy][{gameObject.name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); - } + NetworkLog.LogError($"[Invalid Destroy][{name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-owner client is not valid during a distributed authority session. Call {nameof(Destroy)} or {nameof(Despawn)} on the client-owner instead."); + } + else + { + NetworkLog.LogErrorServer($"[Invalid Destroy][{name}][NetworkObjectId:{NetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call {nameof(Destroy)} or {nameof(Despawn)} on the server/host instead."); } - return; - } - else - { - // If the destroy was authority scene event triggered, then mark this destroy as authority triggered. - isAuthorityDestroy = true; } + + return; } - // Otherwise, clients can despawn NetworkObjects while shutting down and should not generate any messages when this happens } - // Always attempt to remove from scene changed updates - NetworkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this); - - if (NetworkManager.SpawnManager != null && NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) + if (networkManager.SpawnManager != null && networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { if (this == networkObject) { - NetworkManager.SpawnManager.OnDespawnObject(networkObject, false); + networkManager.SpawnManager.OnDespawnObject(networkObject, false); } } } @@ -1802,16 +1720,17 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla { NetworkManagerOwner = NetworkManager.Singleton; } - if (!NetworkManager.IsListening) + + if (!NetworkManagerOwner.IsListening) { - throw new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before spawning objects"); + throw new NotListeningException($"{nameof(NetworkManagerOwner)} is not listening, start a server or host before spawning objects"); } - if ((!NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode) || (NetworkManager.DistributedAuthorityMode && !NetworkManager.LocalClient.IsSessionOwner && NetworkManager.LocalClientId != ownerClientId)) + if ((!NetworkManagerOwner.IsServer && !NetworkManagerOwner.DistributedAuthorityMode) || (NetworkManagerOwner.DistributedAuthorityMode && !NetworkManagerOwner.LocalClient.IsSessionOwner && NetworkManagerOwner.LocalClientId != ownerClientId)) { - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { - throw new NotServerException($"When distributed authority mode is enabled, you can only spawn NetworkObjects that belong to the local instance! Local instance id {NetworkManager.LocalClientId} is not the same as the assigned owner id: {ownerClientId}!"); + throw new NotServerException($"When distributed authority mode is enabled, you can only spawn NetworkObjects that belong to the local instance! Local instance id {NetworkManagerOwner.LocalClientId} is not the same as the assigned owner id: {ownerClientId}!"); } else { @@ -1819,69 +1738,67 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } } - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManagerOwner.DistributedAuthorityMode) { - if (NetworkManager.LocalClient == null || !NetworkManager.IsConnectedClient || !NetworkManager.ConnectionManager.LocalClient.IsApproved) + if (NetworkManagerOwner.LocalClient == null || !NetworkManagerOwner.IsConnectedClient || !NetworkManagerOwner.ConnectionManager.LocalClient.IsApproved) { Debug.LogError($"Cannot spawn {name} until the client is fully connected to the session!"); return; } - if (NetworkManager.NetworkConfig.EnableSceneManagement) + if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement) { - if (!NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(gameObject.scene.handle)) + if (!NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(gameObject.scene.handle)) { // Most likely this issue is due to an integration test - if (NetworkManager.LogLevel <= LogLevel.Developer) + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { - NetworkLog.LogWarning($"Failed to find scene handle {gameObject.scene.handle} for {gameObject.name}!"); + NetworkLog.LogWarning($"[{name}] Failed to find scene handle {gameObject.scene.handle} for {gameObject.name}!"); } // Just use the existing handle NetworkSceneHandle = gameObject.scene.handle; } else { - NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; + NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[gameObject.scene.handle]; } } if (DontDestroyWithOwner && !IsOwnershipDistributable) { - //Ownership |= OwnershipStatus.Distributable; - // DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set - if (NetworkManager.LogLevel == LogLevel.Developer) + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { - NetworkLog.LogWarning("DANGO-TODO: Review over don't destroy with owner being set but DistributeOwnership not being set. For now, if the NetworkObject does not destroy with the owner it will set ownership to SessionOwner."); + NetworkLog.LogWarning($"Don't destroy with owner is set but DistributeOwnership is not set. If the owner leaves, the ownership of {name} will be set to the SessionOwner."); } } } - NetworkManager.SpawnManager.AuthorityLocalSpawn(this, NetworkManager.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene); + NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene); - if ((NetworkManager.DistributedAuthorityMode && NetworkManager.DAHost) || (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer)) + if ((NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.DAHost) || (!NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.IsServer)) { - for (int i = 0; i < NetworkManager.ConnectedClientsList.Count; i++) + for (int i = 0; i < NetworkManagerOwner.ConnectedClientsList.Count; i++) { - if (NetworkManager.ConnectedClientsList[i].ClientId == NetworkManager.ServerClientId) + if (NetworkManagerOwner.ConnectedClientsList[i].ClientId == NetworkManager.ServerClientId) { continue; } - if (Observers.Contains(NetworkManager.ConnectedClientsList[i].ClientId)) + if (Observers.Contains(NetworkManagerOwner.ConnectedClientsList[i].ClientId)) { - NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ConnectedClientsList[i].ClientId, this); + NetworkManagerOwner.SpawnManager.SendSpawnCallForObject(NetworkManagerOwner.ConnectedClientsList[i].ClientId, this); } } } - else if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost) + else if (NetworkManagerOwner.DistributedAuthorityMode && !NetworkManagerOwner.DAHost) { // If spawning with observers or if not spawning with observers but the observer count is greater than 1 (i.e. owner/authority creating), // then we want to send a spawn notification. if (SpawnWithObservers || !SpawnWithObservers && Observers.Count > 1) { - NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this); + NetworkManagerOwner.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this); } } else { - NetworkLog.LogWarningServer($"Ran into unknown conditional check during spawn when determining distributed authority mode or not"); + NetworkLog.LogWarningServer($"[{name}] Ran into unknown conditional check during spawn when determining distributed authority mode or not"); } } @@ -1965,7 +1882,8 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow /// Should the object be destroyed when the scene is changed public void Spawn(bool destroyWithScene = false) { - var clientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; + var networkManager = NetworkManager; + var clientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId; SpawnInternal(destroyWithScene, clientId, false); } @@ -2003,7 +1921,11 @@ public void Despawn(bool destroy = true) { if (!IsSpawned) { - NetworkLog.LogErrorServer("Object is not spawned!"); + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}][Attempted despawn before {nameof(NetworkObject)} was spawned]"); + } + return; } @@ -2011,7 +1933,7 @@ public void Despawn(bool destroy = true) { behavior.MarkVariablesDirty(false); } - NetworkManager.SpawnManager.DespawnObject(this, destroy); + NetworkManagerOwner.SpawnManager.DespawnObject(this, destroy); } internal void ResetOnDespawn() @@ -2030,7 +1952,15 @@ internal void ResetOnDespawn() /// public void RemoveOwnership() { - NetworkManager.SpawnManager.RemoveOwnership(this); + if (!IsSpawned) + { + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}][Attempted ownership removal before {nameof(NetworkObject)} was spawned]"); + } + return; + } + NetworkManagerOwner.SpawnManager.RemoveOwnership(this); } /// @@ -2039,7 +1969,17 @@ public void RemoveOwnership() /// The new owner clientId public void ChangeOwnership(ulong newOwnerClientId) { - NetworkManager.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority); + if (!IsSpawned) + { + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}][Attempted ownership change before {nameof(NetworkObject)} was spawned]"); + } + + return; + } + + NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority); } /// @@ -2048,14 +1988,24 @@ public void ChangeOwnership(ulong newOwnerClientId) /// internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulong newOwnerClientId) { - var distributedAuthorityMode = NetworkManager.DistributedAuthorityMode; - var isServer = NetworkManager.IsServer; - var isPreviousOwner = originalOwnerClientId == NetworkManager.LocalClientId; - var isNewOwner = newOwnerClientId == NetworkManager.LocalClientId; + if (!IsSpawned) + { + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogErrorServer($"[{name}][Attempted behavior invoke on ownership changed before {nameof(NetworkObject)} was spawned]"); + } + + return; + } + + var distributedAuthorityMode = NetworkManagerOwner.DistributedAuthorityMode; + var isServer = NetworkManagerOwner.IsServer; + var isPreviousOwner = originalOwnerClientId == NetworkManagerOwner.LocalClientId; + var isNewOwner = newOwnerClientId == NetworkManagerOwner.LocalClientId; if (distributedAuthorityMode || isPreviousOwner) { - NetworkManager.SpawnManager.UpdateOwnershipTable(this, originalOwnerClientId, true); + NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, originalOwnerClientId, true); } foreach (var childBehaviour in ChildNetworkBehaviours) @@ -2067,7 +2017,7 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo } } - NetworkManager.SpawnManager.UpdateOwnershipTable(this, newOwnerClientId); + NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, newOwnerClientId); if (distributedAuthorityMode || isServer || isNewOwner) { @@ -2075,7 +2025,7 @@ internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulo { if (!childBehaviour.gameObject.activeInHierarchy) { - Debug.LogWarning($"{childBehaviour.gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during ownership assignment!"); + Debug.LogWarning($"[{name}] {childBehaviour.gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during ownership assignment!"); continue; } @@ -2094,7 +2044,7 @@ internal void InvokeOwnershipChanged(ulong previous, ulong next) } else { - Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!"); + Debug.LogWarning($"[{name}] {ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!"); } } } @@ -2180,7 +2130,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 @@ -2200,7 +2150,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 @@ -2244,12 +2194,7 @@ public bool TryRemoveParent(bool worldPositionStays = true) /// Whether or not reparenting was successful. public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true) { - if (!AutoObjectParentSync) - { - return false; - } - - if (NetworkManager == null || !NetworkManager.IsListening) + if (!AutoObjectParentSync || !IsSpawned || !NetworkManagerOwner.IsListening) { return false; } @@ -2260,7 +2205,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 && !NetworkManagerOwner.ShutdownInProgress) { return false; } @@ -2270,35 +2215,27 @@ 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 && (IsSpawned ^ parent.IsSpawned) && !NetworkManager.ShutdownInProgress) { if (NetworkManager.LogLevel <= LogLevel.Developer) { var nameOfNotSpawnedObject = IsSpawned ? $" the parent ({parent.name})" : $"the child ({name})"; - NetworkLog.LogWarning($"Parenting failed because {nameOfNotSpawnedObject} is not spawned!"); + NetworkLog.LogWarning($"[{name}] Parenting failed because {nameOfNotSpawnedObject} is not spawned!"); } return false; } m_CachedWorldPositionStays = worldPositionStays; - - if (parent == null) - { - CurrentParent = null; - transform.SetParent(null, worldPositionStays); - } - else - { - CurrentParent = parent; - transform.SetParent(parent.transform, worldPositionStays); - } + CurrentParent = parent; + transform.SetParent(CurrentParent?.transform, worldPositionStays); return true; } private void OnTransformParentChanged() { - if (!AutoObjectParentSync || NetworkManager.ShutdownInProgress) + var networkManager = NetworkManager; + if (!AutoObjectParentSync || networkManager.ShutdownInProgress) { return; } @@ -2308,41 +2245,17 @@ private void OnTransformParentChanged() return; } - if (NetworkManager == null || !NetworkManager.IsListening) + if (networkManager == null || !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. - if (NetworkManager.DistributedAuthorityMode && m_CachedParent != null && transform.parent == null) + if (networkManager.DistributedAuthorityMode && m_CachedParent != null && transform.parent == null) { m_CachedParent = null; return; } transform.parent = m_CachedParent; - Debug.LogException(new NotListeningException($"{nameof(NetworkManager)} is not listening, start a server or host before reparenting")); - return; - } - var isAuthority = false; - // 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; - - // 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 - // 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!"); - } - else - { - transform.parent = m_CachedParent; - Debug.LogException(new NotServerException($"Only the server can reparent {nameof(NetworkObject)}s")); - } + Debug.LogException(new NotListeningException($"[{name}] {nameof(networkManager)} is not listening, start a server or host before re-parenting")); return; } @@ -2359,27 +2272,48 @@ 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($"[{name}] {nameof(NetworkObject)} can only be re-parented after being spawned")); } return; } + + // With distributed authority, we need to track "valid authoritative" parenting changes. + // So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change". + var isParentingAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); + // If we are spawned and don't have authority; reset the parent back to the cached parent and exit + if (!isParentingAuthority) + { + transform.parent = m_CachedParent; + if (networkManager.LogLevel <= LogLevel.Error) + { + if (networkManager.DistributedAuthorityMode) + { + NetworkLog.LogError($"[{name}][Not Owner] Only the owner-authority of child {gameObject.name}'s {nameof(NetworkObject)} component can re-parent it!"); + } + else + { + Debug.LogException(new NotServerException($"[{name}] Only the server can re-parent {nameof(NetworkObject)}s")); + } + } + return; + } + 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; - Debug.LogException(new InvalidParentException($"Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent")); + Debug.LogException(new InvalidParentException($"[{name}] Invalid parenting, {nameof(NetworkObject)} moved under a non-{nameof(NetworkObject)} parent")); return; } else if (!parentObject.IsSpawned) { 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($"[{name}] {nameof(NetworkObject)} can only be re-parented under another spawned {nameof(NetworkObject)}")); return; } @@ -2417,20 +2351,20 @@ private void OnTransformParentChanged() } // If we're not the server, we should tell the server about this parent change - if (!NetworkManager.IsServer) + if (!networkManager.IsServer) { // Don't send a message in DA mode if we're the only observers of this object (we're the only authority). - if (distributedAuthority && Observers.Count <= 1) + if (networkManager.DistributedAuthorityMode && Observers.Count <= 1) { return; } - NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); + networkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, NetworkManager.ServerClientId); return; } // Otherwise we are a Server (client-server or DAHost). Send to all observers - foreach (var clientId in NetworkManager.ConnectionManager.ConnectedClientIds) + foreach (var clientId in networkManager.ConnectionManager.ConnectedClientIds) { if (clientId == NetworkManager.ServerClientId) { @@ -2438,7 +2372,7 @@ private void OnTransformParentChanged() } if (Observers.Contains(clientId)) { - NetworkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); + networkManager.ConnectionManager.SendMessage(ref message, MessageDeliveryType.DefaultDelivery, clientId); } } } @@ -2533,16 +2467,17 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa return true; } + var networkManager = NetworkManager; // If we have a latest parent id but it hasn't been spawned yet, then add this instance to the orphanChildren // HashSet and return false (i.e. parenting not applied yet) - if (m_LatestParent.HasValue && !NetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value)) + if (m_LatestParent.HasValue && !networkManager.SpawnManager.SpawnedObjects.ContainsKey(m_LatestParent.Value)) { OrphanChildren.Add(this); return false; } // If we made it here, then parent this instance under the parentObject - var parentObject = NetworkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value]; + var parentObject = networkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value]; // If we are handling an orphaned child and its parent is orphaned too, then don't parent yet. if (orphanedChildPass) @@ -2588,7 +2523,7 @@ internal void InvokeBehaviourNetworkPreSpawn() internal void InvokeBehaviourNetworkSpawn() { - NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); + NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); // Always invoke all InternalOnNetworkSpawn methods on each child NetworkBehaviour // ** before ** invoking OnNetworkSpawn. @@ -2659,8 +2594,8 @@ internal void InvokeBehaviourNetworkDespawn() ChildNetworkBehaviours[i].InternalOnNetworkPreDespawn(); } - NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); - NetworkManager.SpawnManager.RemoveNetworkObjectFromSceneChangedUpdates(this); + NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true); + NetworkManagerOwner.SpawnManager.RemoveNetworkObjectFromSceneChangedUpdates(this); for (int i = 0; i < ChildNetworkBehaviours.Count; i++) { @@ -2760,7 +2695,7 @@ internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong orig // Force send a state update for all owner read NetworkVariables and any currently dirty // owner write NetworkVariables. - NetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(true); + NetworkManagerOwner.BehaviourUpdater.NetworkBehaviourUpdate(true); } // NGO currently guarantees that the client will receive spawn data for all objects in one network tick. @@ -2980,7 +2915,7 @@ public struct TransformData : INetworkSerializeByMemcpy public void Serialize(FastBufferWriter writer) { - if (OwnerObject.NetworkManager.DistributedAuthorityMode) + if (OwnerObject.NetworkManagerOwner.DistributedAuthorityMode) { HasOwnershipFlags = true; SpawnWithObservers = OwnerObject.SpawnWithObservers; @@ -3030,7 +2965,7 @@ public void Serialize(FastBufferWriter writer) // The NetworkSceneHandle is the server-side relative // scene handle that the NetworkObject resides in. - if (OwnerObject.NetworkManager.DistributedAuthorityMode) + if (OwnerObject.NetworkManagerOwner.DistributedAuthorityMode) { writer.WriteValue(OwnerObject.NetworkSceneHandle); } @@ -3207,7 +3142,7 @@ internal SerializedObject Serialize(ulong targetClientId = NetworkManager.Server IsSceneObject = IsSceneObject ?? true, DestroyWithScene = DestroyWithScene, DontDestroyWithOwner = DontDestroyWithOwner, - HasOwnershipFlags = NetworkManager.DistributedAuthorityMode, + HasOwnershipFlags = NetworkManagerOwner.DistributedAuthorityMode, OwnershipFlags = (ushort)Ownership, SyncObservers = syncObservers, Observers = syncObservers ? Observers.ToArray() : null, @@ -3444,9 +3379,9 @@ internal void SubscribeToActiveSceneForSynch() /// private void CurrentlyActiveSceneChanged(Scene current, Scene next) { - // 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) + // Early exit if the NetworkObject is not spawned, is an in-scene placed NetworkObject, + // or the NetworkManager is shutting down. + if (!IsSpawned || IsSceneObject != false || NetworkManagerOwner.ShutdownInProgress) { return; } @@ -3469,13 +3404,13 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) /// internal void SceneChangedUpdate(Scene scene, bool notify = false) { - // Avoiding edge case scenarios, if no NetworkSceneManager exit early - if (NetworkManager.SceneManager == null || !IsSpawned) + // Avoiding edge case scenarios, if not spawned or no NetworkSceneManager exit early + if (!IsSpawned || NetworkManagerOwner.SceneManager == null) { return; } - if (NetworkManager.SceneManager.IsSceneEventInProgress()) + if (NetworkManagerOwner.SceneManager.IsSceneEventInProgress()) { return; } @@ -3484,17 +3419,17 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) SceneOriginHandle = scene.handle; // non-authority needs to update the NetworkSceneHandle - if (!isAuthority && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) + if (!isAuthority && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) { - NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; + NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; } else if (isAuthority) { // Since the authority is the source of truth for the NetworkSceneHandle, // the NetworkSceneHandle is the same as the SceneOriginHandle. - if (NetworkManager.DistributedAuthorityMode && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) + if (NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) { - NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; + NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; } else { @@ -3502,22 +3437,22 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } } else // Otherwise, the client did not find the client to server scene handle - if (NetworkManager.LogLevel == LogLevel.Developer) + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { // There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject // into, but that scenario seemed very edge case and under most instances a user should be notified that this // server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to // the server-side too. - NetworkLog.LogWarningServer($"[Client-{NetworkManager.LocalClientId}][{gameObject.name}] Server - " + + NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " + $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" + $"has no associated server side (network) scene handle!"); } OnMigratedToNewScene?.Invoke(); // Only the authority side will notify clients of non-parented NetworkObject scene changes - if (isAuthority && notify && transform.parent == null) + if (isAuthority && notify && !transform.parent) { - NetworkManager.SceneManager.NotifyNetworkObjectSceneChanged(this); + NetworkManagerOwner.SceneManager.NotifyNetworkObjectSceneChanged(this); } } @@ -3545,11 +3480,11 @@ private void Awake() /// internal bool UpdateForSceneChanges() { - // 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 == null || 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; @@ -3573,17 +3508,18 @@ internal bool UpdateForSceneChanges() /// appropriate hash value internal uint CheckForGlobalObjectIdHashOverride() { - if (NetworkManager.IsServer || NetworkManager.DistributedAuthorityMode) + var networkManager = NetworkManager; + if (networkManager.IsServer || networkManager.DistributedAuthorityMode) { - if (NetworkManager.PrefabHandler.ContainsHandler(this)) + if (networkManager.PrefabHandler.ContainsHandler(this)) { - var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash); + var globalObjectIdHash = networkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash); return globalObjectIdHash == 0 ? GlobalObjectIdHash : globalObjectIdHash; } // If scene management is disabled and this is an in-scene placed NetworkObject then go ahead // and send the InScenePlacedSourcePrefab's GlobalObjectIdHash value (i.e. what to dynamically spawn) - if (!NetworkManager.NetworkConfig.EnableSceneManagement && IsSceneObject.Value && InScenePlacedSourceGlobalObjectIdHash != 0) + if (!networkManager.NetworkConfig.EnableSceneManagement && IsSceneObject.Value && InScenePlacedSourceGlobalObjectIdHash != 0) { return InScenePlacedSourceGlobalObjectIdHash; } @@ -3601,9 +3537,9 @@ internal uint CheckForGlobalObjectIdHashOverride() else { // For legacy manual instantiation and spawning, check the OverrideToNetworkPrefab for a possible match - if (NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash)) + if (networkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash)) { - return NetworkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab[GlobalObjectIdHash]; + return networkManager.NetworkConfig.Prefabs.OverrideToNetworkPrefab[GlobalObjectIdHash]; } } } @@ -3620,7 +3556,7 @@ internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour) { if (networkBehaviour.IsSpawned && IsSpawned) { - if (NetworkManager?.LogLevel == LogLevel.Developer) + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) { NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)"); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index 9bd986857e..eb54a25d63 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -166,6 +166,15 @@ public void Handle(ref NetworkContext context) return; } + if (!networkObject.IsSpawned) + { + if (networkManager.LogLevel <= LogLevel.Normal) + { + NetworkLog.LogError($"[{networkObject.name}] Ownership change received for network object that is not yet spawned."); + } + return; + } + // If ownership is changing (either a straight change or a request approval), then run through the ownership changed sequence // Note: There is some extended ownership script at the bottom of HandleOwnershipChange // If not in distributed authority mode, ChangeMessageType will always be OwnershipChanging. diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 3efd90779d..38b21350dd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1170,7 +1170,7 @@ internal void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong // If this the player and the client is the owner, then lock ownership by default if (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClientId == ownerClientId && playerObject) { - networkObject.SetOwnershipLock(); + networkObject.AddOwnershipExtended(NetworkObject.OwnershipStatusExtended.Locked); } networkObject.IsSpawned = true; @@ -1410,46 +1410,53 @@ internal void DespawnAndDestroyNetworkObjects() var networkObjects = UnityEngine.Object.FindObjectsOfType(); #endif - for (int i = 0; i < networkObjects.Length; i++) + foreach (var networkObject in networkObjects) { - if (networkObjects[i].NetworkManager == NetworkManager) + // We are not the authority of this NetworkObject + // Mostly used for integration testing + if (networkObject.NetworkManager != NetworkManager) { - if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i])) - { - OnDespawnObject(networkObjects[i], false); - // Leave destruction up to the handler - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]); - } - else - { - // If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene - // is unloaded. Otherwise, despawn and destroy it. - var shouldDestroy = !(networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value)); + continue; + } + + // The NetworkManagerOwner field must be set before calling OnDespawnObject + networkObject.NetworkManagerOwner = NetworkManager; + + if (NetworkManager.PrefabHandler.ContainsHandler(networkObject)) + { + OnDespawnObject(networkObject, false); + // Leave destruction up to the handler + NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObject); + } + else + { + // If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene + // is unloaded. Otherwise, despawn and destroy it. + var shouldDestroy = !(networkObject.IsSceneObject == null || (networkObject.IsSceneObject != null && networkObject.IsSceneObject.Value)); - // If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed - if (shouldDestroy) + // If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed + if (shouldDestroy) + { + // Check to see if there are any in-scene placed children that are marked to be destroyed with the scene + var childrenObjects = networkObject.GetComponentsInChildren(); + foreach (var childObject in childrenObjects) { - // Check to see if there are any in-scene placed children that are marked to be destroyed with the scene - var childrenObjects = networkObjects[i].GetComponentsInChildren(); - foreach (var childObject in childrenObjects) + if (childObject == networkObject) { - if (childObject == networkObjects[i]) - { - continue; - } + continue; + } - // If the child is an in-scene placed NetworkObject then remove the child from the parent (which was dynamically spawned) - // and set its parent to root - if (childObject.IsSceneObject != null && childObject.IsSceneObject.Value) - { - childObject.TryRemoveParent(childObject.WorldPositionStays()); - } + // If the child is an in-scene placed NetworkObject then remove the child from the parent (which was dynamically spawned) + // and set its parent to root + if (childObject.IsSceneObject != null && childObject.IsSceneObject.Value) + { + childObject.TryRemoveParent(childObject.WorldPositionStays()); } } - - //Despawn and potentially destroy. - OnDespawnObject(networkObjects[i], shouldDestroy); } + + //Despawn and potentially destroy. + OnDespawnObject(networkObject, shouldDestroy); } } } diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index d4fc1e4c9d..29877b268d 100644 --- a/com.unity.netcode.gameobjects/package.json +++ b/com.unity.netcode.gameobjects/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "2.8.1", + "version": "2.9.0", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", @@ -15,4 +15,4 @@ "path": "Samples~/Bootstrap" } ] -} \ No newline at end of file +}