Skip to content

Commit 6289aba

Browse files
fix
- Issue where disabled in-scene placed NetworkObjects could not be spawned after starting a session. - Issue where users are allowed to spawn a NetworkObject with a GlobalObjectIdHash value of 0 (zero).
1 parent 2f1a459 commit 6289aba

4 files changed

Lines changed: 158 additions & 34 deletions

File tree

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ public void OnProcessScene(Scene scene, BuildReport report)
2424
log.AddInfo(scene.name, scene.handle);
2525
foreach (var networkObject in FindObjects.FromSceneByType<NetworkObject>(scene, true))
2626
{
27+
// Trap for users just creating things during runtime where this will be zero.
28+
if (networkObject.GlobalObjectIdHash == 0)
29+
{
30+
log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s GlobalObjectIdHash value is zero! Runtime creating of {nameof(NetworkObject)}s is not supported. Skipping processing.").AddNetworkObject(networkObject));
31+
continue;
32+
}
2733
if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle)
2834
{
2935
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));
@@ -36,7 +42,15 @@ public void OnProcessScene(Scene scene, BuildReport report)
3642
continue;
3743
}
3844

45+
// If already marked, the do nothing.
46+
if (networkObject.InScenePlaced)
47+
{
48+
continue;
49+
}
50+
3951
networkObject.InScenePlaced = true;
52+
// Will not be true when making a build and the values are serialized.
53+
networkObject.InScenePlacedPostProcessorMarkedDuringRuntime = Application.isPlaying;
4054
}
4155
}
4256
}

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

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,81 @@ public class NetworkPrefabs
4747
[NonSerialized]
4848
private List<NetworkPrefab> m_Prefabs = new List<NetworkPrefab>();
4949

50+
[NonSerialized]
51+
private Dictionary<uint, NetworkPrefab> m_PrefabHashIds = new Dictionary<uint, NetworkPrefab>();
52+
5053
[NonSerialized]
5154
private List<NetworkPrefab> m_RuntimeAddedPrefabs = new List<NetworkPrefab>();
5255

53-
private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
56+
private bool InternalAddPrefab(NetworkPrefab networkPrefab)
5457
{
5558
if (AddPrefabRegistration(networkPrefab))
5659
{
5760
// Don't add this to m_RuntimeAddedPrefabs
5861
// This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there.
5962
m_Prefabs.Add(networkPrefab);
63+
64+
// We are not getting all potential overrides but just determining if the prefab has been registered.
65+
if (!m_PrefabHashIds.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
66+
{
67+
m_PrefabHashIds.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab);
68+
}
69+
if (!m_PrefabHashIds.ContainsKey(networkPrefab.TargetPrefabGlobalObjectIdHash))
70+
{
71+
m_PrefabHashIds.Add(networkPrefab.TargetPrefabGlobalObjectIdHash, networkPrefab);
72+
}
73+
return true;
6074
}
75+
return false;
6176
}
6277

63-
private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
78+
private void InternalRemovePrefab(NetworkPrefab networkPrefab)
6479
{
6580
m_Prefabs.Remove(networkPrefab);
81+
m_PrefabHashIds.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash);
82+
}
83+
84+
internal bool IsBasedOnRegisteredPrefab(NetworkObject networkObject)
85+
{
86+
87+
88+
return m_PrefabHashIds.ContainsKey(networkObject.GlobalObjectIdHash);
89+
}
90+
91+
internal bool IsActualPrefabAsset(NetworkObject networkObject)
92+
{
93+
var isActualPrefabAsset = false;
94+
if (m_PrefabHashIds.TryGetValue(networkObject.GlobalObjectIdHash, out NetworkPrefab networkPrefab))
95+
{
96+
switch(networkPrefab.Override)
97+
{
98+
case NetworkPrefabOverride.Prefab:
99+
case NetworkPrefabOverride.None:
100+
{
101+
isActualPrefabAsset = networkPrefab.Prefab != null && networkObject.gameObject == networkPrefab.Prefab;
102+
break;
103+
}
104+
case NetworkPrefabOverride.Hash:
105+
{
106+
isActualPrefabAsset = networkPrefab.SourceHashToOverride == networkObject.GlobalObjectIdHash;
107+
break;
108+
}
109+
}
110+
}
111+
return isActualPrefabAsset;
112+
}
113+
114+
private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
115+
{
116+
// Don't add this to m_RuntimeAddedPrefabs
117+
// This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there.
118+
InternalAddPrefab(networkPrefab);
119+
// Log warning if this returns false?
120+
}
121+
122+
private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
123+
{
124+
InternalRemovePrefab(networkPrefab);
66125
}
67126

