Skip to content

Commit 4b4c8ca

Browse files
update
Several improvements on the attach and detach processing. Added the ability to tie ComponentControllers to AttachableBehaviours in order to auto-notify when something is attaching and detaching. Several adjustments to fix the issue with ungraceful disconnects and re-synchronizing attachables. Added forced change based on flags applied, things like when an AttachableNode is despawning, changing ownership, or being destroyed then local instances, whether authority or not, will all force the attach or detach state. Added an internal virtual destroy method on NetworkBehaviour to allow for helper components to assure on destroy script is invoked.
1 parent fb73b8c commit 4b4c8ca

File tree

5 files changed

+386
-184
lines changed

5 files changed

+386
-184
lines changed

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

Lines changed: 169 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
24
#if UNITY_EDITOR
35
using UnityEditor;
46
#endif
@@ -25,6 +27,49 @@ namespace Unity.Netcode.Components
2527
/// </remarks>
2628
public class AttachableBehaviour : NetworkBehaviour
2729
{
30+
[Serializable]
31+
internal class ComponentControllerEntry
32+
{
33+
// Ignoring the naming convention in order to auto-assign element names
34+
#pragma warning disable IDE1006
35+
/// <summary>
36+
/// Used for naming each element entry.
37+
/// </summary>
38+
[HideInInspector]
39+
public string name;
40+
#pragma warning restore IDE1006
41+
42+
43+
#if UNITY_EDITOR
44+
45+
internal void OnValidate()
46+
{
47+
if (!HasInitialized)
48+
{
49+
AutoTrigger = TriggerTypes.OnAttach | TriggerTypes.OnDetach;
50+
HasInitialized = true;
51+
}
52+
name = ComponentController != null ? ComponentController.GetComponentNameFormatted(ComponentController) : "Component Controller";
53+
}
54+
#endif
55+
56+
[Flags]
57+
public enum TriggerTypes : byte
58+
{
59+
Nothing,
60+
OnAttach,
61+
OnDetach,
62+
}
63+
64+
public TriggerTypes AutoTrigger;
65+
public bool EnableOnAttach = true;
66+
public ComponentController ComponentController;
67+
68+
[HideInInspector]
69+
[SerializeField]
70+
internal bool HasInitialized;
71+
}
72+
2873
#if UNITY_EDITOR
2974
/// <inheritdoc/>
3075
/// <remarks>
@@ -45,6 +90,15 @@ protected virtual void OnValidate()
4590
// Wait for the next editor update to create a nested child and add the AttachableBehaviour
4691
EditorApplication.update += CreatedNestedChild;
4792
}
93+
94+
foreach (var componentController in ComponentControllers)
95+
{
96+
if (componentController == null)
97+
{
98+
continue;
99+
}
100+
componentController.OnValidate();
101+
}
48102
}
49103

50104
private void CreatedNestedChild()
@@ -57,6 +111,34 @@ private void CreatedNestedChild()
57111
DestroyImmediate(this);
58112
}
59113
#endif
114+
/// <summary>
115+
/// Flags to determine if the <see cref="AttachableBehaviour"/> will automatically detatch.
116+
/// </summary>
117+
[Flags]
118+
public enum AutoDetatchTypes
119+
{
120+
None,
121+
/// <summary>
122+
/// Detatch on ownership change.
123+
/// </summary>
124+
OnOwnershipChange,
125+
/// <summary>
126+
/// Detatch on despawn.
127+
/// </summary>
128+
OnDespawn,
129+
/// <summary>
130+
/// Detatch on destroy.
131+
/// </summary>
132+
OnAttachNodeDestroy,
133+
}
134+
135+
/// <summary>
136+
/// Determines if this <see cref="AttachableBehaviour"/> will automatically detatch on all instances if it has one of the <see cref="AutoDetatchTypes"/> flags.
137+
/// </summary>
138+
public AutoDetatchTypes AutoDetach = AutoDetatchTypes.OnDespawn | AutoDetatchTypes.OnOwnershipChange | AutoDetatchTypes.OnAttachNodeDestroy;
139+
140+
[SerializeField]
141+
internal List<ComponentControllerEntry> ComponentControllers;
60142

