Skip to content

Commit f051078

Browse files
authored
Merge branch 'develop-2.0.0' into experimental/logger
2 parents 522e0e9 + 8d555b4 commit f051078

12 files changed

Lines changed: 646 additions & 42 deletions

File tree

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
2929

3030
### Fixed
3131

32+
- Fixed issue where either an `AttachableBehaviour` or an `AttachableNode` can throw an exception if they are attached during a scene unload where one of the two persists the scene unload event and the other does not. (#3931)
3233
- Fixed issue where attempts to use `NetworkLog` when there is no `NetworkManager` instance would result in an exception. (#3917)
3334

3435
### Security

com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableBehaviour.cs

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -261,23 +261,30 @@ internal void ForceDetach()
261261
ForceComponentChange(false, true);
262262

263263
InternalDetach();
264-
// Notify of the changed attached state
265-
NotifyAttachedStateChanged(m_AttachState, m_AttachableNode);
266-
267-
m_AttachedNodeReference = new NetworkBehaviourReference(null);
268264

269-
// When detaching, we want to make our final action
270-
// the invocation of the AttachableNode's Detach method.
271-
if (m_AttachableNode)
265+
if (m_AttachableNode != null && !m_AttachableNode.IsDestroying)
272266
{
267+
// Notify of the changed attached state
268+
NotifyAttachedStateChanged(m_AttachState, m_AttachableNode);
269+
// Only notify of the detach if the node is still valid.
273270
m_AttachableNode.Detach(this);
274-
m_AttachableNode = null;
275271
}
272+
273+
m_AttachedNodeReference = new NetworkBehaviourReference(null);
274+
m_AttachableNode = null;
276275
}
277276

278277
/// <inheritdoc/>
279278
public override void OnNetworkPreDespawn()
280279
{
280+
// If the NetworkObject is being destroyed and not completely detached, then destroy the GameObject for
281+
// this attachable since the associated default parent is being destroyed.
282+
if (IsDestroying && m_AttachState != AttachState.Detached)
283+
{
284+
Destroy(gameObject);
285+
return;
286+
}
287+
281288
if (NetworkManager.ShutdownInProgress || AutoDetach.HasFlag(AutoDetachTypes.OnDespawn))
282289
{
283290
ForceDetach();
@@ -286,7 +293,7 @@ public override void OnNetworkPreDespawn()
286293
}
287294

288295
/// <summary>
289-
/// This will apply the final attach or detatch state based on the current value of <see cref="m_AttachedNodeReference"/>.
296+
/// This will apply the final attach or detach state based on the current value of <see cref="m_AttachedNodeReference"/>.
290297
/// </summary>
291298
[MethodImpl(MethodImplOptions.AggressiveInlining)]
292299
private void UpdateAttachedState()
@@ -304,16 +311,18 @@ private void UpdateAttachedState()
304311
return;
305312
}
306313

307-
// If we are attached to some other AttachableNode, then detach from that before attaching to a new one.
314+
// If we are attaching and already attached to some other AttachableNode,
315+
// then detach from that before attaching to a new one.
308316
if (isAttaching && m_AttachableNode != null && m_AttachState == AttachState.Attached)
309317
{
310-
// Run through the same process without being triggerd by a NetVar update.
318+
// Detach the current attachable
311319
NotifyAttachedStateChanged(AttachState.Detaching, m_AttachableNode);
312320
InternalDetach();
313321
NotifyAttachedStateChanged(AttachState.Detached, m_AttachableNode);
314-
315322
m_AttachableNode.Detach(this);
316323
m_AttachableNode = null;
324+
325+
// Now attach the new attachable
317326
}
318327

319328
// Change the state to attaching or detaching
@@ -392,7 +401,8 @@ internal void ForceComponentChange(bool isAttaching, bool forcedChange)
392401

393402
foreach (var componentControllerEntry in ComponentControllers)
394403
{
395-
if (componentControllerEntry.AutoTrigger.HasFlag(triggerType))
404+
// Only if the component controller still exists and has the appropriate flag.
405+
if (componentControllerEntry.ComponentController && componentControllerEntry.AutoTrigger.HasFlag(triggerType))
396406
{
397407
componentControllerEntry.ComponentController.ForceChangeEnabled(componentControllerEntry.EnableOnAttach ? isAttaching : !isAttaching, forcedChange);
398408
}
@@ -457,7 +467,9 @@ public void Attach(AttachableNode attachableNode)
457467
/// </summary>
458468
internal void InternalDetach()
459469
{
460-
if (m_AttachableNode)
470+
// If this instance is not in the middle of being destroyed, the attachable node is not null, and the node is not destroying
471+
// =or= the scene it is located in is in the middle of being unloaded, then re-parent under the default parent.
472+
if (!IsDestroying && m_AttachableNode && (!m_AttachableNode.IsDestroying || m_AttachableNode.gameObject.scene.isLoaded))
461473
{
462474
if (m_DefaultParent)
463475
{
@@ -553,12 +565,33 @@ private void UpdateAttachStateRpc(NetworkBehaviourReference attachedNodeReferenc
553565
/// </summary>
554566
internal void OnAttachNodeDestroy()
555567
{
556-
// If this instance should force a detach on destroy
557-
if (AutoDetach.HasFlag(AutoDetachTypes.OnAttachNodeDestroy))
568+
// We force a detach on destroy if there is a flag =or= if we are attached to a node that is being destroyed.
569+
if (AutoDetach.HasFlag(AutoDetachTypes.OnAttachNodeDestroy) ||
570+
(AutoDetach.HasFlag(AutoDetachTypes.OnDespawn) && m_AttachState == AttachState.Attached && m_AttachableNode && m_AttachableNode.IsDestroying))
571+
{
572+
ForceDetach();
573+
}
574+
}
575+
576+
577+
/// <summary>
578+
/// When we know this instance is being destroyed or will be destroyed
579+
/// by something outside of NGO's realm of control, this gets invoked.
580+
/// We should detach from any AttachableNode when this is invoked.
581+
/// </summary>
582+
protected internal override void OnIsDestroying()
583+
{
584+
// If we are not already marked as being destroyed, attached, this instance is the authority instance, and the node we are attached
585+
// to is not in the middle of being destroyed...detach normally.
586+
if (!IsDestroying && HasAuthority && m_AttachState == AttachState.Attached && m_AttachableNode && !m_AttachableNode.IsDestroying)
587+
{
588+
Detach();
589+
}
590+
else // Otherwise force the detach.
558591
{
559-
// Force a detach
560592
ForceDetach();
561593
}
594+
base.OnIsDestroying();
562595
}
563596
}
564597
}

com.unity.netcode.gameobjects/Runtime/Components/Helpers/AttachableNode.cs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,20 @@ public override void OnNetworkPreDespawn()
7171
{
7272
for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--)
7373
{
74-
if (!m_AttachedBehaviours[i])
74+
var attachable = m_AttachedBehaviours[i];
75+
if (!attachable)
7576
{
7677
continue;
7778
}
78-
// If we don't have authority but should detach on despawn,
79-
// then proceed to detach.
80-
if (!m_AttachedBehaviours[i].HasAuthority)
79+
80+
if (attachable.HasAuthority && attachable.IsSpawned)
8181
{
82-
m_AttachedBehaviours[i].ForceDetach();
82+
// Detach the normal way with authority
83+
attachable.Detach();
8384
}
84-
else
85+
else if (!attachable.HasAuthority || !attachable.IsDestroying)
8586
{
86-
// Detach the normal way with authority
87-
m_AttachedBehaviours[i].Detach();
87+
attachable.ForceDetach();
8888
}
8989
}
9090
}
@@ -93,12 +93,10 @@ public override void OnNetworkPreDespawn()
9393

9494
internal override void InternalOnDestroy()
9595
{
96-
// Notify any attached behaviours that this node is being destroyed.
97-
for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--)
96+
if (m_AttachedBehaviours.Count > 0)
9897
{
99-
m_AttachedBehaviours[i]?.OnAttachNodeDestroy();
98+
OnIsDestroying();
10099
}
101-
m_AttachedBehaviours.Clear();
102100
base.InternalOnDestroy();
103101
}
104102

@@ -141,4 +139,21 @@ internal void Detach(AttachableBehaviour attachableBehaviour)
141139
m_AttachedBehaviours.Remove(attachableBehaviour);
142140
OnDetached(attachableBehaviour);
143141
}
142+
143+
/// <summary>
144+
/// When we know this instance is being destroyed or will be destroyed
145+
/// by something outside of NGO's realm of control, this gets invoked.
146+
/// We should notify any attached AttachableBehaviour that this node
147+
/// is being destroyed.
148+
/// </summary>
149+
protected internal override void OnIsDestroying()
150+
{
151+
// Notify any attached behaviours that this node is being destroyed.
152+
for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--)
153+
{
154+
m_AttachedBehaviours[i]?.OnAttachNodeDestroy();
155+
}
156+
m_AttachedBehaviours.Clear();
157+
base.OnIsDestroying();
158+
}
144159
}

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,42 @@ protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId)
643643
/// </summary>
644644
public ulong OwnerClientId { get; internal set; }
645645

