Skip to content

Commit 7ffdef3

Browse files
committed
fix: InScenePlaced edge cases
1 parent f4dfd4e commit 7ffdef3

19 files changed

Lines changed: 924 additions & 249 deletions

com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Unity.Netcode.Logging;
12
using UnityEditor;
23
using UnityEditor.Build;
34
using UnityEditor.Build.Reporting;
@@ -19,8 +20,22 @@ internal class SetInScenePlaced : IProcessSceneWithReport
1920
public int callbackOrder => 0;
2021
public void OnProcessScene(Scene scene, BuildReport report)
2122
{
23+
var log = new ContextualLogger();
24+
log.AddInfo(scene.name, scene.handle);
2225
foreach (var networkObject in FindObjects.FromSceneByType<NetworkObject>(scene, true))
2326
{
27+
if (networkObject.SceneOrigin.handle != scene.handle)
28+
{
29+
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));
30+
continue;
31+
}
32+
33+
if (networkObject.HasBeenSpawned)
34+
{
35+
log.Error(new Context(LogLevel.Normal, $"Processing {nameof(NetworkObject)} that has already been spawned! This should not be possible. Skipping processing.").AddNetworkObject(networkObject));
36+
continue;
37+
}
38+
2439
networkObject.InScenePlaced = true;
2540
}
2641
}

com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,22 @@ public bool Validate(int index = -1)
166166
return false;
167167
}
168168

169+
if (networkObject.InScenePlaced)
170+
{
171+
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
172+
{
173+
NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} InScenePlaced {nameof(NetworkObject)} is being registered as a Prefab. This can cause issues!");
174+
}
175+
}
176+
177+
if (networkObject.IsSpawned)
178+
{
179+
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
180+
{
181+
NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} Currently spawned {nameof(NetworkObject)} is being registered as a Prefab. This can cause issues!");
182+
}
183+
}
184+
169185
return true;
170186
}
171187

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ public uint PrefabIdHash
106106
/// </summary>
107107
public NetworkObject CurrentParent { get; private set; }
108108

109+
private int m_SpawnCount;
110+
internal bool HasBeenSpawned => m_SpawnCount > 0;
111+
109112
#if UNITY_EDITOR
110113
private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}";
111114

@@ -1448,12 +1451,16 @@ internal Scene SceneOrigin
14481451

14491452
set
14501453
{
1454+
if (SceneOriginHandle.IsEmpty() && value.IsValid() && value.isLoaded)
1455+
{
1456+
SceneOriginHandle = value.handle;
1457+
}
1458+
14511459
// The scene origin should only be set once.
14521460
// Once set, it should never change.
1453-
if (SceneOriginHandle.IsEmpty() && value.IsValid() && value.isLoaded)
1461+
if (!m_SceneOrigin.IsValid())
14541462
{
14551463
m_SceneOrigin = value;
1456-
SceneOriginHandle = value.handle;
14571464
}
14581465
}
14591466
}
@@ -1782,7 +1789,7 @@ private void OnDestroy()
17821789
}
17831790

17841791
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1785-
internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
1792+
private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject)
17861793
{
17871794
if (NetworkManagerOwner == null)
17881795
{
@@ -1853,7 +1860,28 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla
18531860
}
18541861
}
18551862

