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 );
}
}