Maze / Library /PackageCache /com.unity.visualscripting@1.8.0 /Editor /VisualScripting.Flow /Options /UnitOptionTree.cs
| 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 | |
| { | |
| 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; | |
| 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; | |
| 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; | |
| } | |
| } | |
| 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); | |
| } | |
| } | |
| 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++; | |
| } | |
| } | |
| } | |
| } | |
| } | |