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
77 changes: 57 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 && !HasBeenSpawned)
{
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 @@ -2094,6 +2124,17 @@ internal void SetupOnSpawn(ulong networkId, bool isPlayerObject, ulong ownerClie
}
}

/// <summary>
/// Resets this NetworkObject at the end of a session.
/// Ensures scene objects are ready to be reused
/// </summary>
internal void ResetOnShutdown()
{
NetworkLog.InternalAssert(NetworkManager.ShutdownInProgress, "This method should only be called while the NetworkManager is shutting down");
m_SpawnCount = 0;
ResetOnDespawn();
}

internal void ResetOnDespawn()
{
// Always clear out the observers list when despawned
Expand Down Expand Up @@ -3466,11 +3507,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 +3664,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
Loading