A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Linq; 5using System.Threading; 6using UnityEngine; 7using UnityObject = UnityEngine.Object; 8 9namespace Unity.VisualScripting 10{ 11 public class UnitOptionTree : ExtensibleFuzzyOptionTree 12 { 13 #region Initialization 14 15 public UnitOptionTree(GUIContent label) : base(label) 16 { 17 favorites = new Favorites(this); 18 19 showBackgroundWorkerProgress = true; 20 } 21 22 public override IFuzzyOption Option(object item) 23 { 24 if (item is Namespace @namespace) 25 { 26 return new NamespaceOption(@namespace, true); 27 } 28 29 if (item is Type type) 30 { 31 return new TypeOption(type, true); 32 } 33 34 return base.Option(item); 35 } 36 37 public override void Prewarm() 38 { 39 filter = filter ?? UnitOptionFilter.Any; 40 41 try 42 { 43 options = new HashSet<IUnitOption>(UnitBase.Subset(filter, reference)); 44 } 45 catch (Exception ex) 46 { 47 Debug.LogError($"Failed to fetch node options for fuzzy finder (error log below).\nTry rebuilding the node options from '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n\n{ex}"); 48 options = new HashSet<IUnitOption>(); 49 } 50 51 typesWithMembers = new HashSet<Type>(); 52 53 foreach (var option in options) 54 { 55 if (option is IMemberUnitOption memberUnitOption && memberUnitOption.targetType != null) 56 { 57 typesWithMembers.Add(memberUnitOption.targetType); 58 } 59 } 60 } 61 62 private HashSet<IUnitOption> options; 63 64 private HashSet<Type> typesWithMembers; 65 66 #endregion 67 68 69 #region Configuration 70 71 public UnitOptionFilter filter { get; set; } 72 public GraphReference reference { get; set; } 73 public bool includeNone { get; set; } 74 public bool surfaceCommonTypeLiterals { get; set; } 75 public object[] rootOverride { get; set; } 76 77 public FlowGraph graph => reference.graph as FlowGraph; 78 public GameObject self => reference.self; 79 80 public ActionDirection direction { get; set; } = ActionDirection.Any; 81 82 #endregion 83 84 85 #region Hierarchy 86 87 private readonly FuzzyGroup enumsGroup = new FuzzyGroup("(Enums)", typeof(Enum).Icon()); 88 private readonly FuzzyGroup selfGroup = new FuzzyGroup("This", typeof(GameObject).Icon()); 89 90 private IEnumerable<UnitCategory> SpecialCategories() 91 { 92 yield return new UnitCategory("Codebase"); 93 yield return new UnitCategory("Events"); 94 yield return new UnitCategory("Variables"); 95 yield return new UnitCategory("Math"); 96 yield return new UnitCategory("Nesting"); 97 yield return new UnitCategory("Graphs"); 98 } 99 100 public override IEnumerable<object> Root() 101 { 102 if (rootOverride != null && rootOverride.Length > 0) 103 { 104 foreach (var item in rootOverride) 105 { 106 yield return item; 107 } 108 109 yield break; 110 } 111 112 if (filter.CompatibleOutputType != null) 113 { 114 var outputType = filter.CompatibleOutputType; 115 116 var outputTypeLiteral = options.FirstOrDefault(option => option is LiteralOption literalOption && literalOption.literalType == outputType); 117 118 if (outputTypeLiteral != null) 119 { 120 yield return outputTypeLiteral; 121 } 122 123 HashSet<Type> noSurfaceConstructors = new HashSet<Type>() 124 { 125 typeof(string), 126 typeof(object) 127 }; 128 129 if (!noSurfaceConstructors.Contains(outputType)) 130 { 131 var outputTypeConstructors = options.Where(option => option is InvokeMemberOption invokeMemberOption && 132 invokeMemberOption.targetType == outputType && 133 invokeMemberOption.unit.member.isConstructor); 134 135 foreach (var outputTypeConstructor in outputTypeConstructors) 136 { 137 yield return outputTypeConstructor; 138 } 139 } 140 141 if (outputType == typeof(bool)) 142 { 143 foreach (var logicOperation in CategoryChildren(new UnitCategory("Logic"))) 144 { 145 yield return logicOperation; 146 } 147 } 148 149 if (outputType.IsNumeric()) 150 { 151 foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Scalar"))) 152 { 153 yield return mathOperation; 154 } 155 } 156 157 if (outputType == typeof(Vector2)) 158 { 159 foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 2"))) 160 { 161 yield return mathOperation; 162 } 163 } 164 165 if (outputType == typeof(Vector3)) 166 { 167 foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 3"))) 168 { 169 yield return mathOperation; 170 } 171 } 172 173 if (outputType == typeof(Vector4)) 174 { 175 foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 4"))) 176 { 177 yield return mathOperation; 178 } 179 } 180 } 181 182 if (surfaceCommonTypeLiterals) 183 { 184 foreach (var commonType in EditorTypeUtility.commonTypes) 185 { 186 if (commonType == filter.CompatibleOutputType) 187 { 188 continue; 189 } 190 191 var commonTypeLiteral = options.FirstOrDefault(option => option is LiteralOption literalOption && literalOption.literalType == commonType); 192 193 if (commonTypeLiteral != null) 194 { 195 yield return commonTypeLiteral; 196 } 197 } 198 } 199 200 if (filter.CompatibleInputType != null) 201 { 202 var inputType = filter.CompatibleInputType; 203 204 if (!inputType.IsPrimitive && inputType != typeof(object)) 205 { 206 yield return inputType; 207 } 208 209 if (inputType == typeof(bool)) 210 { 211 yield return options.Single(o => o.UnitIs<If>()); 212 yield return options.Single(o => o.UnitIs<SelectUnit>()); 213 } 214 215 if (inputType == typeof(bool) || inputType.IsNumeric()) 216 { 217 foreach (var logicOperation in CategoryChildren(new UnitCategory("Logic"))) 218 { 219 yield return logicOperation; 220 } 221 } 222 223 if (inputType.IsNumeric()) 224 { 225 foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Scalar"))) 226 { 227 yield return mathOperation; 228 } 229 } 230 231 if (inputType == typeof(Vector2)) 232 { 233 foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 2"))) 234 { 235 yield return mathOperation; 236 } 237 } 238 239 if (inputType == typeof(Vector3)) 240 { 241 foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 3"))) 242 { 243 yield return mathOperation; 244 } 245 } 246 247 if (inputType == typeof(Vector4)) 248 { 249 foreach (var mathOperation in CategoryChildren(new UnitCategory("Math/Vector 4"))) 250 { 251 yield return mathOperation; 252 } 253 } 254 255 if (typeof(IEnumerable).IsAssignableFrom(inputType) && (inputType != typeof(string) && inputType != typeof(Transform))) 256 { 257 foreach (var mathOperation in CategoryChildren(new UnitCategory("Collections"), false)) 258 { 259 yield return mathOperation; 260 } 261 } 262 263 if (typeof(IList).IsAssignableFrom(inputType)) 264 { 265 foreach (var listOperation in CategoryChildren(new UnitCategory("Collections/Lists"))) 266 { 267 yield return listOperation; 268 } 269 } 270 271 if (typeof(IDictionary).IsAssignableFrom(inputType)) 272 { 273 foreach (var dictionaryOperation in CategoryChildren(new UnitCategory("Collections/Dictionaries"))) 274 { 275 yield return dictionaryOperation; 276 } 277 } 278 } 279 280 if (filter.NoConnection) 281 { 282 yield return new StickyNoteOption(); 283 } 284 285 if (UnityAPI.Await 286 ( 287 () => 288 { 289 if (self != null) 290 { 291 selfGroup.label = self.name; 292 selfGroup.icon = self.Icon(); 293 return true; 294 } 295 296 return false; 297 } 298 ) 299 ) 300 { 301 yield return selfGroup; 302 } 303 304 foreach (var category in options.Select(option => option.category?.root) 305 .NotNull() 306 .Concat(SpecialCategories()) 307 .Distinct() 308 .OrderBy(c => c.name)) 309 { 310 yield return category; 311 } 312 313 foreach (var extensionRootItem in base.Root()) 314 { 315 yield return extensionRootItem; 316 } 317 318 if (filter.Self) 319 { 320 var self = options.FirstOrDefault(option => option.UnitIs<This>()); 321 322 if (self != null) 323 { 324 yield return self; 325 } 326 } 327 328 foreach (var unit in CategoryChildren(null)) 329 { 330 yield return unit; 331 } 332 333 if (includeNone) 334 { 335 yield return null; 336 } 337 } 338 339 public override IEnumerable<object> Children(object parent) 340 { 341 if (parent is Namespace @namespace) 342 { 343 return NamespaceChildren(@namespace); 344 } 345 else if (parent is Type type) 346 { 347 return TypeChildren(type); 348 } 349 else if (parent == enumsGroup) 350 { 351 return EnumsChildren(); 352 } 353 else if (parent == selfGroup) 354 { 355 return SelfChildren(); 356 } 357 else if (parent is UnitCategory unitCategory) 358 { 359 return CategoryChildren(unitCategory); 360 } 361 else if (parent is VariableKind variableKind) 362 { 363 return VariableKindChildren(variableKind); 364 } 365 else 366 { 367 return base.Children(parent); 368 } 369 } 370 371 private IEnumerable<object> SelfChildren() 372 { 373 yield return typeof(GameObject); 374 375 // Self components can be null if no script is assigned to them 376 // https://support.ludiq.io/forums/5-bolt/topics/817-/ 377 foreach (var selfComponentType in UnityAPI.Await(() => self.GetComponents<Component>().NotUnityNull().Select(c => c.GetType()))) 378 { 379 yield return selfComponentType; 380 } 381 } 382 383 private IEnumerable<object> CodebaseChildren() 384 { 385 foreach (var rootNamespace in typesWithMembers.Where(t => !t.IsEnum) 386 .Select(t => t.Namespace().Root) 387 .OrderBy(ns => ns.DisplayName(false)) 388 .Distinct()) 389 { 390 yield return rootNamespace; 391 } 392 393 if (filter.Literals && options.Any(option => option is LiteralOption literalOption && literalOption.literalType.IsEnum)) 394 { 395 yield return enumsGroup; 396 } 397 } 398 399 private IEnumerable<object> MathChildren() 400 { 401 foreach (var mathMember in GetMembers(typeof(Mathf)).Where(option => !((MemberUnit)option.unit).member.requiresTarget)) 402 { 403 yield return mathMember; 404 } 405 } 406 407 private IEnumerable<object> TimeChildren() 408 { 409 foreach (var timeMember in GetMembers(typeof(Time)).Where(option => !((MemberUnit)option.unit).member.requiresTarget)) 410 { 411 yield return timeMember; 412 } 413 } 414 415 private IEnumerable<object> NestingChildren() 416 { 417 foreach (var nester in options.Where(option => option.UnitIs<IGraphNesterElement>() && ((IGraphNesterElement)option.unit).nest.macro == null) 418 .OrderBy(option => option.label)) 419 { 420 yield return nester; 421 } 422 } 423 424 private IEnumerable<object> MacroChildren() 425 { 426 foreach (var macroNester in options.Where(option => option.UnitIs<IGraphNesterElement>() && ((IGraphNesterElement)option.unit).nest.macro != null) 427 .OrderBy(option => option.label)) 428 { 429 yield return macroNester; 430 } 431 } 432 433 private IEnumerable<object> VariablesChildren() 434 { 435 yield return VariableKind.Flow; 436 yield return VariableKind.Graph; 437 yield return VariableKind.Object; 438 yield return VariableKind.Scene; 439 yield return VariableKind.Application; 440 yield return VariableKind.Saved; 441 } 442 443 private IEnumerable<object> VariableKindChildren(VariableKind kind) 444 { 445 foreach (var variable in options.OfType<IUnifiedVariableUnitOption>() 446 .Where(option => option.kind == kind) 447 .OrderBy(option => option.name)) 448 { 449 yield return variable; 450 } 451 } 452 453 private IEnumerable<object> NamespaceChildren(Namespace @namespace) 454 { 455 foreach (var childNamespace in GetChildrenNamespaces(@namespace)) 456 { 457 yield return childNamespace; 458 } 459 460 foreach (var type in GetNamespaceTypes(@namespace)) 461 { 462 yield return type; 463 } 464 } 465 466 private IEnumerable<Namespace> GetChildrenNamespaces(Namespace @namespace) 467 { 468 if (!@namespace.IsGlobal) 469 { 470 foreach (var childNamespace in typesWithMembers.Where(t => !t.IsEnum) 471 .SelectMany(t => t.Namespace().AndAncestors()) 472 .Distinct() 473 .Where(ns => ns.Parent == @namespace) 474 .OrderBy(ns => ns.DisplayName(false))) 475 { 476 yield return childNamespace; 477 } 478 } 479 } 480 481 private IEnumerable<Type> GetNamespaceTypes(Namespace @namespace) 482 { 483 foreach (var type in typesWithMembers.Where(t => t.Namespace() == @namespace && !t.IsEnum) 484 .OrderBy(t => t.DisplayName())) 485 { 486 yield return type; 487 } 488 } 489 490 private IEnumerable<object> TypeChildren(Type type) 491 { 492 foreach (var literal in options.Where(option => option is LiteralOption literalOption && literalOption.literalType == type)) 493 { 494 yield return literal; 495 } 496 497 foreach (var expose in options.Where(option => option is ExposeOption exposeOption && exposeOption.exposedType == type)) 498 { 499 yield return expose; 500 } 501 502 if (type.IsStruct()) 503 { 504 foreach (var createStruct in options.Where(option => option is CreateStructOption createStructOption && createStructOption.structType == type)) 505 { 506 yield return createStruct; 507 } 508 } 509 510 foreach (var member in GetMembers(type)) 511 { 512 yield return member; 513 } 514 } 515 516 private IEnumerable<IUnitOption> GetMembers(Type type) 517 { 518 foreach (var member in options.Where(option => option is IMemberUnitOption memberUnitOption && memberUnitOption.targetType == type && option.unit.canDefine) 519 .OrderBy(option => BoltCore.Configuration.groupInheritedMembers && ((MemberUnit)option.unit).member.isPseudoInherited) 520 .ThenBy(option => option.order) 521 .ThenBy(option => option.label)) 522 { 523 yield return member; 524 } 525 } 526 527 private IEnumerable<object> EnumsChildren() 528 { 529 foreach (var literal in options.Where(option => option is LiteralOption literalOption && literalOption.literalType.IsEnum) 530 .OrderBy(option => option.label)) 531 { 532 yield return literal; 533 } 534 } 535 536 private IEnumerable<object> CategoryChildren(UnitCategory category, bool subCategories = true) 537 { 538 if (category != null && subCategories) 539 { 540 foreach (var subCategory in options.SelectMany(option => option.category == null ? Enumerable.Empty<UnitCategory>() : option.category.AndAncestors()) 541 .Distinct() 542 .Where(c => c.parent == category) 543 .OrderBy(c => c.name)) 544 { 545 yield return subCategory; 546 } 547 } 548 549 foreach (var unit in options.Where(option => option.category == category) 550 .Where(option => !option.unitType.HasAttribute<SpecialUnitAttribute>()) 551 .OrderBy(option => option.order) 552 .ThenBy(option => option.label)) 553 { 554 yield return unit; 555 } 556 557 if (category != null) 558 { 559 if (category.root.name == "Events") 560 { 561 foreach (var eventChild in EventsChildren(category)) 562 { 563 yield return eventChild; 564 } 565 } 566 else if (category.fullName == "Codebase") 567 { 568 foreach (var codebaseChild in CodebaseChildren()) 569 { 570 yield return codebaseChild; 571 } 572 } 573 else if (category.fullName == "Variables") 574 { 575 foreach (var variableChild in VariablesChildren()) 576 { 577 yield return variableChild; 578 } 579 } 580 else if (category.fullName == "Math") 581 { 582 foreach (var mathChild in MathChildren()) 583 { 584 yield return mathChild; 585 } 586 } 587 else if (category.fullName == "Time") 588 { 589 foreach (var timeChild in TimeChildren()) 590 { 591 yield return timeChild; 592 } 593 } 594 else if (category.fullName == "Nesting") 595 { 596 foreach (var nestingChild in NestingChildren()) 597 { 598 yield return nestingChild; 599 } 600 } 601 else if (category.fullName == "Graphs") 602 { 603 foreach (var macroChild in MacroChildren()) 604 { 605 yield return macroChild; 606 } 607 } 608 } 609 } 610 611 private IEnumerable<object> EventsChildren(UnitCategory category) 612 { 613 foreach (var unit in options.Where(option => option.UnitIs<IEventUnit>() && option.category == category) 614 .OrderBy(option => option.order) 615 .ThenBy(option => option.label)) 616 { 617 yield return unit; 618 } 619 } 620 621 #endregion 622 623 624 #region Search 625 626 public override bool searchable { get; } = true; 627 628 public override IEnumerable<ISearchResult> SearchResults(string query, CancellationToken cancellation) 629 { 630 foreach (var typeResult in typesWithMembers.Cancellable(cancellation).OrderableSearchFilter(query, t => t.DisplayName())) 631 { 632 yield return typeResult; 633 } 634 635 foreach (var optionResult in options.Cancellable(cancellation) 636 .OrderableSearchFilter(query, o => o.haystack, o => o.formerHaystack) 637 .WithoutInheritedDuplicates(r => r.result, cancellation)) 638 { 639 yield return optionResult; 640 } 641 } 642 643 public override string SearchResultLabel(object item, string query) 644 { 645 if (item is Type type) 646 { 647 return TypeOption.SearchResultLabel(type, query); 648 } 649 else if (item is IUnitOption unitOption) 650 { 651 return unitOption.SearchResultLabel(query); 652 } 653 else 654 { 655 return base.SearchResultLabel(item, query); 656 } 657 } 658 659 #endregion 660 661 662 #region Favorites 663 664 public override ICollection<object> favorites { get; } 665 666 public override bool CanFavorite(object item) 667 { 668 return (item as IUnitOption)?.favoritable ?? false; 669 } 670 671 public override string FavoritesLabel(object item) 672 { 673 return SearchResultLabel(item, null); 674 } 675 676 public override void OnFavoritesChange() 677 { 678 BoltFlow.Configuration.SetDirty(); 679 BoltFlow.Configuration.Save(); 680 } 681 682 private class Favorites : ICollection<object> 683 { 684 public Favorites(UnitOptionTree tree) 685 { 686 this.tree = tree; 687 } 688 689 private UnitOptionTree tree { get; } 690 691 private IEnumerable<IUnitOption> options => tree.options.Where(option => BoltFlow.Configuration.favoriteUnitOptions.Contains(option.favoriteKey)); 692 693 public bool IsReadOnly => false; 694 695 public int Count => BoltFlow.Configuration.favoriteUnitOptions.Count; 696 697 public IEnumerator<object> GetEnumerator() 698 { 699 foreach (var option in options) 700 { 701 yield return option; 702 } 703 } 704 705 IEnumerator IEnumerable.GetEnumerator() 706 { 707 return GetEnumerator(); 708 } 709 710 public bool Contains(object item) 711 { 712 var option = (IUnitOption)item; 713 714 return BoltFlow.Configuration.favoriteUnitOptions.Contains(option.favoriteKey); 715 } 716 717 public void Add(object item) 718 { 719 var option = (IUnitOption)item; 720 721 BoltFlow.Configuration.favoriteUnitOptions.Add(option.favoriteKey); 722 } 723 724 public bool Remove(object item) 725 { 726 var option = (IUnitOption)item; 727 728 return BoltFlow.Configuration.favoriteUnitOptions.Remove(option.favoriteKey); 729 } 730 731 public void Clear() 732 { 733 BoltFlow.Configuration.favoriteUnitOptions.Clear(); 734 } 735 736 public void CopyTo(object[] array, int arrayIndex) 737 { 738 if (array == null) 739 { 740 throw new ArgumentNullException(nameof(array)); 741 } 742 743 if (arrayIndex < 0) 744 { 745 throw new ArgumentOutOfRangeException(nameof(arrayIndex)); 746 } 747 748 if (array.Length - arrayIndex < Count) 749 { 750 throw new ArgumentException(); 751 } 752 753 var i = 0; 754 755 foreach (var item in this) 756 { 757 array[i + arrayIndex] = item; 758 i++; 759 } 760 } 761 } 762 763 #endregion 764 } 765}