11using System ;
2+ using System . Collections . Generic ;
3+
24#if UNITY_EDITOR
35using 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}
0 commit comments