Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Unity.Netcode.Logging;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
Expand All @@ -19,8 +20,22 @@ internal class SetInScenePlaced : IProcessSceneWithReport
public int callbackOrder => 0;
public void OnProcessScene(Scene scene, BuildReport report)
{
var log = new ContextualLogger();
log.AddInfo(scene.name, scene.handle);
foreach (var networkObject in FindObjects.FromSceneByType<NetworkObject>(scene, true))
{
if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle)
{
log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s SceneOrigin doesn't match current scene being processed! Skipping processing.").AddInfo("SceneOrigin", networkObject.SceneOriginHandle).AddNetworkObject(networkObject));
continue;
}

if (networkObject.HasBeenSpawned)
{
log.Error(new Context(LogLevel.Normal, $"Processing {nameof(NetworkObject)} that has already been spawned! This should not be possible. Skipping processing.").AddNetworkObject(networkObject));
continue;
}

networkObject.InScenePlaced = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ public bool Validate(int index = -1)
return false;
}

if (networkObject.InScenePlaced)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} InScenePlaced {nameof(NetworkObject)} is being registered as a Prefab. This can cause issues!");
}
}

if (networkObject.IsSpawned)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} Currently spawned {nameof(NetworkObject)} is being registered as a Prefab. This can cause issues!");
}
}

return true;
}

Expand Down
66 changes: 46 additions & 20 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Runtime.CompilerServices;
using System.Text;
using Unity.Netcode.Components;
using Unity.Netcode.Logging;
using Unity.Netcode.Runtime;
#if UNITY_EDITOR
using UnityEditor;
Expand Down Expand Up @@ -106,6 +107,9 @@ public uint PrefabIdHash
/// </summary>
public NetworkObject CurrentParent { get; private set; }

private int m_SpawnCount;
internal bool HasBeenSpawned => m_SpawnCount > 0;

#if UNITY_EDITOR
private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}";

Expand Down Expand Up @@ -1448,12 +1452,16 @@ internal Scene SceneOrigin

set
{
if (SceneOriginHandle.IsEmpty() && value.IsValid() && value.isLoaded)
{
SceneOriginHandle = value.handle;
}

// The scene origin should only be set once.
// Once set, it should never change.
if (SceneOriginHandle.IsEmpty() && value.IsValid() && value.isLoaded)
if (!m_SceneOrigin.IsValid())
{
m_SceneOrigin = value;
SceneOriginHandle = value.handle;
}
}
}
Expand Down Expand Up @@ -1782,7 +1790,7 @@ private void OnDestroy()
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
{
if (NetworkManagerOwner == null)
{
Expand Down Expand Up @@ -1853,7 +1861,28 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla
}
}

if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), InScenePlaced, playerObject, ownerClientId, destroyWithScene))

// Calculate the legacy IsSceneObject value as the public field is obsolete with warning
// We can't break the public behavior of the field.
#pragma warning disable CS0618 // Type or member is obsolete
var legacyIsSceneObject = IsSceneObject.HasValue && IsSceneObject.Value;
#pragma warning restore CS0618 // Type or member is obsolete

// If SpawnInternal is being called on an object that is marked as InScenePlaced,
// The scene object was never automatically spawned when the scene was loaded.
// Count this object as a dynamically spawned object.
// TODO-[MTT-15388]: Actually support disabled/not spawned InScenePlaced NetworkObjects
if (InScenePlaced && m_SpawnCount == 0)
{
if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Developer)
{
Debug.LogWarning($"[{name}][SceneOrigin={SceneOriginHandle}] Dynamically spawning InScenePlaced network object. This can cause issues!", this);
}

InScenePlaced = false;
}