646+
/// <summary>
647+
/// Returns true if the NetworkObject is in the middle of being destroyed.
648+
/// </summary>
649+
/// <remarks>
650+
/// <see cref="SetIsDestroying"/>
651+
/// </remarks>
652+
internal bool IsDestroying { get; private set; }
653+
654+
/// <summary>
655+
/// This provides us with a way to track when something is in the middle
656+
/// of being destroyed or will be destroyed by something like SceneManager.
657+
/// </summary>
658+
protected internal virtual void OnIsDestroying()
659+
{
660+
}
661+
662+
/// <summary>
663+
/// Invoked by <see cref="NetworkObject.SetIsDestroying"/>.
664+
/// </summary>
665+
/// <remarks>
666+
/// We want to invoke the virtual method prior to setting the
667+
/// IsDestroying flag to be able to distinguish between knowing
668+
/// when something will be destroyed (i.e. scene manager unload
669+
/// or load in single mode) or is in the middle of being
670+
/// destroyed.
671+
/// Setting the flag provides a way for other instances or internals
672+
/// to determine if this <see cref="NetworkBehaviour"/> instance is
673+
/// in the middle of being destroyed.
674+
/// </remarks>
675+
internal void SetIsDestroying()
676+
{
677+
// We intentionally invoke this before setting the IsDestroying flag.
678+
OnIsDestroying();
679+
IsDestroying = true;
680+
}
681+
646682
/// <summary>
647683
/// Updates properties with network session related
648684
/// dependencies such as a NetworkObject's spawned

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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,8 +1688,52 @@ public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientI
16881688
}
16891689
}
16901690