61143
/// <summary>
62144
/// Invoked when the <see cref="AttachState"/> of this instance has changed.
@@ -115,6 +197,7 @@ public enum AttachState
115197
/// If attached, attaching, or detaching this will be the <see cref="AttachableNode"/> this <see cref="AttachableBehaviour"/> instance is attached to.
116198
/// </summary>
117199
protected AttachableNode m_AttachableNode { get; private set; }
200+
internal AttachableNode AttachableNode => m_AttachableNode;
118201

119202
private NetworkBehaviourReference m_AttachedNodeReference = new NetworkBehaviourReference(null);
120203
private Vector3 m_OriginalLocalPosition;
@@ -125,17 +208,20 @@ protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
125208
{
126209
// Example of how to synchronize late joining clients when using an RPC to update
127210
// a local property's state.
128-
if (serializer.IsWriter)
129-
{
130-
serializer.SerializeValue(ref m_AttachedNodeReference);
131-
}
132-
else
133-
{
134-
serializer.SerializeValue(ref m_AttachedNodeReference);
135-
}
211+
serializer.SerializeValue(ref m_AttachedNodeReference);
136212
base.OnSynchronize(ref serializer);
137213
}
138214

215+
/// <summary>
216+
/// Override this method in place of Awake. This method is invoked during Awake.
217+
/// </summary>
218+
/// <remarks>
219+
/// The <see cref="AttachableBehaviour"/>'s Awake method is protected to assure it initializes itself at this point in time.
220+
/// </remarks>
221+
protected virtual void OnAwake()
222+
{
223+
}
224+
139225
/// <summary>
140226
/// If you create a custom <see cref="AttachableBehaviour"/> and override this method, you must invoke
141227
/// this base instance of <see cref="Awake"/>.
@@ -147,6 +233,7 @@ protected virtual void Awake()
147233
m_OriginalLocalRotation = transform.localRotation;
148234
m_AttachState = AttachState.Detached;
149235
m_AttachableNode = null;
236+
OnAwake();
150237
}
151238

152239
/// <inheritdoc/>
@@ -162,28 +249,52 @@ protected override void OnNetworkSessionSynchronized()
162249
base.OnNetworkSessionSynchronized();
163250
}
164251

165-
/// <inheritdoc/>
166-
public override void OnNetworkDespawn()
252+
internal void ForceDetatch()
167253
{
254+
if (m_AttachState == AttachState.Detached || m_AttachState == AttachState.Detaching)
255+
{
256+
return;
257+
}
258+
259+
ForceComponentChange(false, true);
168260
InternalDetach();
169261
if (NetworkManager && !NetworkManager.ShutdownInProgress)
170262
{
171263
// Notify of the changed attached state
172264
UpdateAttachState(m_AttachState, m_AttachableNode);
173265
}
174266
m_AttachedNodeReference = new NetworkBehaviourReference(null);
267+
}
268+
269+
/// <inheritdoc/>
270+
public override void OnNetworkPreDespawn()
271+
{
272+
if (AutoDetach.HasFlag(AutoDetatchTypes.OnDespawn))
273+
{
274+
ForceDetatch();
275+
}
175276
base.OnNetworkDespawn();
176277
}
177278

