Skip to content
Open
197 changes: 134 additions & 63 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true)
return;
}

if (!HasAuthority)
if (!m_HasAuthority)
{
if (NetworkManagerOwner.LogLevel <= LogLevel.Error)
{
Expand Down Expand Up @@ -633,7 +633,7 @@ public bool SetOwnershipLock(bool lockOwnership = true)
}

// If we don't have authority exit early
if (!HasAuthority)
if (!m_HasAuthority)
{
if (NetworkManager.LogLevel <= LogLevel.Error)
{
Expand Down Expand Up @@ -909,7 +909,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.
NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true);
NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, m_HasAuthority, true);
}
else
{
Expand Down Expand Up @@ -1155,14 +1155,9 @@ public bool HasOwnershipStatus(OwnershipStatus status)
/// <remarks>
/// When in client-server mode, authority should is not considered the same as ownership.
/// </remarks>
public bool HasAuthority => InternalHasAuthority();
public bool HasAuthority => IsSpawned ? m_HasAuthority : !NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool InternalHasAuthority()
{
var networkManager = NetworkManager;
return networkManager.DistributedAuthorityMode ? OwnerClientId == networkManager.LocalClientId : networkManager.IsServer;
}
private bool m_HasAuthority;

/// <summary>
/// The NetworkManager that owns this NetworkObject.
Expand Down Expand Up @@ -1227,7 +1222,7 @@ private bool InternalHasAuthority()
/// <summary>
/// Gets if the object has yet been spawned across the network
/// </summary>
public bool IsSpawned { get; internal set; }
public bool IsSpawned { get; private set; }

/// <summary>
/// Gets if the object is a SceneObject.
Expand Down Expand Up @@ -1448,10 +1443,7 @@ public bool IsNetworkVisibleTo(ulong clientId)
/// </summary>
internal Scene SceneOrigin
{
get
{
return m_SceneOrigin;
}
get => m_SceneOrigin;

set
{
Expand All @@ -1471,13 +1463,8 @@ internal Scene SceneOrigin
/// </summary>
internal NetworkSceneHandle GetSceneOriginHandle()
{
if (SceneOriginHandle.IsEmpty() && IsSpawned && InScenePlaced)
{
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!");
}
}
NetworkLog.InternalAssert(!(IsSpawned && InScenePlaced && SceneOriginHandle.IsEmpty()), $"Spawned in scene placed NetworkObject {name} should always have a valid SceneOriginHandle");

return !SceneOriginHandle.IsEmpty() ? SceneOriginHandle : gameObject.scene.handle;
}

Expand Down Expand Up @@ -1506,7 +1493,7 @@ public void NetworkShow(ulong clientId)
return;
}

if (!HasAuthority)
if (!m_HasAuthority)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
Expand Down Expand Up @@ -1601,7 +1588,7 @@ public void NetworkHide(ulong clientId)
return;
}

if (!HasAuthority)
if (!m_HasAuthority)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
Expand Down Expand Up @@ -1760,7 +1747,7 @@ private void OnDestroy()
{
// 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;
var isAuthorityDestroy = m_HasAuthority || NetworkManager.DAHost || DestroyPendingSceneEvent;

// 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;
Expand Down Expand Up @@ -2005,8 +1992,7 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow
/// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
public void Spawn(bool destroyWithScene = false)
{
var networkManager = NetworkManager;
var clientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId;
var clientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId;
SpawnInternal(destroyWithScene, clientId, false);
}

Expand Down Expand Up @@ -2058,17 +2044,123 @@ public void Despawn(bool destroy = true)
NetworkManagerOwner.SpawnManager.DespawnObject(this, destroy);
}

internal void SetupOnSpawn(ulong networkId, bool isPlayerObject, ulong ownerClientId, bool destroyWithScene)
{
NetworkObjectId = networkId;
IsPlayerObject = isPlayerObject;
OwnerClientId = ownerClientId;
// When spawned, previous owner is always the first assigned owner
PreviousOwnerId = ownerClientId;
m_HasAuthority = NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer;
IsSpawned = true;

// If this is the player, and the client is the owner, then lock ownership by default
if (NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.LocalClientId == ownerClientId && isPlayerObject)
{
AddOwnershipExtended(OwnershipStatusExtended.Locked);
}

if (IsSpawnAuthority)
{
SetupObservers();
}

/*
* Setup scene related settings
*/
DestroyWithScene = InScenePlaced || destroyWithScene;
if (InScenePlaced)
{
// Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects
// Note: Always check SceneOriginHandle directly at this specific location.
if (SceneOriginHandle.IsEmpty())
{
SceneOrigin = gameObject.scene;
}

// If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set
// then assign this to the PrefabGlobalObjectIdHash
if (InScenePlacedSourceGlobalObjectIdHash != 0)
{
PrefabGlobalObjectIdHash = InScenePlacedSourceGlobalObjectIdHash;
}
}
else if (ActiveSceneSynchronization)
{
// Just in case it is a recycled NetworkObject, unsubscribe first
SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged;
SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged;
}
}

internal void ResetOnDespawn()
{
// Always clear out the observers list when despawned
Observers.Clear();
m_HasAuthority = false;
IsSpawnAuthority = false;
IsSpawned = false;
DeferredDespawnTick = 0;
m_LatestParent = null;
RemoveOwnershipExtended(OwnershipStatusExtended.Locked | OwnershipStatusExtended.Requested);
}

