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(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(); } typesWithMembers = new HashSet(); foreach (var option in options) { if (option is IMemberUnitOption memberUnitOption && memberUnitOption.targetType != null) { typesWithMembers.Add(memberUnitOption.targetType); } } } private HashSet options; private HashSet 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 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 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 noSurfaceConstructors = new HashSet() { 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()); yield return options.Single(o => o.UnitIs()); } 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()); if (self != null) { yield return self; } } foreach (var unit in CategoryChildren(null)) { yield return unit; } if (includeNone) { yield return null; } } public override IEnumerable 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 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().NotUnityNull().Select(c => c.GetType()))) { yield return selfComponentType; } } private IEnumerable 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 MathChildren() { foreach (var mathMember in GetMembers(typeof(Mathf)).Where(option => !((MemberUnit)option.unit).member.requiresTarget)) { yield return mathMember; } } private IEnumerable TimeChildren() { foreach (var timeMember in GetMembers(typeof(Time)).Where(option => !((MemberUnit)option.unit).member.requiresTarget)) { yield return timeMember; } } private IEnumerable NestingChildren() { foreach (var nester in options.Where(option => option.UnitIs() && ((IGraphNesterElement)option.unit).nest.macro == null) .OrderBy(option => option.label)) { yield return nester; } } private IEnumerable MacroChildren() { foreach (var macroNester in options.Where(option => option.UnitIs() && ((IGraphNesterElement)option.unit).nest.macro != null) .OrderBy(option => option.label)) { yield return macroNester; } } private IEnumerable 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 VariableKindChildren(VariableKind kind) { foreach (var variable in options.OfType() .Where(option => option.kind == kind) .OrderBy(option => option.name)) { yield return variable; } } private IEnumerable NamespaceChildren(Namespace @namespace) { foreach (var childNamespace in GetChildrenNamespaces(@namespace)) { yield return childNamespace; } foreach (var type in GetNamespaceTypes(@namespace)) { yield return type; } } private IEnumerable 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 GetNamespaceTypes(Namespace @namespace) { foreach (var type in typesWithMembers.Where(t => t.Namespace() == @namespace && !t.IsEnum) .OrderBy(t => t.DisplayName())) { yield return type; } } private IEnumerable 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 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 EnumsChildren() { foreach (var literal in options.Where(option => option is LiteralOption literalOption && literalOption.literalType.IsEnum) .OrderBy(option => option.label)) { yield return literal; } } private IEnumerable CategoryChildren(UnitCategory category, bool subCategories = true) { if (category != null && subCategories) { foreach (var subCategory in options.SelectMany(option => option.category == null ? Enumerable.Empty() : 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()) .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 EventsChildren(UnitCategory category) { foreach (var unit in options.Where(option => option.UnitIs() && 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 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 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.SetDirty(); BoltFlow.Configuration.Save(); } private class Favorites : ICollection { public Favorites(UnitOptionTree tree) { this.tree = tree; } private UnitOptionTree tree { get; } private IEnumerable options => tree.options.Where(option => BoltFlow.Configuration.favoriteUnitOptions.Contains(option.favoriteKey)); public bool IsReadOnly => false; public int Count => BoltFlow.Configuration.favoriteUnitOptions.Count; public IEnumerator 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 } }