namespace Sandbox; /// /// Flags to search for Components. /// I've named this something generic because I think we can re-use it to search for GameObjects too. /// [Flags, Expose] public enum FindMode { /// /// Components that are enabled /// Enabled = 1, /// /// Components that are disabled /// Disabled = 2, /// /// Components in this object /// InSelf = 4, /// /// Components in our parent /// InParent = 8, /// /// Components in all ancestors (parent, their parent, their parent, etc) /// InAncestors = 16, /// /// Components in our children /// InChildren = 32, /// /// Components in all decendants (our children, their children, their children etc) /// InDescendants = 64, EnabledInSelf = Enabled | InSelf, EnabledInSelfAndDescendants = Enabled | InSelf | InDescendants, EnabledInSelfAndChildren = Enabled | InSelf | InChildren, DisabledInSelf = Disabled | InSelf, DisabledInSelfAndDescendants = Disabled | InSelf | InDescendants, DisabledInSelfAndChildren = Disabled | InSelf | InChildren, EverythingInSelf = Enabled | InSelf | Disabled, EverythingInSelfAndDescendants = Enabled | InSelf | Disabled | InDescendants, EverythingInSelfAndChildren = Enabled | InSelf | Disabled | InChildren, EverythingInSelfAndParent = Enabled | InSelf | Disabled | InParent, EverythingInSelfAndAncestors = Enabled | InSelf | Disabled | InAncestors, EverythingInAncestors = Enabled | Disabled | InAncestors, EverythingInChildren = Enabled | Disabled | InChildren, EverythingInDescendants = Enabled | Disabled | InDescendants, } public class ComponentList { readonly GameObject go; /// /// This is the hard list of components. /// This isn't a HashSet because we need the order to stay. /// List _list; internal ComponentList( GameObject o ) { go = o; _list = new List(); } /// /// Get all components, including disabled ones /// public IEnumerable GetAll() { return _list; } /// /// Hotload has occurred /// internal void OnHotload() { _list.RemoveAll( x => x is null ); } /// /// Add a component of this type /// public Component Create( TypeDescription type, bool startEnabled = true ) { if ( !type.TargetType.IsAssignableTo( typeof( Component ) ) ) return null; using var batch = CallbackBatch.Batch(); var t = type.Create(); t.GameObject = go; _list.Add( t ); t.Enabled = startEnabled; go.OnComponentAdded( t ); return t; } /// /// Add a component of this type /// public T Create( bool startEnabled = true ) where T : Component, new() { using var batch = CallbackBatch.Batch(); var t = new T(); t.GameObject = go; _list.Add( t ); t.InitializeComponent(); t.Enabled = startEnabled; go.OnComponentAdded( t ); return t; } /// /// Add a component of this type /// internal Component Create( Type type, bool startEnabled = true ) { if ( !type.IsAssignableTo( typeof( Component ) ) ) return null; using var batch = CallbackBatch.Batch(); var t = (Component)Activator.CreateInstance( type ); t.GameObject = go; _list.Add( t ); t.InitializeComponent(); t.Enabled = startEnabled; go.OnComponentAdded( t ); return t; } /// /// Get a component of this type /// public T Get( FindMode search ) { return GetAll( search ).FirstOrDefault(); } /// /// Get a component of this type /// public Component Get( Type type, FindMode find = FindMode.EnabledInSelf ) { return GetAll( type, find ).FirstOrDefault(); } /// /// Get all components of this type /// public IEnumerable GetAll( Type type, FindMode find ) { return GetAll( find ).Where( x => x.GetType().IsAssignableTo( type ) ); } /// /// Get all components /// public IEnumerable GetAll( FindMode find ) => GetAll( find ); /// /// Get a list of components on this game object, optionally recurse when deep is true /// public IEnumerable GetAll( FindMode find = FindMode.InSelf | FindMode.Enabled | FindMode.InDescendants ) { if ( go.IsDestroyed ) return Enumerable.Empty(); var results = new List( 16 ); CollectAll( results, find ); return results; } // This is an incredibly hot code path, even the slightest change should be verified with benchmarks. private void CollectAll( List results, FindMode find ) { bool enabledOnly = find.Contains( FindMode.Enabled ); bool disabledOnly = find.Contains( FindMode.Disabled ); if ( enabledOnly == disabledOnly ) { enabledOnly = false; disabledOnly = false; } if ( enabledOnly && !go.Enabled ) return; // // Find in self // if ( find.Contains( FindMode.InSelf ) ) { for ( int i = 0; i < _list.Count; i++ ) { var component = _list[i]; if ( component is null ) continue; if ( enabledOnly && !component.Active ) continue; if ( disabledOnly && component.Active ) continue; if ( component is T c ) { results.Add( c ); } } } // // Find in children // if ( find.Contains( FindMode.InChildren ) || find.Contains( FindMode.InDescendants ) ) { var childFlags = find | FindMode.InSelf; childFlags &= ~FindMode.InParent; childFlags &= ~FindMode.InAncestors; // If we're not searching all descendants then remove the InChildren flag if ( !find.Contains( FindMode.InDescendants ) ) { childFlags &= ~FindMode.InChildren; } for ( int i = 0; i < go.Children.Count; i++ ) { var child = go.Children[i]; if ( child.IsValid() ) { child.Components.CollectAll( results, childFlags ); } } } // // Find in parent // if ( find.Contains( FindMode.InParent ) || find.Contains( FindMode.InAncestors ) ) { var parentFlags = find | FindMode.InSelf; parentFlags &= ~FindMode.InChildren; parentFlags &= ~FindMode.InDescendants; // If we're not searching all ancestors then remove the InParent flag if ( !find.Contains( FindMode.InAncestors ) ) { parentFlags &= ~FindMode.InParent; } if ( go.Parent is not null && go.Parent is PrefabScene or not Scene ) { go.Parent.Components.CollectAll( results, parentFlags ); } } } internal void Execute( Action action, FindMode find = FindMode.EnabledInSelfAndDescendants ) { switch ( find ) { // Most common case has a fast path case FindMode.EnabledInSelfAndDescendants: ExecuteEnabledInSelfAndDescendants( action ); break; default: ExecuteGeneric( action, find ); break; } } // Calling this directly is faster than going through Execute internal void ExecuteEnabledInSelfAndDescendants( Action action ) { if ( !go.IsValid() || !go.Enabled ) return; // Check components on this GameObject for ( int i = 0; i < _list.Count; i++ ) { var component = _list[i]; if ( component is null ) continue; if ( component is T target && component.Active ) { action.Invoke( target ); } } // Recurse to children for ( int i = go.Children.Count - 1; i >= 0; i-- ) { if ( i >= go.Children.Count ) continue; var child = go.Children[i]; if ( !child.IsValid() ) continue; child.Components.ExecuteEnabledInSelfAndDescendants( action ); } } private void ExecuteGeneric( Action action, FindMode find = FindMode.EnabledInSelfAndDescendants ) { bool enabledOnly = find.Contains( FindMode.Enabled ); bool disabledOnly = find.Contains( FindMode.Disabled ); if ( enabledOnly == disabledOnly ) { enabledOnly = false; disabledOnly = false; } if ( enabledOnly && !go.Enabled ) return; // // Execute in self // if ( find.Contains( FindMode.InSelf ) ) { for ( int i = 0; i < _list.Count; i++ ) { var component = _list[i]; if ( component is null ) continue; if ( enabledOnly && !component.Active ) continue; if ( disabledOnly && component.Active ) continue; if ( component is T target ) { action.Invoke( target ); } } } // // Execute in children // if ( find.Contains( FindMode.InChildren ) || find.Contains( FindMode.InDescendants ) ) { var childFlags = find | FindMode.InSelf; childFlags &= ~FindMode.InParent; childFlags &= ~FindMode.InAncestors; // If we're not searching all descendants then remove the InChildren flag if ( !find.Contains( FindMode.InDescendants ) ) { childFlags &= ~FindMode.InChildren; } for ( int i = 0; i < go.Children.Count; i++ ) { var child = go.Children[i]; if ( child.IsValid() ) { child.Components.Execute( action, childFlags ); } } } // // Execute in parent // if ( find.Contains( FindMode.InParent ) || find.Contains( FindMode.InAncestors ) ) { var parentFlags = find | FindMode.InSelf; parentFlags &= ~FindMode.InChildren; parentFlags &= ~FindMode.InDescendants; // If we're not searching all ancestors then remove the InParent flag if ( !find.Contains( FindMode.InAncestors ) ) { parentFlags &= ~FindMode.InParent; } if ( go.Parent is not null && go.Parent is PrefabScene or not Scene ) { go.Parent.Components.Execute( action, parentFlags ); } } } /// /// Try to get this component /// public bool TryGet( out T component, FindMode search = FindMode.EnabledInSelf ) { component = Get( search ); return component is not null; } /// /// Allows linq style queries /// public Component FirstOrDefault( Func value ) => _list.FirstOrDefault( value ); /// /// Amount of components - including disabled /// public int Count => _list.Count; public void ForEach( string name, bool includeDisabled, Action action ) { if ( !includeDisabled && !go.Active ) return; for ( int i = _list.Count - 1; i >= 0 && i < _list.Count; i-- ) { Component c = _list[i]; if ( c is null ) { _list.RemoveAt( i ); continue; } if ( !includeDisabled && !c.Active ) continue; if ( c is not T t ) continue; try { action( t ); } catch ( System.Exception e ) { Log.Warning( e, $"Exception when calling {name} on {c}: {e.Message}" ); } } } public void ForEach( string name, bool includeDisabled, Action action ) => ForEach( name, includeDisabled, action ); internal void RemoveNull() { _list.RemoveAll( x => x is null ); } internal void OnDestroyedInternal( Component baseComponent ) { if ( _list.Remove( baseComponent ) ) { go.OnComponentRemoved( baseComponent ); } } internal int IndexOf( Component baseComponent ) { return _list.IndexOf( baseComponent ); } /// /// Move the position of the component in the list by delta (-1 means up one, 1 means down one) /// public void Move( Component baseComponent, int delta ) { var i = _list.IndexOf( baseComponent ); if ( i < 0 ) return; i += delta; if ( i < 0 ) i = 0; if ( i >= _list.Count ) i = _list.Count - 1; // Move the element _list.RemoveAt( _list.IndexOf( baseComponent ) ); _list.Insert( i, baseComponent ); } /// /// Move the component to a specific index in the list. /// If a component is already at that index, it will be swapped with the component being moved. /// internal void MoveToIndex( Component comp, int targetIndex ) { var compIndex = _list.IndexOf( comp ); _list[compIndex] = _list[targetIndex]; _list[targetIndex] = comp; } // // Easy Modes // /// /// Find component on this gameobject /// public T Get( bool includeDisabled = false ) { var f = FindMode.InSelf; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find this component, if it doesn't exist - create it. /// public T GetOrCreate( FindMode flags = FindMode.EverythingInSelf ) where T : Component, new() { if ( TryGet( out var component, flags ) ) return component; return Create(); } /// /// Find component on this gameobject's ancestors or on self /// public T GetInAncestorsOrSelf( bool includeDisabled = false ) { var f = FindMode.InSelf | FindMode.InAncestors; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find component on this gameobject's ancestors /// public T GetInAncestors( bool includeDisabled = false ) { var f = FindMode.InAncestors; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find component on this gameobject's decendants or on self /// public T GetInDescendantsOrSelf( bool includeDisabled = false ) { var f = FindMode.InSelf | FindMode.InDescendants; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find component on this gameobject's decendants /// public T GetInDescendants( bool includeDisabled = false ) { var f = FindMode.InDescendants; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find component on this gameobject's immediate children or on self /// public T GetInChildrenOrSelf( bool includeDisabled = false ) { var f = FindMode.InSelf | FindMode.InChildren; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find component on this gameobject's immediate children /// public T GetInChildren( bool includeDisabled = false ) { var f = FindMode.InChildren; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find component on this gameobject's parent or on self /// public T GetInParentOrSelf( bool includeDisabled = false ) { var f = FindMode.InSelf | FindMode.InParent; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find component on this gameobject's parent /// public T GetInParent( bool includeDisabled = false ) { var f = FindMode.InParent; if ( !includeDisabled ) f |= FindMode.Enabled; return Get( f ); } /// /// Find component on this gameobject with the specified id /// public Component Get( Guid id ) { return GetAll().FirstOrDefault( x => x.Id.Equals( id ) ); } /// /// Adds a special component that will keep information about a missing component. /// This component just holds the raw json of this component. /// internal void AddMissing( MissingComponent missing ) { missing.GameObject = go; _list.Add( missing ); go.OnComponentAdded( missing ); } } /// /// Interface for types that reference a , to provide /// convenience method for accessing that list. /// [Expose, Title( "Component List" ), Icon( "apps" )] public interface IComponentLister { [ActionGraphIgnore] ComponentList Components { get; } [Title( "Create {T|Component}" )] public T Create( bool startEnabled = true ) where T : Component, new() { return Components.Create( startEnabled ); } [Pure, Title( "Get {T|Component}" )] public T Get<[HasImplementation( typeof( Component ) )] T>( FindMode search = FindMode.EnabledInSelf ) { return Components.GetAll( search ).FirstOrDefault(); } [Pure, Title( "Try Get {T|Component}" )] public bool TryGet<[HasImplementation( typeof( Component ) )] T>( out T component, FindMode search = FindMode.EnabledInSelf ) { return Components.TryGet( out component, search ); } [Pure, Title( "Get All {T|Component}" )] public IEnumerable GetAll<[HasImplementation( typeof( Component ) )] T>( FindMode search = FindMode.EnabledInSelf ) { return Components.GetAll( search ); } [Title( "Get or Create {T|Component}" )] public T GetOrCreate( FindMode flags = FindMode.EverythingInSelf ) where T : Component, new() { return Components.GetOrCreate( flags ); } }