internal void SetupObservers()
{
NetworkLog.InternalAssert(IsSpawnAuthority, "This function should only be called on the authority.");

if (!SpawnWithObservers)
{
if (NetworkManagerOwner.DistributedAuthorityMode)
{
// Always add the owner/authority in DA mode even if SpawnWithObservers is false
// (authority should not take into consideration networkObject.CheckObjectVisibility when SpawnWithObservers is false)
AddObserver(OwnerClientId);
}

return;
}

// If running as a server only, then make sure to always add the server's client identifier
if (NetworkManagerOwner.IsServer && !NetworkManagerOwner.IsHost)
{
AddObserver(NetworkManager.ServerClientId);
}

// If SpawnWithObservers is set,
// 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))
{
continue;
}
AddObserver(clientId);
}

// Intentionally checking as opposed to just assigning in order to generate notification.
if (!Observers.Contains(OwnerClientId))
{
// The owner only needs to always be included in DA mode.
if (NetworkManagerOwner.DistributedAuthorityMode)
{
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"Client-{OwnerClientId} is the owner of {name} but is not an observer! Adding owner as an observer!");
}
AddObserver(OwnerClientId);
}
else
{
if (NetworkManager.LogLevel <= LogLevel.Developer)
{
NetworkLog.LogWarning($"Client-{OwnerClientId} is the owner of {name} but is not an observer! This may cause issues");
}
}
}
}

/// <summary>
/// Removes all ownership of an object from any client. Can only be called from server
/// </summary>
Expand Down Expand Up @@ -2099,7 +2191,7 @@ public void ChangeOwnership(ulong newOwnerClientId)
}
return;
}
NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority);
NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, m_HasAuthority);
}

/// <summary>
Expand All @@ -2108,20 +2200,18 @@ public void ChangeOwnership(ulong newOwnerClientId)
/// </summary>
internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulong newOwnerClientId)
{
if (!IsSpawned)
{
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{name}][Attempted behavior invoke on ownership changed before {nameof(NetworkObject)} was spawned]");
}
return;
}
NetworkLog.InternalAssert(IsSpawned, "[{name}][Attempted behavior invoke on ownership changed before {nameof(NetworkObject)} was spawned]");

var distributedAuthorityMode = NetworkManagerOwner.DistributedAuthorityMode;
var isServer = NetworkManagerOwner.IsServer;
var isPreviousOwner = originalOwnerClientId == NetworkManagerOwner.LocalClientId;
var isNewOwner = newOwnerClientId == NetworkManagerOwner.LocalClientId;

if (distributedAuthorityMode)
{
m_HasAuthority = isNewOwner;
}

if (distributedAuthorityMode || isPreviousOwner)
{
NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, originalOwnerClientId, true);
Expand Down Expand Up @@ -2334,7 +2424,7 @@ public bool TrySetParent(NetworkObject parent, bool worldPositionStays = true)

// DANGO-TODO: Do we want to worry about ownership permissions here?
// It wouldn't make sense to not allow parenting, but keeping this note here as a reminder.
var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner);
var isAuthority = m_HasAuthority || (AllowOwnerToParent && IsOwner);

// 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.
Expand Down Expand Up @@ -2409,7 +2499,7 @@ private void OnTransformParentChanged()

// With distributed authority, we need to track "valid authoritative" parenting changes.
// So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change".
var isParentingAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner);
var isParentingAuthority = m_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)
{
Expand Down Expand Up @@ -2663,8 +2753,6 @@ internal void InvokeBehaviourNetworkPreSpawn()

internal void InvokeBehaviourNetworkSpawn()
{
NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);

// Always invoke all InternalOnNetworkSpawn methods on each child NetworkBehaviour
// ** before ** invoking OnNetworkSpawn.
// This assures all NetworkVariables and RPC related tables have been initialized
Expand Down Expand Up @@ -3465,29 +3553,13 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject,
return networkObject;
}

/// <summary>
/// Subscribes to changes in the currently active scene
/// </summary>
/// <remarks>
/// Only for dynamically spawned NetworkObjects
/// </remarks>
internal void SubscribeToActiveSceneForSynch()
{
if (ActiveSceneSynchronization)
{
if (!InScenePlaced)
{
// Just in case it is a recycled NetworkObject, unsubscribe first
SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged;
SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged;
}
}
}

/// <summary>
/// If AutoSynchActiveScene is enabled, then this is the callback that handles updating
/// a NetworkObject's scene information.
/// </summary>
/// <remarks>
/// Should only be used for dynamically spawned NetworkObjects
/// </remarks>
private void CurrentlyActiveSceneChanged(Scene current, Scene next)
{
// Early exit if the NetworkObject is not spawned, is an in-scene placed NetworkObject,
Expand Down Expand Up @@ -3526,15 +3598,14 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false)
return;
}

var isAuthority = HasAuthority;
SceneOriginHandle = scene.handle;

// non-authority needs to update the NetworkSceneHandle
if (!isAuthority && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle))
if (!m_HasAuthority && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle))
{
NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle];
}
else if (isAuthority)
else if (m_HasAuthority)
{
// Since the authority is the source of truth for the NetworkSceneHandle,
// the NetworkSceneHandle is the same as the SceneOriginHandle.
Expand All @@ -3561,7 +3632,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false)
OnMigratedToNewScene?.Invoke();

// Only the authority side will notify clients of non-parented NetworkObject scene changes
if (isAuthority && notify && !transform.parent)
if (m_HasAuthority && notify && !transform.parent)
{
NetworkManagerOwner.SceneManager.NotifyNetworkObjectSceneChanged(this);
}
Expand Down
8 changes: 8 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using Unity.Netcode.Logging;
using UnityEngine;
using UnityEngine.Assertions;

namespace Unity.Netcode
{
Expand Down Expand Up @@ -158,5 +160,11 @@ private static bool TryGetNetworkObjectName([NotNull] NetworkManager networkMana
return true;
}

[HideInCallstack]
[Conditional("NETCODE_INTERNAL")]
internal static void InternalAssert(bool condition, string message)
{
Assert.IsTrue(condition, message);
}
}
}
Loading
Loading