765 lines
25 KiB
C#
765 lines
25 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using UnityEngine;
|
|
using UnityObject = UnityEngine.Object;
|
|
|
|
namespace Unity.VisualScripting
|
|
{
|
|
public class UnitOptionTree : ExtensibleFuzzyOptionTree
|
|
{
|
|
#region Initialization
|
|
|
|
public UnitOptionTree(GUIContent label) : base(label)
|
|
{
|
|
favorites = new Favorites(this);
|
|
|
|
showBackgroundWorkerProgress = true;
|
|
}
|
|
|
|
public override IFuzzyOption Option(object item)
|
|
{
|
|
if (item is Namespace @namespace)
|
|
{
|
|
return new NamespaceOption(@namespace, true);
|
|
}
|
|
|
|
if (item is Type type)
|
|
{
|
|
return new TypeOption(type, true);
|
|
}
|
|
|
|
return base.Option(item);
|
|
}
|
|
|
|
public override void Prewarm()
|
|
{
|
|
filter = filter ?? UnitOptionFilter.Any;
|
|
|
|
try
|
|
{
|
|
options = new HashSet<IUnitOption>(UnitBase.Subset(filter, reference));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"Failed to fetch node options for fuzzy finder (error log below).\nTry rebuilding the node options from '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n\n{ex}");
|
|
options = new HashSet<IUnitOption>();
|
|
}
|
|
|
|
typesWithMembers = new HashSet<Type>();
|
|
|
|
foreach (var option in options)
|
|
{
|
|
if (option is IMemberUnitOption memberUnitOption && memberUnitOption.targetType != null)
|
|
{
|
|
typesWithMembers.Add(memberUnitOption.targetType);
|
|
}
|
|
}
|
|
}
|
|
|
|
private HashSet<IUnitOption> options;
|
|
|
|
private HashSet<Type> typesWithMembers;
|
|
|
|
#endregion
|
|
|
|
|
|
#region Configuration
|
|
|
|
public UnitOptionFilter filter { get; set; }
|
|
public GraphReference reference { get; set; }
|
|
public bool includeNone { get; set; }
|
|
public bool surfaceCommonTypeLiterals { get; set; }
|
|
public object[] rootOverride { get; set; }
|
|
|
|
public FlowGraph graph => reference.graph as FlowGraph;
|
|
public GameObject self => reference.self;
|
|
|
|
public ActionDirection direction { get; set; } = ActionDirection.Any;
|
|
|
|
#endregion
|
|
|
|
|
|
#region Hierarchy
|
|
|
|
private readonly FuzzyGroup enumsGroup = new FuzzyGroup("(Enums)", typeof(Enum).Icon());
|
|
private readonly FuzzyGroup selfGroup = new FuzzyGroup("This", typeof(GameObject).Icon());
|
|
|
|
private IEnumerable<UnitCategory> SpecialCategories()
|
|
{
|
|
yield return new UnitCategory("Codebase");
|
|
yield return new UnitCategory("Events");
|
|
yield return new UnitCategory("Variables");
|
|
yield return new UnitCategory("Math");
|
|
yield return new UnitCategory("Nesting");
|
|
yield return new UnitCategory("Graphs");
|
|
}
|
|
|
|
public override IEnumerable<object> Root()
|
|
{
|
|
if (rootOverride != null && rootOverride.Length > 0)
|
|
{
|
|
foreach (var item in rootOverride)
|
|
{
|
|
yield return item;
|
|
}
|
|
|
|
yield break;
|
|
}
|
|
|
|
if (filter.CompatibleOutputType != null)
|
|
{
|
|
var outputType = filter.CompatibleOutputType;
|
|
|
|
var outputTypeLiteral = options.FirstOrDefault(option => option is LiteralOption literalOption && literalOption.literalType == outputType);
|
|
|
|
if (outputTypeLiteral != null)
|
|
{
|
|
yield return outputTypeLiteral;
|
|
}
|
|
|
|
HashSet<Type> noSurfaceConstructors = new HashSet<Type>()
|
|
{
|
|
typeof(string),
|
|
typeof(object)
|
|
};
|
|
|
|
if (!noSurfaceConstructors.Contains(outputType))
|
|
{
|
|
var outputTypeConstructors = options.Where(option => option is InvokeMemberOption invokeMemberOption &&
|
|
invokeMemberOption.targetType == outputType &&
|
|
invokeMemberOption.unit.member.isConstructor);
|
|
|
|
foreach (var outputTypeConstructor in outputTypeConstructors)
|
|
{
|
|
yield return outputTypeConstructor;
|
|
}
|
|
}
|
|
|
|
if (outputType == typeof(bool))
|
|
{
|
|
foreach (var logicOperation in CategoryChildren(new UnitCategory("Logic")))
|
|
{
|
|
yield return logicOperation;
|
|
}
|
|
}
|
|
|
|
if (outputType.IsNumeric())
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Scalar")))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
|
|
if (outputType == typeof(Vector2))
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 2")))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
|
|
if (outputType == typeof(Vector3))
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 3")))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
|
|
if (outputType == typeof(Vector4))
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 4")))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (surfaceCommonTypeLiterals)
|
|
{
|
|
foreach (var commonType in EditorTypeUtility.commonTypes)
|
|
{
|
|
if (commonType == filter.CompatibleOutputType)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var commonTypeLiteral = options.FirstOrDefault(option => option is LiteralOption literalOption && literalOption.literalType == commonType);
|
|
|
|
if (commonTypeLiteral != null)
|
|
{
|
|
yield return commonTypeLiteral;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (filter.CompatibleInputType != null)
|
|
{
|
|
var inputType = filter.CompatibleInputType;
|
|
|
|
if (!inputType.IsPrimitive && inputType != typeof(object))
|
|
{
|
|
yield return inputType;
|
|
}
|
|
|
|
if (inputType == typeof(bool))
|
|
{
|
|
yield return options.Single(o => o.UnitIs<If>());
|
|
yield return options.Single(o => o.UnitIs<SelectUnit>());
|
|
}
|
|
|
|
if (inputType == typeof(bool) || inputType.IsNumeric())
|
|
{
|
|
foreach (var logicOperation in CategoryChildren(new UnitCategory("Logic")))
|
|
{
|
|
yield return logicOperation;
|
|
}
|
|
}
|
|
|
|
if (inputType.IsNumeric())
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Scalar")))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
|
|
if (inputType == typeof(Vector2))
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 2")))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
|
|
if (inputType == typeof(Vector3))
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 3")))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
|
|
if (inputType == typeof(Vector4))
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 4")))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
|
|
if (typeof(IEnumerable).IsAssignableFrom(inputType) && (inputType != typeof(string) && inputType != typeof(Transform)))
|
|
{
|
|
foreach (var mathOperation in CategoryChildren(new UnitCategory("Collections"), false))
|
|
{
|
|
yield return mathOperation;
|
|
}
|
|
}
|
|
|
|
if (typeof(IList).IsAssignableFrom(inputType))
|
|
{
|
|
foreach (var listOperation in CategoryChildren(new UnitCategory("Collections/Lists")))
|
|
{
|
|
yield return listOperation;
|
|
}
|
|
}
|
|
|
|
if (typeof(IDictionary).IsAssignableFrom(inputType))
|
|
{
|
|
foreach (var dictionaryOperation in CategoryChildren(new UnitCategory("Collections/Dictionaries")))
|
|
{
|
|
yield return dictionaryOperation;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (filter.NoConnection)
|
|
{
|
|
yield return new StickyNoteOption();
|
|
}
|
|
|
|
if (UnityAPI.Await
|
|
(
|
|
() =>
|
|
{
|
|
if (self != null)
|
|
{
|
|
selfGroup.label = self.name;
|
|
selfGroup.icon = self.Icon();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
)
|
|
)
|
|
{
|
|
yield return selfGroup;
|
|
}
|
|
|
|
foreach (var category in options.Select(option => option.category?.root)
|
|
.NotNull()
|
|
.Concat(SpecialCategories())
|
|
.Distinct()
|
|
.OrderBy(c => c.name))
|
|
{
|
|
yield return category;
|
|
}
|
|
|
|
foreach (var extensionRootItem in base.Root())
|
|
{
|
|
yield return extensionRootItem;
|
|
}
|
|
|
|
if (filter.Self)
|
|
{
|
|
var self = options.FirstOrDefault(option => option.UnitIs<This>());
|
|
|
|
if (self != null)
|
|
{
|
|
yield return self;
|
|
}
|
|
}
|
|
|
|
foreach (var unit in CategoryChildren(null))
|
|
{
|
|
yield return unit;
|
|
}
|
|
|
|
if (includeNone)
|
|
{
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<object> Children(object parent)
|
|
{
|
|
if (parent is Namespace @namespace)
|
|
{
|
|
return NamespaceChildren(@namespace);
|
|
}
|
|
else if (parent is Type type)
|
|
{
|
|
return TypeChildren(type);
|
|
}
|
|
else if (parent == enumsGroup)
|
|
{
|
|
return EnumsChildren();
|
|
}
|
|
else if (parent == selfGroup)
|
|
{
|
|
return SelfChildren();
|
|
}
|
|
else if (parent is UnitCategory unitCategory)
|
|
{
|
|
return CategoryChildren(unitCategory);
|
|
}
|
|
else if (parent is VariableKind variableKind)
|
|
{
|
|
return VariableKindChildren(variableKind);
|
|
}
|
|
else
|
|
{
|
|
return base.Children(parent);
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> SelfChildren()
|
|
{
|
|
yield return typeof(GameObject);
|
|
|
|
// Self components can be null if no script is assigned to them
|
|
// https://support.ludiq.io/forums/5-bolt/topics/817-/
|
|
foreach (var selfComponentType in UnityAPI.Await(() => self.GetComponents<Component>().NotUnityNull().Select(c => c.GetType())))
|
|
{
|
|
yield return selfComponentType;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> CodebaseChildren()
|
|
{
|
|
foreach (var rootNamespace in typesWithMembers.Where(t => !t.IsEnum)
|
|
.Select(t => t.Namespace().Root)
|
|
.OrderBy(ns => ns.DisplayName(false))
|
|
.Distinct())
|
|
{
|
|
yield return rootNamespace;
|
|
}
|
|
|
|
if (filter.Literals && options.Any(option => option is LiteralOption literalOption && literalOption.literalType.IsEnum))
|
|
{
|
|
yield return enumsGroup;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> MathChildren()
|
|
{
|
|
foreach (var mathMember in GetMembers(typeof(Mathf)).Where(option => !((MemberUnit)option.unit).member.requiresTarget))
|
|
{
|
|
yield return mathMember;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> TimeChildren()
|
|
{
|
|
foreach (var timeMember in GetMembers(typeof(Time)).Where(option => !((MemberUnit)option.unit).member.requiresTarget))
|
|
{
|
|
yield return timeMember;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> NestingChildren()
|
|
{
|
|
foreach (var nester in options.Where(option => option.UnitIs<IGraphNesterElement>() && ((IGraphNesterElement)option.unit).nest.macro == null)
|
|
.OrderBy(option => option.label))
|
|
{
|
|
yield return nester;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> MacroChildren()
|
|
{
|
|
foreach (var macroNester in options.Where(option => option.UnitIs<IGraphNesterElement>() && ((IGraphNesterElement)option.unit).nest.macro != null)
|
|
.OrderBy(option => option.label))
|
|
{
|
|
yield return macroNester;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> VariablesChildren()
|
|
{
|
|
yield return VariableKind.Flow;
|
|
yield return VariableKind.Graph;
|
|
yield return VariableKind.Object;
|
|
yield return VariableKind.Scene;
|
|
yield return VariableKind.Application;
|
|
yield return VariableKind.Saved;
|
|
}
|
|
|
|
private IEnumerable<object> VariableKindChildren(VariableKind kind)
|
|
{
|
|
foreach (var variable in options.OfType<IUnifiedVariableUnitOption>()
|
|
.Where(option => option.kind == kind)
|
|
.OrderBy(option => option.name))
|
|
{
|
|
yield return variable;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> NamespaceChildren(Namespace @namespace)
|
|
{
|
|
foreach (var childNamespace in GetChildrenNamespaces(@namespace))
|
|
{
|
|
yield return childNamespace;
|
|
}
|
|
|
|
foreach (var type in GetNamespaceTypes(@namespace))
|
|
{
|
|
yield return type;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<Namespace> GetChildrenNamespaces(Namespace @namespace)
|
|
{
|
|
if (!@namespace.IsGlobal)
|
|
{
|
|
foreach (var childNamespace in typesWithMembers.Where(t => !t.IsEnum)
|
|
.SelectMany(t => t.Namespace().AndAncestors())
|
|
.Distinct()
|
|
.Where(ns => ns.Parent == @namespace)
|
|
.OrderBy(ns => ns.DisplayName(false)))
|
|
{
|
|
yield return childNamespace;
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable<Type> GetNamespaceTypes(Namespace @namespace)
|
|
{
|
|
foreach (var type in typesWithMembers.Where(t => t.Namespace() == @namespace && !t.IsEnum)
|
|
.OrderBy(t => t.DisplayName()))
|
|
{
|
|
yield return type;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> TypeChildren(Type type)
|
|
{
|
|
foreach (var literal in options.Where(option => option is LiteralOption literalOption && literalOption.literalType == type))
|
|
{
|
|
yield return literal;
|
|
}
|
|
|
|
foreach (var expose in options.Where(option => option is ExposeOption exposeOption && exposeOption.exposedType == type))
|
|
{
|
|
yield return expose;
|
|
}
|
|
|
|
if (type.IsStruct())
|
|
{
|
|
foreach (var createStruct in options.Where(option => option is CreateStructOption createStructOption && createStructOption.structType == type))
|
|
{
|
|
yield return createStruct;
|
|
}
|
|
}
|
|
|
|
foreach (var member in GetMembers(type))
|
|
{
|
|
yield return member;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<IUnitOption> GetMembers(Type type)
|
|
{
|
|
foreach (var member in options.Where(option => option is IMemberUnitOption memberUnitOption && memberUnitOption.targetType == type && option.unit.canDefine)
|
|
.OrderBy(option => BoltCore.Configuration.groupInheritedMembers && ((MemberUnit)option.unit).member.isPseudoInherited)
|
|
.ThenBy(option => option.order)
|
|
.ThenBy(option => option.label))
|
|
{
|
|
yield return member;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> EnumsChildren()
|
|
{
|
|
foreach (var literal in options.Where(option => option is LiteralOption literalOption && literalOption.literalType.IsEnum)
|
|
.OrderBy(option => option.label))
|
|
{
|
|
yield return literal;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> CategoryChildren(UnitCategory category, bool subCategories = true)
|
|
{
|
|
if (category != null && subCategories)
|
|
{
|
|
foreach (var subCategory in options.SelectMany(option => option.category == null ? Enumerable.Empty<UnitCategory>() : option.category.AndAncestors())
|
|
.Distinct()
|
|
.Where(c => c.parent == category)
|
|
.OrderBy(c => c.name))
|
|
{
|
|
yield return subCategory;
|
|
}
|
|
}
|
|
|
|
foreach (var unit in options.Where(option => option.category == category)
|
|
.Where(option => !option.unitType.HasAttribute<SpecialUnitAttribute>())
|
|
.OrderBy(option => option.order)
|
|
.ThenBy(option => option.label))
|
|
{
|
|
yield return unit;
|
|
}
|
|
|
|
if (category != null)
|
|
{
|
|
if (category.root.name == "Events")
|
|
{
|
|
foreach (var eventChild in EventsChildren(category))
|
|
{
|
|
yield return eventChild;
|
|
}
|
|
}
|
|
else if (category.fullName == "Codebase")
|
|
{
|
|
foreach (var codebaseChild in CodebaseChildren())
|
|
{
|
|
yield return codebaseChild;
|
|
}
|
|
}
|
|
else if (category.fullName == "Variables")
|
|
{
|
|
foreach (var variableChild in VariablesChildren())
|
|
{
|
|
yield return variableChild;
|
|
}
|
|
}
|
|
else if (category.fullName == "Math")
|
|
{
|
|
foreach (var mathChild in MathChildren())
|
|
{
|
|
yield return mathChild;
|
|
}
|
|
}
|
|
else if (category.fullName == "Time")
|
|
{
|
|
foreach (var timeChild in TimeChildren())
|
|
{
|
|
yield return timeChild;
|
|
}
|
|
}
|
|
else if (category.fullName == "Nesting")
|
|
{
|
|
foreach (var nestingChild in NestingChildren())
|
|
{
|
|
yield return nestingChild;
|
|
}
|
|
}
|
|
else if (category.fullName == "Graphs")
|
|
{
|
|
foreach (var macroChild in MacroChildren())
|
|
{
|
|
yield return macroChild;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable<object> EventsChildren(UnitCategory category)
|
|
{
|
|
foreach (var unit in options.Where(option => option.UnitIs<IEventUnit>() && option.category == category)
|
|
.OrderBy(option => option.order)
|
|
.ThenBy(option => option.label))
|
|
{
|
|
yield return unit;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Search
|
|
|
|
public override bool searchable { get; } = true;
|
|
|
|
public override IEnumerable<ISearchResult> SearchResults(string query, CancellationToken cancellation)
|
|
{
|
|
foreach (var typeResult in typesWithMembers.Cancellable(cancellation).OrderableSearchFilter(query, t => t.DisplayName()))
|
|
{
|
|
yield return typeResult;
|
|
}
|
|
|
|
foreach (var optionResult in options.Cancellable(cancellation)
|
|
.OrderableSearchFilter(query, o => o.haystack, o => o.formerHaystack)
|
|
.WithoutInheritedDuplicates(r => r.result, cancellation))
|
|
{
|
|
yield return optionResult;
|
|
}
|
|
}
|
|
|
|
public override string SearchResultLabel(object item, string query)
|
|
{
|
|
if (item is Type type)
|
|
{
|
|
return TypeOption.SearchResultLabel(type, query);
|
|
}
|
|
else if (item is IUnitOption unitOption)
|
|
{
|
|
return unitOption.SearchResultLabel(query);
|
|
}
|
|
else
|
|
{
|
|
return base.SearchResultLabel(item, query);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
#region Favorites
|
|
|
|
public override ICollection<object> favorites { get; }
|
|
|
|
public override bool CanFavorite(object item)
|
|
{
|
|
return (item as IUnitOption)?.favoritable ?? false;
|
|
}
|
|
|
|
public override string FavoritesLabel(object item)
|
|
{
|
|
return SearchResultLabel(item, null);
|
|
}
|
|
|
|
public override void OnFavoritesChange()
|
|
{
|
|
BoltFlow.Configuration.Save();
|
|
}
|
|
|
|
private class Favorites : ICollection<object>
|
|
{
|
|
public Favorites(UnitOptionTree tree)
|
|
{
|
|
this.tree = tree;
|
|
}
|
|
|
|
private UnitOptionTree tree { get; }
|
|
|
|
private IEnumerable<IUnitOption> options => tree.options.Where(option => BoltFlow.Configuration.favoriteUnitOptions.Contains(option.favoriteKey));
|
|
|
|
public bool IsReadOnly => false;
|
|
|
|
public int Count => BoltFlow.Configuration.favoriteUnitOptions.Count;
|
|
|
|
public IEnumerator<object> GetEnumerator()
|
|
{
|
|
foreach (var option in options)
|
|
{
|
|
yield return option;
|
|
}
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public bool Contains(object item)
|
|
{
|
|
var option = (IUnitOption)item;
|
|
|
|
return BoltFlow.Configuration.favoriteUnitOptions.Contains(option.favoriteKey);
|
|
}
|
|
|
|
public void Add(object item)
|
|
{
|
|
var option = (IUnitOption)item;
|
|
|
|
BoltFlow.Configuration.favoriteUnitOptions.Add(option.favoriteKey);
|
|
}
|
|
|
|
public bool Remove(object item)
|
|
{
|
|
var option = (IUnitOption)item;
|
|
|
|
return BoltFlow.Configuration.favoriteUnitOptions.Remove(option.favoriteKey);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
BoltFlow.Configuration.favoriteUnitOptions.Clear();
|
|
}
|
|
|
|
public void CopyTo(object[] array, int arrayIndex)
|
|
{
|
|
if (array == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(array));
|
|
}
|
|
|
|
if (arrayIndex < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
|
}
|
|
|
|
if (array.Length - arrayIndex < Count)
|
|
{
|
|
throw new ArgumentException();
|
|
}
|
|
|
|
var i = 0;
|
|
|
|
foreach (var item in this)
|
|
{
|
|
array[i + arrayIndex] = item;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|