1856-
if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), InScenePlaced, playerObject, ownerClientId, destroyWithScene))
1863+
1864+
// Calculate the legacy IsSceneObject value as the public field is obsolete with warning
1865+
// We can't break the public behavior of the field.
1866+
#pragma warning disable CS0618 // Type or member is obsolete
1867+
var legacyIsSceneObject = IsSceneObject.HasValue && IsSceneObject.Value;
1868+
#pragma warning restore CS0618 // Type or member is obsolete
1869+
1870+
// If SpawnInternal is being called on an object that is marked as InScenePlaced,
1871+
// The scene object was never automatically spawned when the scene was loaded.
1872+
// Count this object as a dynamically spawned object.
1873+
// TODO-[MTT-15388]: Actually support disabled/not spawned InScenePlaced NetworkObjects
1874+
if (InScenePlaced && m_SpawnCount == 0)
1875+
{
1876+
if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Developer)
1877+
{
1878+
Debug.LogWarning($"[{name}][SceneOrigin={SceneOriginHandle}] Dynamically spawning InScenePlaced network object. This can cause issues!", this);
1879+
}
1880+
1881+
InScenePlaced = false;
1882+
}
1883+
1884+
if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), legacyIsSceneObject, playerObject, ownerClientId, destroyWithScene))
18571885
{
18581886
if (NetworkManagerOwner.LogLevel <= LogLevel.Normal)
18591887
{
@@ -2053,6 +2081,7 @@ internal void SetupOnSpawn(ulong networkId, bool isPlayerObject, ulong ownerClie
20532081
// When spawned, previous owner is always the first assigned owner
20542082
PreviousOwnerId = ownerClientId;
20552083
m_HasAuthority = NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer;
2084+
m_SpawnCount++;
20562085
IsSpawned = true;
20572086

20582087
// If this is the player, and the client is the owner, then lock ownership by default
@@ -3627,17 +3656,17 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false)
36273656
NetworkSceneHandle = SceneOriginHandle;
36283657
}
36293658
}
3630-
else // Otherwise, the client did not find the client to server scene handle
3631-
if (NetworkManagerOwner.LogLevel <= LogLevel.Developer)
3632-
{
3633-
// There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject
3634-
// into, but that scenario seemed very edge case and under most instances a user should be notified that this
3635-
// server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to
3636-
// the server-side too.
3637-
NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " +
3638-
$"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" +
3639-
$"has no associated server side (network) scene handle!");
3640-
}
3659+
// Otherwise, the client did not find the client to server scene handle
3660+
else if (NetworkManagerOwner.LogLevel <= LogLevel.Developer)
3661+
{
3662+
// There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject
3663+
// into, but that scenario seemed very edge case and under most instances a user should be notified that this
3664+
// server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to
3665+
// the server-side too.
3666+
NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " +
3667+
$"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" +
3668+
$"has no associated server side (network) scene handle!");
3669+
}
36413670
OnMigratedToNewScene?.Invoke();
36423671

36433672
// Only the authority side will notify clients of non-parented NetworkObject scene changes

com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ internal static void ConfigureIntegrationTestLogging(NetworkManager networkManag
103103
/// <param name="message">The message to log</param>
104104
[HideInCallstack]
105105
public static void LogErrorServer(string message) => s_Log.ErrorServer(new Context(LogLevel.Error, message, true));
106+
[HideInCallstack]
107+
internal static void LogErrorServer(Context context) => s_Log.ErrorServer(context);
106108

107109
internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType)
108110
{
@@ -115,14 +117,14 @@ internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType)
115117
};
116118
}
117119

118-
119120
private const string k_SenderId = "SenderId";
120121
internal static Context BuildContextForServerMessage([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message)
121122
{
122-
var ctx = new Context(level, message, true).AddInfo(k_SenderId, senderId);
123-
if (TryGetNetworkObjectName(networkManager, message, out var name))
123+
var ctx = new Context(level, message, true).AddTag("Received log from client!").AddInfo(k_SenderId, senderId);
124+
var networkObject = TryGetNetworkObject(networkManager, message);
125+
if (networkObject != null)
124126
{
125-
ctx.AddTag(name);
127+
ctx.AddNetworkObject(networkObject);
126128
}
127129
return ctx;
128130
}
@@ -135,29 +137,41 @@ internal enum LogType : byte
135137
None
136138
}
137139

140+
private static readonly Regex k_NetworkObjectId = new($@"\[{nameof(NetworkObject.NetworkObjectId)}=(\d+)\]", RegexOptions.Compiled);
138141
private static readonly Regex k_GlobalObjectIdHash = new($@"\[{nameof(NetworkObject.GlobalObjectIdHash)}=(\d+)\]", RegexOptions.Compiled);
139142
[MethodImpl(MethodImplOptions.AggressiveInlining)]
140-
private static bool TryGetNetworkObjectName([NotNull] NetworkManager networkManager, string message, out string name)
143+
private static NetworkObject TryGetNetworkObject([NotNull] NetworkManager networkManager, string message)
141144
{
142-
name = null;
145+
if (k_NetworkObjectId.IsMatch(message))
146+
{
147+
var stringId = k_NetworkObjectId.Match(message).Groups[1].Value;
148+
if (ulong.TryParse(stringId, out var networkObjectId) && networkObjectId > 0 && networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject))
149+
{
150+
return networkObject;
151+
}
152+
}
153+
143154
if (!k_GlobalObjectIdHash.IsMatch(message))
144155
{
145-
return false;
156+
return null;
146157
}
147158