if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), legacyIsSceneObject, playerObject, ownerClientId, destroyWithScene))
{
if (NetworkManagerOwner.LogLevel <= LogLevel.Normal)
{
Expand Down Expand Up @@ -2053,6 +2082,7 @@ internal void SetupOnSpawn(ulong networkId, bool isPlayerObject, ulong ownerClie
// When spawned, previous owner is always the first assigned owner
PreviousOwnerId = ownerClientId;
m_HasAuthority = NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer;
m_SpawnCount++;
IsSpawned = true;

// If this is the player, and the client is the owner, then lock ownership by default
Expand Down Expand Up @@ -3466,11 +3496,7 @@ internal static NetworkObject DeserializeAndSpawnObject(in SerializedObject seri

if (serializedObject.NetworkObjectId == default)
{
if (networkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{nameof(GlobalObjectIdHash)}={serializedObject.Hash}] Received spawn request with invalid {nameof(NetworkObjectId)} {serializedObject.NetworkObjectId}. This should not happen!");
}

NetworkLog.LogErrorServer(new Context(LogLevel.Error, $"Received spawn request with invalid {nameof(NetworkObjectId)}. This should not happen!").AddInfo(nameof(NetworkObjectId), serializedObject.NetworkObjectId).AddInfo(nameof(GlobalObjectIdHash), serializedObject.Hash));
reader.Seek(endOfSynchronizationData);
return null;
}
Expand Down Expand Up @@ -3627,17 +3653,17 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false)
NetworkSceneHandle = SceneOriginHandle;
}
}
else // Otherwise, the client did not find the client to server scene handle
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-{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!");
}
// Otherwise, the client did not find the client to server scene handle
else 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-{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
Expand Down
40 changes: 27 additions & 13 deletions com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ internal static void ConfigureIntegrationTestLogging(NetworkManager networkManag
/// <param name="message">The message to log</param>
[HideInCallstack]
public static void LogErrorServer(string message) => s_Log.ErrorServer(new Context(LogLevel.Error, message, true));
[HideInCallstack]
internal static void LogErrorServer(Context context) => s_Log.ErrorServer(context);

internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType)
{
Expand All @@ -115,14 +117,14 @@ internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType)
};
}


private const string k_SenderId = "SenderId";
internal static Context BuildContextForServerMessage([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message)
{
var ctx = new Context(level, message, true).AddInfo(k_SenderId, senderId);
if (TryGetNetworkObjectName(networkManager, message, out var name))
var ctx = new Context(level, message, true).AddTag("Received log from client").AddInfo(k_SenderId, senderId);
var networkObject = TryGetNetworkObject(networkManager, message);
if (networkObject != null)
{
ctx.AddTag(name);
ctx.AddNetworkObject(networkObject);
}
return ctx;
}
Expand All @@ -135,29 +137,41 @@ internal enum LogType : byte
None
}

private static readonly Regex k_GlobalObjectIdHash = new($@"\[{nameof(NetworkObject.GlobalObjectIdHash)}=(\d+)\]", RegexOptions.Compiled);
private static readonly Regex k_NetworkObjectId = new($@"\[{nameof(NetworkObject.NetworkObjectId)}:(\d+)\]", RegexOptions.Compiled);
private static readonly Regex k_GlobalObjectIdHash = new($@"\[{nameof(NetworkObject.GlobalObjectIdHash)}:(\d+)\]", RegexOptions.Compiled);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryGetNetworkObjectName([NotNull] NetworkManager networkManager, string message, out string name)
private static NetworkObject TryGetNetworkObject([NotNull] NetworkManager networkManager, string message)
{
name = null;
if (k_NetworkObjectId.IsMatch(message))
{
var stringId = k_NetworkObjectId.Match(message).Groups[1].Value;
if (ulong.TryParse(stringId, out var networkObjectId) && networkObjectId > 0 && networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject))
{
return networkObject;
}
}

if (!k_GlobalObjectIdHash.IsMatch(message))
{
return false;
return null;
}

var stringHash = k_GlobalObjectIdHash.Match(message).Groups[1].Value;
if (!ulong.TryParse(stringHash, out var globalObjectIdHash))
{
return false;
return null;
}

if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(globalObjectIdHash, out var networkObject))
NetworkObject matchingObject = null;
foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList)
{
return false;
if (networkObject.GlobalObjectIdHash == globalObjectIdHash)
{
matchingObject = networkObject;
}
}