178279
private void UpdateAttachedState()
179280
{
180281
var attachableNode = (AttachableNode)null;
181-
var shouldParent = m_AttachedNodeReference.TryGet(out attachableNode, NetworkManager);
182-
var preState = shouldParent ? AttachState.Attaching : AttachState.Detaching;
183-
var preNode = shouldParent ? attachableNode : m_AttachableNode;
184-
shouldParent = shouldParent && attachableNode != null;
282+
var isAttaching = m_AttachedNodeReference.TryGet(out attachableNode, NetworkManager);
283+
var preState = isAttaching ? AttachState.Attaching : AttachState.Detaching;
284+
285+
// Exit early if we are already in the correct attached state and the incoming
286+
// AttachableNode reference is the same as the local AttachableNode property.
287+
if (attachableNode == m_AttachableNode &&
288+
((isAttaching && m_AttachState == AttachState.Attached) ||
289+
(!isAttaching && m_AttachState == AttachState.Detached)))
290+
{
291+
return;
292+
}
293+
294+
var preNode = isAttaching ? attachableNode : m_AttachableNode;
295+
isAttaching = isAttaching && attachableNode != null;
185296

186-
if (shouldParent && m_AttachableNode != null && m_AttachState == AttachState.Attached)
297+
if (isAttaching && m_AttachableNode != null && m_AttachState == AttachState.Attached)
187298
{
188299
// If we are attached to some other AttachableNode, then detach from that before attaching to a new one.
189300
if (m_AttachableNode != attachableNode)
@@ -201,7 +312,8 @@ private void UpdateAttachedState()
201312
// Change the state to attaching or detaching
202313
UpdateAttachState(preState, preNode);
203314

204-
if (shouldParent)
315+
ForceComponentChange(isAttaching, false);
316+
if (isAttaching)
205317
{
206318
InternalAttach(attachableNode);
207319
}
@@ -215,7 +327,7 @@ private void UpdateAttachedState()
215327

216328
// When detaching, we want to make our final action
217329
// the invocation of the AttachableNode's Detach method.
218-
if (!shouldParent && m_AttachableNode)
330+
if (!isAttaching && m_AttachableNode)
219331
{
220332
m_AttachableNode.Detach(this);
221333
m_AttachableNode = null;
@@ -257,6 +369,29 @@ private void UpdateAttachState(AttachState attachState, AttachableNode attachabl
257369
}
258370
}
259371

372+
/// <inheritdoc/>
373+
protected override void OnOwnershipChanged(ulong previous, ulong current)
374+
{
375+
if (AutoDetach.HasFlag(AutoDetatchTypes.OnOwnershipChange))
376+
{
377+
ForceDetatch();
378+
}
379+
base.OnOwnershipChanged(previous, current);
380+
}
381+
382+
internal void ForceComponentChange(bool isAttaching, bool forcedChange)
383+
{
384+
var triggerType = isAttaching ? ComponentControllerEntry.TriggerTypes.OnAttach : ComponentControllerEntry.TriggerTypes.OnDetach;
385+
386+
foreach (var componentControllerEntry in ComponentControllers)
387+
{
388+
if (componentControllerEntry.AutoTrigger.HasFlag(triggerType))
389+
{
390+
componentControllerEntry.ComponentController.ForceChangeEnabled(componentControllerEntry.EnableOnAttach ? isAttaching : !isAttaching, forcedChange);
391+
}
392+
}
393+
}
394+
260395
/// <summary>
261396
/// Internal attach method that just handles changing state, parenting, and sending the <see cref="AttachableNode"/> a
262397
/// notification that an <see cref="AttachableBehaviour"/> has attached.
@@ -349,23 +484,23 @@ public void Detach()
349484
return;
350485
}
351486

352-
if (m_AttachState != AttachState.Attached || m_AttachableNode == null)
487+
if (m_AttachState == AttachState.Detached || m_AttachState == AttachState.Detaching || m_AttachableNode == null)
353488
{
354489
// Check for the unlikely scenario that an instance has mismatch between the state and assigned attachable node.
355-
if (!m_AttachableNode && m_AttachState == AttachState.Attached)
490+
if (!m_AttachableNode)
356491
{
357492
NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name}'s state is still {m_AttachState} but has no {nameof(AttachableNode)} assigned!");
358493
}
359494

360495
// Developer only notification for the most likely scenario where this method is invoked but the instance is not attached to anything.
361-
if (NetworkManager && NetworkManager.LogLevel <= LogLevel.Developer)
496+
if (!m_AttachableNode && NetworkManager && NetworkManager.LogLevel <= LogLevel.Developer)
362497
{
363498
NetworkLog.LogWarning($"[{name}][Detach] Cannot detach! {name} is not attached to anything!");
364499
}
365500

366501
// If we have the attachable node set and we are not in the middle of detaching, then log an error and note
367502
// this could potentially occur if inoked more than once for the same instance in the same frame.
368-
if (m_AttachableNode && m_AttachState != AttachState.Detaching)
503+
if (m_AttachableNode)
369504
{
370505
NetworkLog.LogError($"[{name}][Detach] Invalid state detected! {name} is still referencing {nameof(AttachableNode)} {m_AttachableNode.name}! Could {nameof(Detach)} be getting invoked more than once for the same instance?");
371506
}
@@ -407,5 +542,18 @@ private void UpdateAttachStateRpc(NetworkBehaviourReference attachedNodeReferenc
407542
{
408543
ChangeReference(attachedNodeReference);
409544
}
545+
546+
/// <summary>
547+
/// Notification that the <see cref="AttachableNode"/> is being destroyed
548+
/// </summary>
549+
internal void OnAttachNodeDestroy()
550+
{
551+
// If this instance should force a detatch on destroy
552+
if (AutoDetach.HasFlag(AutoDetatchTypes.OnAttachNodeDestroy))
553+
{
554+
// Force a detatch
555+
ForceDetatch();
556+
}
557+
}
410558
}
411559
}

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

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,35 @@ public class AttachableNode : NetworkBehaviour
2929
/// </summary>
3030
protected readonly List<AttachableBehaviour> m_AttachedBehaviours = new List<AttachableBehaviour>();
3131