148159
var stringHash = k_GlobalObjectIdHash.Match(message).Groups[1].Value;
149160
if (!ulong.TryParse(stringHash, out var globalObjectIdHash))
150161
{
151-
return false;
162+
return null;
152163
}
153164

154-
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(globalObjectIdHash, out var networkObject))
165+
NetworkObject matchingObject = null;
166+
foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList)
155167
{
156-
return false;
168+
if (networkObject.GlobalObjectIdHash == globalObjectIdHash)
169+
{
170+
matchingObject = networkObject;
171+
}
157172
}
158173

159-
name = networkObject.name;
160-
return true;
174+
return matchingObject;
161175
}
162176

163177
[HideInCallstack]

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,7 @@ internal NetworkObject InstantiateNetworkPrefab([NotNull] GameObject networkPref
957957
/// <remarks>
958958
/// For most cases this is client-side only, except when the server is spawning a player.
959959
/// </remarks>
960+
[return: MaybeNull]
960961
internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject serializedObject, byte[] instantiationData = null)
961962
{
962963
NetworkObject networkObject = null;
@@ -977,11 +978,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject s
977978
networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, serializedObject.NetworkSceneHandle);
978979
if (networkObject == null)
979980
{
980-
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
981-
{
982-
NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
983-
}
984-
981+
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));
985982
return null;
986983
}
987984

@@ -1122,7 +1119,14 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n
11221119
return false;
11231120
}
11241121

1125-
if (!sceneObject && NetworkManager.LogLevel <= LogLevel.Error)
1122+
if (playerObject && networkObject.InScenePlaced)
1123+
{
1124+
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));
1125+
networkObject.InScenePlaced = false;
1126+
}
1127+
NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value.");
1128+
1129+
if (!networkObject.InScenePlaced && NetworkManager.LogLevel <= LogLevel.Error)
11261130
{
11271131
var networkObjectChildren = networkObject.GetComponentsInChildren<NetworkObject>();
11281132
if (networkObjectChildren.Length > 1)
@@ -1137,7 +1141,7 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n
11371141
networkObject.NetworkManagerOwner = NetworkManager;
11381142
networkObject.InvokeBehaviourNetworkPreSpawn();
11391143

1140-
if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && sceneObject)
1144+
if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && networkObject.InScenePlaced)
11411145
{
11421146
networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle;
11431147
networkObject.NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle];
@@ -1203,6 +1207,7 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize
12031207
return false;
12041208
}
12051209

1210+
networkObject.InScenePlaced = serializedObject.IsSceneObject;
12061211
networkObject.NetworkManagerOwner = NetworkManager;
12071212

12081213
// This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning

testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,21 @@ MonoBehaviour:
1313
m_Name: Default Local Group
1414
m_EditorClassIdentifier:
1515
m_GroupName: Default Local Group
16-
m_Data:
17-
m_SerializedData: []
1816
m_GUID: 93aa504d1b753cb41a8a779ae63f5795
1917
m_SerializeEntries:
18+
- m_GUID: 68d21678646384e6291bb2b568b5d95c
19+
m_Address: Assets/Scenes/AddressableInSceneObject.unity
20+
m_ReadOnly: 0
21+
m_SerializedLabels: []
22+
FlaggedDuringContentUpdateRestriction: 0
2023
- m_GUID: ffa1ab8ed58b72343ad93116ded1700a
2124
m_Address: AddressableTestObject.prefab
2225
m_ReadOnly: 0
2326
m_SerializedLabels: []
24-
m_MainAsset: {fileID: 0}
25-
m_TargetAsset: {fileID: 0}
27+
FlaggedDuringContentUpdateRestriction: 0
2628
m_ReadOnly: 0
2729
m_Settings: {fileID: 11400000, guid: 75e5cd8b6bfca5d49a5818e70d71b64d, type: 2}
2830
m_SchemaSet:
2931
m_Schemas:
30-
- {fileID: 11400000, guid: 6b8a226e7c996ad4b841b997672f866c, type: 2}
3132
- {fileID: 11400000, guid: 07e6ff907f5779a42b6e632340d10c58, type: 2}
33+
- {fileID: 11400000, guid: 6b8a226e7c996ad4b841b997672f866c, type: 2}