name = networkObject.name;
return true;
return matchingObject;
}

[HideInCallstack]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,7 @@ internal NetworkObject InstantiateNetworkPrefab([NotNull] GameObject networkPref
/// <remarks>
/// For most cases this is client-side only, except when the server is spawning a player.
/// </remarks>
[return: MaybeNull]
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject serializedObject, byte[] instantiationData = null)
{
NetworkObject networkObject = null;
Expand All @@ -977,11 +978,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject s
networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, serializedObject.NetworkSceneHandle);
if (networkObject == null)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
{
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
}

NetworkLog.LogErrorServer(new Context(LogLevel.Error, $"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure!").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), globalObjectIdHash));
return null;
}

Expand Down Expand Up @@ -1122,7 +1119,14 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n
return false;
}

if (!sceneObject && NetworkManager.LogLevel <= LogLevel.Error)
if (playerObject && networkObject.InScenePlaced)
{
NetworkLog.LogError(new Context(LogLevel.Developer, "Player prefab is marked as belonging to a scene. This may cause issues.").AddNetworkObject(networkObject).AddInfo("SceneName", networkObject.SceneOrigin.name));
networkObject.InScenePlaced = false;
}
NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value.");

if (!networkObject.InScenePlaced && NetworkManager.LogLevel <= LogLevel.Error)
{
var networkObjectChildren = networkObject.GetComponentsInChildren<NetworkObject>();
if (networkObjectChildren.Length > 1)
Expand All @@ -1137,7 +1141,7 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n
networkObject.NetworkManagerOwner = NetworkManager;
networkObject.InvokeBehaviourNetworkPreSpawn();

if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && sceneObject)
if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && networkObject.InScenePlaced)
{
networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle;
networkObject.NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle];
Expand Down Expand Up @@ -1175,10 +1179,7 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize
{
if (SpawnedObjects.ContainsKey(serializedObject.NetworkObjectId))
{
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogWarning($"Trying to spawn a {nameof(NetworkObject)} with a {nameof(NetworkObject.NetworkObjectId)} of {serializedObject.NetworkObjectId} but an object with that id is already in the spawned list. This should not happen!");
}
NetworkManager.Log.Warning(new Context(LogLevel.Error, $"Trying to spawn a {nameof(NetworkObject)} but an object with that {nameof(NetworkObject.NetworkObjectId)} is already in the spawned list. This should not happen!"));
networkObject = null;
return false;
}
Expand All @@ -1195,14 +1196,11 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize
// Log the error that the NetworkObject failed to construct
if (networkObject == null)
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogError($"[{nameof(NetworkObject.GlobalObjectIdHash)}={serializedObject.Hash}] Failed to spawn {nameof(NetworkObject)}!");
}

NetworkManager.Log.ErrorServer(new Context(LogLevel.Normal, $"Failed to spawn {nameof(NetworkObject)}").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), serializedObject.Hash));
return false;
}

networkObject.InScenePlaced = serializedObject.IsSceneObject;
networkObject.NetworkManagerOwner = NetworkManager;

// This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning
Expand All @@ -1227,11 +1225,7 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize

if (networkObject.IsSpawned)
{
if (NetworkManager.LogLevel <= LogLevel.Error)
{
NetworkLog.LogErrorServer($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!");
}

NetworkManager.Log.ErrorServer(new Context(LogLevel.Normal, $"{nameof(NetworkObject)} is already spawned!").AddNetworkObject(networkObject));
// Mark the spawn as a success if the object is already spawned
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
Expand Down Expand Up @@ -193,7 +194,7 @@ private bool HaveLogsBeenReceived()
return false;
}

if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] [SenderId:{m_ClientNetworkManagers[0].LocalClientId}] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead."))
if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, new Regex($"SenderId:{m_ClientNetworkManagers[0].LocalClientId}]")))
{
return false;
}
Expand Down
Loading
Loading