68127
/// <summary>
@@ -93,6 +152,7 @@ internal void Shutdown()
93152
/// <param name="warnInvalid">When true, logs warnings about invalid prefabs that are removed during initialization</param>
94153
public void Initialize(bool warnInvalid = true)
95154
{
155+
m_PrefabHashIds.Clear();
96156
m_Prefabs.Clear();
97157
NetworkPrefabsLists.RemoveAll(x => x == null);
98158
foreach (var list in NetworkPrefabsLists)
@@ -113,7 +173,7 @@ public void Initialize(bool warnInvalid = true)
113173
prefabs.AddRange(list.PrefabList);
114174
}
115175
}
116-
176+
m_PrefabHashIds = new Dictionary<uint, NetworkPrefab>();
117177
m_Prefabs = new List<NetworkPrefab>();
118178

119179
List<NetworkPrefab> removeList = null;
@@ -124,23 +184,15 @@ public void Initialize(bool warnInvalid = true)
124184

125185
foreach (var networkPrefab in prefabs)
126186
{
127-
if (AddPrefabRegistration(networkPrefab))
128-
{
129-
m_Prefabs.Add(networkPrefab);
130-
}
131-
else
187+
if (!InternalAddPrefab(networkPrefab))
132188
{
133189
removeList?.Add(networkPrefab);
134190
}
135191
}
136192

137193
foreach (var networkPrefab in m_RuntimeAddedPrefabs)
138194
{
139-
if (AddPrefabRegistration(networkPrefab))
140-
{
141-
m_Prefabs.Add(networkPrefab);
142-
}
143-
else
195+
if (!InternalAddPrefab(networkPrefab))
144196
{
145197
removeList?.Add(networkPrefab);
146198
}
@@ -171,14 +223,12 @@ public void Initialize(bool warnInvalid = true)
171223
/// </remarks>
172224
public bool Add(NetworkPrefab networkPrefab)
173225
{
174-
if (AddPrefabRegistration(networkPrefab))
226+
var added = InternalAddPrefab(networkPrefab);
227+
if (added)
175228
{
176-
m_Prefabs.Add(networkPrefab);
177229
m_RuntimeAddedPrefabs.Add(networkPrefab);
178-
return true;
179230
}
180-
181-
return false;
231+
return added;
182232
}
183233

184234
/// <summary>
@@ -197,8 +247,7 @@ public void Remove(NetworkPrefab prefab)
197247
{
198248
throw new ArgumentNullException(nameof(prefab));
199249
}
200-
201-
m_Prefabs.Remove(prefab);
250+
InternalRemovePrefab(prefab);
202251
m_RuntimeAddedPrefabs.Remove(prefab);
203252
OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash);
204253
NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash);
@@ -294,14 +343,12 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab)
294343

295344
uint source = networkPrefab.SourcePrefabGlobalObjectIdHash;
296345
uint target = networkPrefab.TargetPrefabGlobalObjectIdHash;
297-
298346
// Make sure the prefab isn't already registered.
299347
if (NetworkPrefabOverrideLinks.ContainsKey(source))
300348
{
301-
var networkObject = networkPrefab.Prefab.GetComponent<NetworkObject>();
302-
349+
var nameOrHashOverride = networkPrefab.Override == NetworkPrefabOverride.Hash ? $"Hash: {networkPrefab.SourcePrefabGlobalObjectIdHash}" : networkPrefab.Prefab?.name;
303350
// This should never happen, but in the case it somehow does log an error and remove the duplicate entry
304-
Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!");
351+
Debug.LogError($"{nameof(NetworkPrefab)} ({nameOrHashOverride}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!");
305352
return false;
306353
}
307354

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,13 @@ internal set
12581258
}
12591259
}
12601260

1261+
/// <summary>
1262+
/// This provides a means to determine if the post processing had applied
1263+
/// the in-scene placed status or if it was already serialized. This is used
1264+
/// when determining if the thing being spawned is a valid thing to spawn.
1265+
/// </summary>
1266+
internal bool InScenePlacedPostProcessorMarkedDuringRuntime;
1267+
12611268
/// <summary>
12621269
/// Sets whether this NetworkObject was instantiated as part of a scene
12631270
/// </summary>
@@ -1877,18 +1884,27 @@ private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool play
18771884
}
18781885
}
18791886

1887+
// Trap for runtime generated instances as this is not valid
1888+
if (GlobalObjectIdHash == 0)
1889+
{
1890+
NetworkManager.Log.ErrorServer(new Context(LogLevel.Error, $"{name} has a {nameof(GlobalObjectIdHash)} value of {GlobalObjectIdHash}(zero)!" +
1891+
$"This is typically a sign of runtime generated {nameof(NetworkObject)}s which is not supported."));
1892+
return;
1893+
}
18801894

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

1887-
// If SpawnInternal is being called on an object that is marked as InScenePlaced,
1888-
// The scene object was never automatically spawned when the scene was loaded.
1889-
// Count this object as a dynamically spawned object.
1890-
// TODO-[MTT-15388]: Actually support disabled/not spawned InScenePlaced NetworkObjects
1891-
if (InScenePlaced && !HasBeenSpawned)
1901+
// If the initial state of the GameObject was disabled and InScenePlaced is marked,
1902+
// then spawn it as in-scene placed.[MTT-15388]
1903+
// Otherwise:
1904+
// If we are marked as in-scene place, have never been spawned, and the root GameObject
1905+
// was not disabled upon being instantiated, then treat this as a dynamically spawned
1906+
// instance.
1907+
if (InScenePlaced && !m_GameObjectWasDisabledWhenInstantiated && !HasBeenSpawned)
18921908
{
18931909
if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Developer)
18941910
{
@@ -3701,10 +3717,13 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false)
37013717
}
37023718
}
37033719