1691+
/// <summary>
1692+
/// Returns true if the NetworkObject is in the middle of being destroyed.
1693+
/// </summary>
1694+
/// <remarks>
1695+
/// This is particularly useful when determining if something is being de-spawned
1696+
/// normally or if it is being de-spawned because the NetworkObject/GameObject is
1697+
/// being destroyed.
1698+
/// </remarks>
1699+
internal bool IsDestroying { get; private set; }
1700+
1701+
/// <summary>
1702+
/// Applies the despawning flag for the local instance and
1703+
/// its child NetworkBehaviours. Private to assure this is
1704+
/// only invoked from within OnDestroy.
1705+
/// </summary>
1706+
internal void SetIsDestroying()
1707+
{
1708+
if (IsDestroying)
1709+
{
1710+
return;
1711+
}
1712+
1713+
if (m_ChildNetworkBehaviours != null)
1714+
{
1715+
foreach (var childBehaviour in m_ChildNetworkBehaviours)
1716+
{
1717+
// Just ignore and continue processing through the entries
1718+
if (!childBehaviour)
1719+
{
1720+
continue;
1721+
}
1722+
1723+
// Keeping the property a private set to assure this is
1724+
// the only way it can be set as it should never be reset
1725+
// back to false once invoked.
1726+
childBehaviour.SetIsDestroying();
1727+
}
1728+
}
1729+
IsDestroying = true;
1730+
}
1731+
16911732
private void OnDestroy()
16921733
{
1734+
// Apply the is destroying flag
1735+
SetIsDestroying();
1736+
16931737
var networkManager = NetworkManager;
16941738
// If no NetworkManager is assigned, then just exit early
16951739
if (!networkManager)

com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,19 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa
318318
}
319319
else if (networkObject.HasAuthority)
320320
{
321+
// We know this instance is going to be destroyed (when it receives the destroy object message).
322+
// We have to invoke this prior to invoking despawn in order to know that we are de-spawning in
323+
// preparation of being destroyed by the SceneManager.
324+
networkObject.SetIsDestroying();
325+
// This sends a de-spawn message prior to the scene event.
321326
networkObject.Despawn();
322327
}
323328
else // We are a client, migrate the object into the DDOL temporarily until it receives the destroy command from the server
324329
{
330+
// We know this instance is going to be destroyed (when it receives the destroy object message).
331+
// We have to invoke this prior to invoking despawn in order to know that we are de-spawning in
332+
// preparation of being destroyed by the SceneManager.
333+
networkObject.SetIsDestroying();
325334
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
326335
}
327336
}

com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2720,7 +2720,11 @@ internal void MoveObjectsToDontDestroyOnLoad()
27202720
}
27212721
else if (networkObject.HasAuthority)
27222722
{
2723-
networkObject.Despawn();
2723+
networkObject.SetIsDestroying();
2724+
var isSceneObject = networkObject.IsSceneObject;
2725+
// Only destroy non-scene placed NetworkObjects to avoid warnings about destroying in-scene placed NetworkObjects.
2726+
// (MoveObjectsToDontDestroyOnLoad is only invoked during a scene event type of load and the load scene mode is single)
2727+
networkObject.Despawn(isSceneObject.HasValue && isSceneObject.Value == false);
27242728
}
27252729
}
27262730
}

0 commit comments

Comments
 (0)