32+
/// <inheritdoc/>
33+
protected override void OnNetworkPreSpawn(ref NetworkManager networkManager)
34+
{
35+
m_AttachedBehaviours.Clear();
36+
base.OnNetworkPreSpawn(ref networkManager);
37+
}
38+
39+
/// <inheritdoc/>
40+
/// <remarks>
41+
/// When the ownership of an <see cref="AttachableNode"/> changes, it will find all currently attached <see cref="AttachableBehaviour"/> components
42+
/// that are registered as being attached to this instance.
43+
/// </remarks>
44+
protected override void OnOwnershipChanged(ulong previous, ulong current)
45+
{
46+
if (current == NetworkManager.LocalClientId)
47+
{
48+
m_AttachedBehaviours.Clear();
49+
var attachables = NetworkObject.transform.GetComponentsInChildren<AttachableBehaviour>();
50+
foreach (var attachable in attachables)
51+
{
52+
if (attachable.AttachableNode == this)
53+
{
54+
m_AttachedBehaviours.Add(attachable);
55+
}
56+
}
57+
}
58+
base.OnOwnershipChanged(previous, current);
59+
}
60+
3261
/// <inheritdoc/>
3362
/// <remarks>
3463
/// If the <see cref="NetworkObject"/> this <see cref="AttachableNode"/> belongs to is despawned,
@@ -46,11 +75,15 @@ public override void OnNetworkPreDespawn()
4675
base.OnNetworkPreDespawn();
4776
}
4877

49-
/// <inheritdoc/>
50-
public override void OnNetworkDespawn()
78+
internal override void InternalOnDestroy()
5179
{
80+
// Notify any attached behaviours that this node is being destroyed.
81+
for (int i = m_AttachedBehaviours.Count - 1; i >= 0; i--)
82+
{
83+
m_AttachedBehaviours[i]?.OnAttachNodeDestroy();
84+
}
5285
m_AttachedBehaviours.Clear();
53-
base.OnNetworkDespawn();
86+
base.InternalOnDestroy();
5487
}
5588

5689
/// <summary>
@@ -69,7 +102,6 @@ internal void Attach(AttachableBehaviour attachableBehaviour)
69102
NetworkLog.LogError($"[{nameof(AttachableNode)}][{name}][Attach] {nameof(AttachableBehaviour)} {attachableBehaviour.name} is already attached!");
70103
return;
71104
}
72-
73105
m_AttachedBehaviours.Add(attachableBehaviour);
74106
OnAttached(attachableBehaviour);
75107
}
@@ -90,7 +122,6 @@ internal void Detach(AttachableBehaviour attachableBehaviour)
90122
NetworkLog.LogError($"[{nameof(AttachableNode)}][{name}][Detach] {nameof(AttachableBehaviour)} {attachableBehaviour.name} is not attached!");
91123
return;
92124
}
93-
94125
m_AttachedBehaviours.Remove(attachableBehaviour);
95126
OnDetached(attachableBehaviour);
96127
}

0 commit comments

Comments
 (0)