3720+
private bool m_GameObjectWasDisabledWhenInstantiated;
3721+
37043722
private void Awake()
37053723
{
37063724
SetCachedParent(transform.parent);
37073725
SceneOrigin = gameObject.scene;
3726+
m_GameObjectWasDisabledWhenInstantiated = gameObject.activeInHierarchy;
37083727
}
37093728

37103729
/// <summary>

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

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,9 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n
11211121
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));
11221122
networkObject.InScenePlaced = false;
11231123
}
1124-
NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value.");
1124+
// This logic is no longer true with the adjustments to spawn pre-disabled in-scene placed NetworkObjects.
1125+
// Leaving this for reference purposes.
1126+
//NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value.");
11251127

11261128
if (!networkObject.InScenePlaced && NetworkManager.LogLevel <= LogLevel.Error)
11271129
{
@@ -1580,10 +1582,52 @@ internal void ServerSpawnSceneObjectsOnStartSweep()
15801582
continue;
15811583
}
15821584

1583-
// This used to be two loops.
1584-
// The first added all NetworkObjects to a list and the second spawned all NetworkObjects in the list.
1585-
// Now, a parent will set its children's IsSceneObject value when spawned, so we check for null or for true.
1586-
if (networkObject.InScenePlaced)
1585+
// Do not attempt to spawn if it is the actual prefab asset itself:
1586+
// - This is not supported by NGO.
1587+
// - This will lead to other issues if it gets destroyed, when de-spawned, but the prefab is still registered.
1588+
// - This also prevents from spawning integration test prefabs.
1589+
if (NetworkManager.NetworkConfig.Prefabs.IsActualPrefabAsset(networkObject))
1590+
{
1591+
NetworkManager.Log.Warning(new Context(LogLevel.Developer, $"Skipping {networkObject.name} as it is the actual prefab asset itself!"));
1592+
continue;
1593+
}
1594+
1595+
// Determine if this is even a valid thing to spawn:
1596+
// - If it is not based on a registered prefab, it is invalid.
1597+
// - If the GlobalObjectIdHash is zero, it is invalid.
1598+
var isInvalidInstanceToSpawn = !NetworkManager.NetworkConfig.Prefabs.IsBasedOnRegisteredPrefab(networkObject) || networkObject.GlobalObjectIdHash == 0;
1599+
1600+
// If we are a valid prefab asset, marked as in-scene placed, but this was marked during runtime by the post processor.
1601+
if (!isInvalidInstanceToSpawn && networkObject.InScenePlaced && networkObject.InScenePlacedPostProcessorMarkedDuringRuntime)
1602+
{
1603+
// Then it is not in-scene placed and was pre-instantiated. Spawn dynamically.
1604+
networkObject.InScenePlaced = false;
1605+
}
1606+
else if(networkObject.InScenePlaced && !networkObject.InScenePlacedPostProcessorMarkedDuringRuntime)
1607+
{
1608+
// If this was marked as in-scene placed within the editor, then it is valid.
1609+
isInvalidInstanceToSpawn = false;
1610+
}
1611+
1612+
var wasPreInstantiated = !networkObject.IsSpawned && !networkObject.InScenePlaced;
1613+
1614+
// Dynamically created NetworkObjects instances are not supported and will not be spawned during the sweep.
1615+
if (wasPreInstantiated && isInvalidInstanceToSpawn)
1616+
{
1617+
// If this isn't the original prefab asset being skipped over (integration test would be a good example), then log the error.
1618+
if (!NetworkManager.NetworkConfig.Prefabs.IsActualPrefabAsset(networkObject))
1619+
{
1620+
NetworkManager.Log.Error(new Context(LogLevel.Error, $"{networkObject.name} appears to be a pre-instantiated {nameof(GameObject)} " +
1621+
$"with a {nameof(NetworkObject)} component instance that is not a registered prefab nor is it an in-scene placed {nameof(NetworkObject)}." +
1622+
$" Dynamically creating unregistered {nameof(NetworkObject)}s is not supported! {networkObject.name} will not be spawned."));
1623+
}
1624+
continue;
1625+
}
1626+
1627+
// The only valid things to spawn during the sweep are:
1628+
// - In-scene placed NetworkObjects.
1629+
// - Pre-instantiated NetworkObjects that are registered with the NetworkManager's network prefab list(s).
1630+
if (networkObject.InScenePlaced || wasPreInstantiated)
15871631
{
15881632
var ownerId = networkObject.OwnerClientId;
15891633
if (NetworkManager.DistributedAuthorityMode)

0 commit comments

Comments
 (0)