testproject/Assets/Prefabs/AddressableTestObject.prefab

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ GameObject:
99
serializedVersion: 6
1010
m_Component:
1111
- component: {fileID: 6175900562428407388}
12-
- component: {fileID: 6175900562428407389}
1312
- component: {fileID: 6175900562428407386}
13+
- component: {fileID: 6175900562428407389}
1414
m_Layer: 0
1515
m_Name: AddressableTestObject
1616
m_TagString: Untagged
@@ -25,14 +25,15 @@ Transform:
2525
m_PrefabInstance: {fileID: 0}
2626
m_PrefabAsset: {fileID: 0}
2727
m_GameObject: {fileID: 6175900562428407387}
28+
serializedVersion: 2
2829
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
2930
m_LocalPosition: {x: 0, y: 0, z: 0}
3031
m_LocalScale: {x: 1, y: 1, z: 1}
32+
m_ConstrainProportionsScale: 0
3133
m_Children: []
3234
m_Father: {fileID: 0}
33-
m_RootOrder: 0
3435
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
35-
--- !u!114 &6175900562428407389
36+
--- !u!114 &6175900562428407386
3637
MonoBehaviour:
3738
m_ObjectHideFlags: 0
3839
m_CorrespondingSourceObject: {fileID: 0}
@@ -41,12 +42,24 @@ MonoBehaviour:
4142
m_GameObject: {fileID: 6175900562428407387}
4243
m_Enabled: 1
4344
m_EditorHideFlags: 0
44-
m_Script: {fileID: 11500000, guid: 6c8e5a8a448245769b69af7de9c47b21, type: 3}
45+
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
4546
m_Name:
4647
m_EditorClassIdentifier:
47-
AnIntVal: 1234567
48-
AStringVal: 1234567
49-
--- !u!114 &6175900562428407386
48+
GlobalObjectIdHash: 4054942115
49+
InScenePlacedSourceGlobalObjectIdHash: 0
50+
DeferredDespawnTick: 0
51+
Ownership: 1
52+
AlwaysReplicateAsRoot: 0
53+
SynchronizeTransform: 1
54+
<InScenePlaced>k__BackingField: 0
55+
ActiveSceneSynchronization: 0
56+
SceneMigrationSynchronization: 1
57+
SpawnWithObservers: 1
58+
DontDestroyWithOwner: 0
59+
AutoObjectParentSync: 1
60+
SyncOwnerTransformWhenParented: 1
61+
AllowOwnerToParent: 0
62+
--- !u!114 &6175900562428407389
5063
MonoBehaviour:
5164
m_ObjectHideFlags: 0
5265
m_CorrespondingSourceObject: {fileID: 0}
@@ -55,10 +68,9 @@ MonoBehaviour:
5568
m_GameObject: {fileID: 6175900562428407387}
5669
m_Enabled: 1
5770
m_EditorHideFlags: 0
58-
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
71+
m_Script: {fileID: 11500000, guid: 6c8e5a8a448245769b69af7de9c47b21, type: 3}
5972
m_Name:
6073
m_EditorClassIdentifier:
61-
GlobalObjectIdHash: 951099334
62-
AlwaysReplicateAsRoot: 0
63-
DontDestroyWithOwner: 0
64-
AutoObjectParentSync: 1
74+
ShowTopMostFoldoutHeaderGroup: 1
75+
AnIntVal: 1234567
76+
AStringVal: 1234567

0 commit comments

Comments
 (0)