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}