A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.IO; 4using System.Linq; 5using System.Text; 6using System.Threading; 7using Unity.VisualScripting.Dependencies.Sqlite; 8using UnityEditor; 9using UnityEngine; 10 11namespace Unity.VisualScripting 12{ 13 [InitializeAfterPlugins] 14 public static class UnitBase 15 { 16 static UnitBase() 17 { 18 staticUnitsExtensions = new NonNullableList<Func<IEnumerable<IUnitOption>>>(); 19 dynamicUnitsExtensions = new NonNullableList<Func<IEnumerable<IUnitOption>>>(); 20 contextualUnitsExtensions = new NonNullableList<Func<GraphReference, IEnumerable<IUnitOption>>>(); 21 BackgroundWorker.Schedule(AutoLoad); 22 } 23 24 private static readonly object @lock = new object(); 25 26 private static HashSet<IUnitOption> options; 27 28 private static void AutoLoad() 29 { 30 lock (@lock) 31 { 32 // If the fuzzy finder was opened really fast, 33 // a load operation might already have started. 34 if (options == null) 35 { 36 Load(); 37 } 38 } 39 } 40 41 private static void Load() 42 { 43 if (IsUnitOptionsBuilt()) 44 { 45 // Update before loading if required, ensuring no "in-between" state 46 // where the loaded options are not yet loaded. 47 // The update code will not touch the options array if it is null. 48 if (BoltFlow.Configuration.updateNodesAutomatically) 49 { 50 try 51 { 52 ProgressUtility.DisplayProgressBar("Checking for codebase changes...", null, 0); 53 54 if (requiresUpdate) 55 { 56 Update(); 57 } 58 } 59 catch (Exception ex) 60 { 61 Debug.LogError($"Failed to update node options.\nRetry with '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n{ex}"); 62 } 63 finally 64 { 65 ProgressUtility.ClearProgressBar(); 66 } 67 } 68 69 lock (@lock) 70 { 71 using (ProfilingUtility.SampleBlock("Load Node Database")) 72 { 73 using (NativeUtility.Module("sqlite3.dll")) 74 { 75 ProgressUtility.DisplayProgressBar("Loading node database...", null, 0); 76 77 SQLiteConnection database = null; 78 79 try 80 { 81 database = new SQLiteConnection(BoltFlow.Paths.unitOptions, SQLiteOpenFlags.ReadOnly); 82 83 int total; 84 85 total = database.Table<UnitOptionRow>().Count(); 86 87 var progress = 0f; 88 89 options = new HashSet<IUnitOption>(); 90 91 var failedOptions = new Dictionary<UnitOptionRow, Exception>(); 92 93 foreach (var row in database.Table<UnitOptionRow>()) 94 { 95 try 96 { 97 var option = row.ToOption(); 98 99 options.Add(option); 100 } 101 catch (Exception rowEx) 102 { 103 failedOptions.Add(row, rowEx); 104 } 105 106 ProgressUtility.DisplayProgressBar("Loading node database...", BoltCore.Configuration.humanNaming ? row.labelHuman : row.labelProgrammer, progress++ / total); 107 } 108 109 if (failedOptions.Count > 0) 110 { 111 var sb = new StringBuilder(); 112 113 sb.AppendLine($"{failedOptions.Count} node options failed to load and were skipped."); 114 sb.AppendLine($"Try rebuilding the node options with '{UnitOptionUtility.GenerateUnitDatabasePath}' to purge outdated nodes."); 115 sb.AppendLine(); 116 117 foreach (var failedOption in failedOptions) 118 { 119 sb.AppendLine(failedOption.Key.favoriteKey); 120 } 121 122 sb.AppendLine(); 123 124 foreach (var failedOption in failedOptions) 125 { 126 sb.AppendLine(failedOption.Key.favoriteKey + ": "); 127 sb.AppendLine(failedOption.Value.ToString()); 128 sb.AppendLine(); 129 } 130 131 Debug.LogWarning(sb.ToString()); 132 } 133 } 134 catch (Exception ex) 135 { 136 options = new HashSet<IUnitOption>(); 137 Debug.LogError($"Failed to load node options.\nTry to rebuild them with '{UnitOptionUtility.GenerateUnitDatabasePath}'.\n\n{ex}"); 138 } 139 finally 140 { 141 database?.Close(); 142 //ConsoleProfiler.Dump(); 143 ProgressUtility.ClearProgressBar(); 144 } 145 } 146 } 147 } 148 } 149 } 150 151 private static bool IsUnitOptionsBuilt() 152 { 153 return File.Exists(BoltFlow.Paths.unitOptions); 154 } 155 156 public static void Rebuild() 157 { 158 if (IsUnitOptionsBuilt()) 159 { 160 VersionControlUtility.Unlock(BoltFlow.Paths.unitOptions); 161 File.Delete(BoltFlow.Paths.unitOptions); 162 } 163 164 Build(); 165 } 166 167 public static void Build(bool initialBuild = false) 168 { 169 if (IsUnitOptionsBuilt()) return; 170 171 if (initialBuild) 172 { 173 ProgressUtility.SetTitleOverride("Visual Scripting: Initial Node Generation..."); 174 } 175 176 const string progressTitle = "Visual Scripting: Building node database..."; 177 178 lock (@lock) 179 { 180 using (ProfilingUtility.SampleBlock("Update Node Database")) 181 { 182 using (NativeUtility.Module("sqlite3.dll")) 183 { 184 SQLiteConnection database = null; 185 186 try 187 { 188 ProgressUtility.DisplayProgressBar(progressTitle, "Creating database...", 0); 189 190 PathUtility.CreateParentDirectoryIfNeeded(BoltFlow.Paths.unitOptions); 191 database = new SQLiteConnection(BoltFlow.Paths.unitOptions); 192 database.CreateTable<UnitOptionRow>(); 193 194 ProgressUtility.DisplayProgressBar(progressTitle, "Updating codebase...", 0); 195 196 UpdateCodebase(); 197 198 ProgressUtility.DisplayProgressBar(progressTitle, "Updating type mappings...", 0); 199 200 UpdateTypeMappings(); 201 202 ProgressUtility.DisplayProgressBar(progressTitle, 203 "Converting codebase to node options...", 0); 204 205 options = new HashSet<IUnitOption>(GetStaticOptions()); 206 207 var rows = new HashSet<UnitOptionRow>(); 208 209 var progress = 0; 210 var lastShownProgress = 0f; 211 212 foreach (var option in options) 213 { 214 try 215 { 216 var shownProgress = (float)progress / options.Count; 217 218 if (shownProgress > lastShownProgress + 0.01f) 219 { 220 ProgressUtility.DisplayProgressBar(progressTitle, 221 "Converting codebase to node options...", shownProgress); 222 lastShownProgress = shownProgress; 223 } 224 225 rows.Add(option.Serialize()); 226 } 227 catch (Exception ex) 228 { 229 Debug.LogError($"Failed to save option '{option.GetType()}'.\n{ex}"); 230 } 231 232 progress++; 233 } 234 235 ProgressUtility.DisplayProgressBar(progressTitle, "Writing to database...", 1); 236 237 try 238 { 239 database.CreateTable<UnitOptionRow>(); 240 database.InsertAll(rows); 241 } 242 catch (Exception ex) 243 { 244 Debug.LogError($"Failed to write options to database.\n{ex}"); 245 } 246 } 247 finally 248 { 249 database?.Close(); 250 ProgressUtility.ClearProgressBar(); 251 ProgressUtility.ClearTitleOverride(); 252 AssetDatabase.Refresh(); 253 //ConsoleProfiler.Dump(); 254 } 255 } 256 } 257 } 258 } 259 260 public static void Update() 261 { 262 if (!IsUnitOptionsBuilt()) 263 { 264 Build(); 265 return; 266 } 267 268 lock (@lock) 269 { 270 using (ProfilingUtility.SampleBlock("Update Node Database")) 271 { 272 using (NativeUtility.Module("sqlite3.dll")) 273 { 274 var progressTitle = "Updating node database..."; 275 276 SQLiteConnection database = null; 277 278 try 279 { 280 VersionControlUtility.Unlock(BoltFlow.Paths.unitOptions); 281 282 var steps = 7f; 283 var step = 0f; 284 285 ProgressUtility.DisplayProgressBar(progressTitle, "Connecting to database...", step++ / steps); 286 287 database = new SQLiteConnection(BoltFlow.Paths.unitOptions); 288 289 ProgressUtility.DisplayProgressBar(progressTitle, "Updating type mappings...", step++ / steps); 290 291 UpdateTypeMappings(); 292 293 ProgressUtility.DisplayProgressBar(progressTitle, "Fetching modified scripts...", step++ / steps); 294 295 var modifiedScriptGuids = GetModifiedScriptGuids().Distinct().ToHashSet(); 296 297 ProgressUtility.DisplayProgressBar(progressTitle, "Fetching deleted scripts...", step++ / steps); 298 299 var deletedScriptGuids = GetDeletedScriptGuids().Distinct().ToHashSet(); 300 301 ProgressUtility.DisplayProgressBar(progressTitle, "Updating codebase...", step++ / steps); 302 303 var modifiedScriptTypes = modifiedScriptGuids.SelectMany(GetScriptTypes).ToArray(); 304 305 UpdateCodebase(modifiedScriptTypes); 306 307 var outdatedScriptGuids = new HashSet<string>(); 308 outdatedScriptGuids.UnionWith(modifiedScriptGuids); 309 outdatedScriptGuids.UnionWith(deletedScriptGuids); 310 311 ProgressUtility.DisplayProgressBar(progressTitle, "Removing outdated node options...", step++ / steps); 312 313 options?.RemoveWhere(option => outdatedScriptGuids.Overlaps(option.sourceScriptGuids)); 314 315 // We want to use the database level WHERE here for speed, 316 // so we'll run multiple queries, one for each outdated script GUID. 317 318 foreach (var outdatedScriptGuid in outdatedScriptGuids) 319 { 320 foreach (var outdatedRowId in database.Table<UnitOptionRow>() 321 .Where(row => row.sourceScriptGuids.Contains(outdatedScriptGuid)) 322 .Select(row => row.id)) 323 { 324 database.Delete<UnitOptionRow>(outdatedRowId); 325 } 326 } 327 328 ProgressUtility.DisplayProgressBar(progressTitle, "Converting codebase to node options...", step++ / steps); 329 330 var newOptions = new HashSet<IUnitOption>(modifiedScriptGuids.SelectMany(GetScriptTypes) 331 .Distinct() 332 .SelectMany(GetIncrementalOptions)); 333 334 var rows = new HashSet<UnitOptionRow>(); 335 336 float progress = 0; 337 338 foreach (var newOption in newOptions) 339 { 340 options?.Add(newOption); 341 342 try 343 { 344 ProgressUtility.DisplayProgressBar(progressTitle, newOption.label, (step / steps) + ((1 / step) * (progress / newOptions.Count))); 345 rows.Add(newOption.Serialize()); 346 } 347 catch (Exception ex) 348 { 349 Debug.LogError($"Failed to serialize option '{newOption.GetType()}'.\n{ex}"); 350 } 351 352 progress++; 353 } 354 355 ProgressUtility.DisplayProgressBar(progressTitle, "Writing to database...", 1); 356 357 try 358 { 359 database.InsertAll(rows); 360 } 361 catch (Exception ex) 362 { 363 Debug.LogError($"Failed to write options to database.\n{ex}"); 364 } 365 366 // Make sure the database is touched to the current date, 367 // even if we didn't do any change. This will avoid unnecessary 368 // analysis in future update checks. 369 File.SetLastWriteTimeUtc(BoltFlow.Paths.unitOptions, DateTime.UtcNow); 370 } 371 finally 372 { 373 database?.Close(); 374 ProgressUtility.ClearProgressBar(); 375 UnityAPI.Async(AssetDatabase.Refresh); 376 //ConsoleProfiler.Dump(); 377 } 378 } 379 } 380 } 381 } 382 383 public static IEnumerable<IUnitOption> Subset(UnitOptionFilter filter, GraphReference reference) 384 { 385 lock (@lock) 386 { 387 if (options == null) 388 { 389 Load(); 390 } 391 392 var dynamicOptions = UnityAPI.Await(() => GetDynamicOptions().ToHashSet()); 393 var contextualOptions = UnityAPI.Await(() => GetContextualOptions(reference).ToHashSet()); 394 395 return LinqUtility.Concat<IUnitOption>(options, dynamicOptions, contextualOptions) 396 .Where((filter ?? UnitOptionFilter.Any).ValidateOption) 397 .ToArray(); 398 } 399 } 400 401 #region Units 402 403 private static CodebaseSubset codebase; 404 405 private static void UpdateCodebase(IEnumerable<Type> typeSet = null) 406 { 407 using var profilerScope = ProfilingUtility.SampleBlock("UpdateCodebase"); 408 if (typeSet == null) 409 { 410 typeSet = Codebase.settingsTypes; 411 } 412 else 413 { 414 typeSet = typeSet.Where(t => Codebase.settingsTypes.Contains(t)); 415 } 416 417 Codebase.UpdateSettings(); 418 codebase = Codebase.Subset(typeSet, TypeFilter.Any.Configured(), MemberFilter.Any.Configured(), TypeFilter.Any.Configured(false)); 419 codebase.Cache(); 420 } 421 422 private static IEnumerable<IUnitOption> GetStaticOptions() 423 { 424 // Standalones 425 426 foreach (var unit in Codebase.ludiqRuntimeTypes.Where(t => typeof(IUnit).IsAssignableFrom(t) && 427 t.IsConcrete() && 428 t.GetDefaultConstructor() != null && 429 !t.HasAttribute<SpecialUnitAttribute>() && 430 (EditorPlatformUtility.allowJit || !t.HasAttribute<AotIncompatibleAttribute>()) && 431 t.GetDefaultConstructor() != null) 432 .Select(t => (IUnit)t.Instantiate())) 433 { 434 yield return unit.Option(); 435 } 436 437 // Self 438 439 yield return new This().Option(); 440 441 // Types 442 443 foreach (var type in codebase.types) 444 { 445 foreach (var typeOption in GetTypeOptions(type)) 446 { 447 yield return typeOption; 448 } 449 } 450 451 // Members 452 453 foreach (var member in codebase.members) 454 { 455 foreach (var memberOption in GetMemberOptions(member)) 456 { 457 yield return memberOption; 458 } 459 } 460 461 // Events 462 463 foreach (var eventType in Codebase.ludiqRuntimeTypes.Where(t => typeof(IEventUnit).IsAssignableFrom(t) && t.IsConcrete())) 464 { 465 yield return ((IEventUnit)eventType.Instantiate()).Option(); 466 } 467 468 // Blank Variables 469 470 yield return new GetVariableOption(VariableKind.Flow); 471 yield return new GetVariableOption(VariableKind.Graph); 472 yield return new GetVariableOption(VariableKind.Object); 473 yield return new GetVariableOption(VariableKind.Scene); 474 yield return new GetVariableOption(VariableKind.Application); 475 yield return new GetVariableOption(VariableKind.Saved); 476 477 yield return new SetVariableOption(VariableKind.Flow); 478 yield return new SetVariableOption(VariableKind.Graph); 479 yield return new SetVariableOption(VariableKind.Object); 480 yield return new SetVariableOption(VariableKind.Scene); 481 yield return new SetVariableOption(VariableKind.Application); 482 yield return new SetVariableOption(VariableKind.Saved); 483 484 yield return new IsVariableDefinedOption(VariableKind.Flow); 485 yield return new IsVariableDefinedOption(VariableKind.Graph); 486 yield return new IsVariableDefinedOption(VariableKind.Object); 487 yield return new IsVariableDefinedOption(VariableKind.Scene); 488 yield return new IsVariableDefinedOption(VariableKind.Application); 489 yield return new IsVariableDefinedOption(VariableKind.Saved); 490 491 // Blank Super Unit 492 493 yield return SubgraphUnit.WithInputOutput().Option(); 494 495 // Extensions 496 497 foreach (var staticUnitsExtension in staticUnitsExtensions) 498 { 499 foreach (var extensionStaticUnit in staticUnitsExtension()) 500 { 501 yield return extensionStaticUnit; 502 } 503 } 504 } 505 506 private static IEnumerable<IUnitOption> GetIncrementalOptions(Type type) 507 { 508 if (!codebase.ValidateType(type)) 509 { 510 yield break; 511 } 512 513 foreach (var typeOption in GetTypeOptions(type)) 514 { 515 yield return typeOption; 516 } 517 518 foreach (var member in codebase.FilterMembers(type)) 519 { 520 foreach (var memberOption in GetMemberOptions(member)) 521 { 522 yield return memberOption; 523 } 524 } 525 } 526 527 private static IEnumerable<IUnitOption> GetTypeOptions(Type type) 528 { 529 if (type == typeof(object)) 530 { 531 yield break; 532 } 533 534 // Struct Initializer 535 536 if (type.IsStruct()) 537 { 538 yield return new CreateStruct(type).Option(); 539 } 540 541 // Literals 542 543 if (type.HasInspector()) 544 { 545 yield return new Literal(type).Option(); 546 547 if (EditorPlatformUtility.allowJit) 548 { 549 var listType = typeof(List<>).MakeGenericType(type); 550 551 yield return new Literal(listType).Option(); 552 } 553 } 554 555 // Exposes 556 557 if (!type.IsEnum) 558 { 559 yield return new Expose(type).Option(); 560 } 561 } 562 563 private static IEnumerable<IUnitOption> GetMemberOptions(Member member) 564 { 565 // Operators are handled with special math units 566 // that are more elegant than the raw methods 567 if (member.isOperator) 568 { 569 yield break; 570 } 571 572 // Conversions are handled automatically by connections 573 if (member.isConversion) 574 { 575 yield break; 576 } 577 578 if (member.isAccessor) 579 { 580 if (member.isPubliclyGettable) 581 { 582 yield return new GetMember(member).Option(); 583 } 584 585 if (member.isPubliclySettable) 586 { 587 yield return new SetMember(member).Option(); 588 } 589 } 590 else if (member.isPubliclyInvocable) 591 { 592 yield return new InvokeMember(member).Option(); 593 } 594 } 595 596 private static IEnumerable<IUnitOption> GetDynamicOptions() 597 { 598 // Super Units 599 600 var flowMacros = AssetUtility.GetAllAssetsOfType<ScriptGraphAsset>().ToArray(); 601 602 foreach (var superUnit in flowMacros.Select(flowMacro => new SubgraphUnit(flowMacro))) 603 { 604 yield return superUnit.Option(); 605 } 606 607 // Extensions 608 609 foreach (var dynamicUnitsExtension in dynamicUnitsExtensions) 610 { 611 foreach (var extensionDynamicUnit in dynamicUnitsExtension()) 612 { 613 yield return extensionDynamicUnit; 614 } 615 } 616 } 617 618 private static IEnumerable<IUnitOption> GetContextualOptions(GraphReference reference) 619 { 620 foreach (var variableKind in Enum.GetValues(typeof(VariableKind)).Cast<VariableKind>()) 621 { 622 foreach (var graphVariableName in EditorVariablesUtility.GetVariableNameSuggestions(variableKind, reference)) 623 { 624 yield return new GetVariableOption(variableKind, graphVariableName); 625 yield return new SetVariableOption(variableKind, graphVariableName); 626 yield return new IsVariableDefinedOption(variableKind, graphVariableName); 627 } 628 } 629 630 // Extensions 631 632 foreach (var contextualUnitsExtension in contextualUnitsExtensions) 633 { 634 foreach (var extensionContextualUnitOption in contextualUnitsExtension(reference)) 635 { 636 yield return extensionContextualUnitOption; 637 } 638 } 639 } 640 641 #endregion 642 643 #region Scripts 644 645 private static Dictionary<Type, HashSet<string>> typesToGuids; 646 private static Dictionary<string, HashSet<Type>> guidsToTypes; 647 648 private static void UpdateTypeMappings() 649 { 650 using var profilerScope = ProfilingUtility.SampleBlock("UpdateTypeMappings"); 651 652 typesToGuids = new Dictionary<Type, HashSet<string>>(); 653 guidsToTypes = new Dictionary<string, HashSet<Type>>(); 654 655 UnityAPI.AwaitForever(() => 656 { 657 foreach (var script in UnityEngine.Resources.FindObjectsOfTypeAll<MonoScript>()) 658 { 659 var type = script.GetClass(); 660 661 // Skip scripts without types 662 if (type == null) 663 { 664 continue; 665 } 666 667 var path = AssetDatabase.GetAssetPath(script); 668 // Skip built-in Unity plugins, which are referenced by full path 669 if (!path.StartsWith("Assets")) 670 { 671 continue; 672 } 673 674 var guid = AssetDatabase.AssetPathToGUID(path); 675 // Add the GUID to the list, even if it doesn't have any type 676 if (!guidsToTypes.ContainsKey(guid)) 677 { 678 guidsToTypes.Add(guid, new HashSet<Type>()); 679 } 680 681 if (!typesToGuids.ContainsKey(type)) 682 { 683 typesToGuids.Add(type, new HashSet<string>()); 684 } 685 686 typesToGuids[type].Add(guid); 687 guidsToTypes[guid].Add(type); 688 } 689 }); 690 } 691 692 public static IEnumerable<string> GetScriptGuids(Type type) 693 { 694 if (typesToGuids == null) 695 { 696 UpdateTypeMappings(); 697 } 698 699 using (var recursion = Recursion.New(1)) 700 { 701 return GetScriptGuids(recursion, type).ToArray(); // No delayed execution for recursion disposal 702 } 703 } 704 705 private static IEnumerable<string> GetScriptGuids(Recursion recursion, Type type) 706 { 707 if (!recursion?.TryEnter(type) ?? false) 708 { 709 yield break; 710 } 711 712 if (typesToGuids.ContainsKey(type)) 713 { 714 foreach (var guid in typesToGuids[type]) 715 { 716 yield return guid; 717 } 718 } 719 720 // Loop through generic arguments. 721 // For example, a List<Enemy> type should return the script GUID for Enemy. 722 if (type.IsGenericType) 723 { 724 foreach (var genericArgument in type.GetGenericArguments()) 725 { 726 foreach (var genericGuid in GetScriptGuids(recursion, genericArgument)) 727 { 728 yield return genericGuid; 729 } 730 } 731 } 732 } 733 734 public static IEnumerable<Type> GetScriptTypes(string guid) 735 { 736 if (guidsToTypes == null) 737 { 738 UpdateTypeMappings(); 739 } 740 741 if (guidsToTypes.ContainsKey(guid)) 742 { 743 return guidsToTypes[guid]; 744 } 745 else 746 { 747 return Enumerable.Empty<Type>(); 748 } 749 } 750 751 private static bool requiresUpdate => GetModifiedScriptGuids().Any() || GetDeletedScriptGuids().Any(); 752 753 private static IEnumerable<string> GetModifiedScriptGuids() 754 { 755 var guids = new HashSet<string>(); 756 757 UnityAPI.AwaitForever(() => 758 { 759 var databaseTimestamp = File.GetLastWriteTimeUtc(BoltFlow.Paths.unitOptions); 760 761 foreach (var script in UnityEngine.Resources.FindObjectsOfTypeAll<MonoScript>()) 762 { 763 var path = AssetDatabase.GetAssetPath(script); 764 var guid = AssetDatabase.AssetPathToGUID(path); 765 766 // Skip built-in Unity plugins, which are referenced by full path 767 if (!path.StartsWith("Assets")) 768 { 769 continue; 770 } 771 772 var scriptTimestamp = File.GetLastWriteTimeUtc(Path.Combine(Paths.project, path)); 773 774 if (scriptTimestamp > databaseTimestamp) 775 { 776 guids.Add(guid); 777 } 778 } 779 }); 780 781 return guids; 782 } 783 784 private static IEnumerable<string> GetDeletedScriptGuids() 785 { 786 if (!IsUnitOptionsBuilt()) 787 { 788 return Enumerable.Empty<string>(); 789 } 790 791 using (NativeUtility.Module("sqlite3.dll")) 792 { 793 SQLiteConnection database = null; 794 795 try 796 { 797 HashSet<string> databaseGuids; 798 799 lock (@lock) 800 { 801 database = new SQLiteConnection(BoltFlow.Paths.unitOptions); 802 803 databaseGuids = database.Query<UnitOptionRow>($"SELECT DISTINCT {nameof(UnitOptionRow.sourceScriptGuids)} FROM {nameof(UnitOptionRow)}") 804 .Select(row => row.sourceScriptGuids) 805 .NotNull() 806 .SelectMany(guids => guids.Split(',')) 807 .ToHashSet(); 808 } 809 810 var assetGuids = UnityAPI.AwaitForever(() => UnityEngine.Resources 811 .FindObjectsOfTypeAll<MonoScript>() 812 .Where(script => script.GetClass() != null) 813 .Select(script => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(script))) 814 .ToHashSet()); 815 816 databaseGuids.ExceptWith(assetGuids); 817 818 return databaseGuids; 819 } 820 finally 821 { 822 database?.Close(); 823 } 824 } 825 } 826 827 #endregion 828 829 #region Extensions 830 831 public static NonNullableList<Func<IEnumerable<IUnitOption>>> staticUnitsExtensions { get; } 832 833 public static NonNullableList<Func<IEnumerable<IUnitOption>>> dynamicUnitsExtensions { get; } 834 835 public static NonNullableList<Func<GraphReference, IEnumerable<IUnitOption>>> contextualUnitsExtensions { get; } 836 837 #endregion 838 839 #region Duplicates 840 841 public static IEnumerable<T> WithoutInheritedDuplicates<T>(this IEnumerable<T> items, Func<T, IUnitOption> optionSelector, CancellationToken cancellation) 842 { 843 // Doing everything we can to avoid reflection here, as it then becomes the main search bottleneck 844 845 var _items = items.ToArray(); 846 847 var pseudoDeclarers = new HashSet<Member>(); 848 849 foreach (var item in _items.Cancellable(cancellation)) 850 { 851 var option = optionSelector(item); 852 853 if (option is IMemberUnitOption memberOption) 854 { 855 if (memberOption.targetType == memberOption.pseudoDeclarer.targetType) 856 { 857 pseudoDeclarers.Add(memberOption.pseudoDeclarer); 858 } 859 } 860 } 861 862 foreach (var item in _items.Cancellable(cancellation)) 863 { 864 var option = optionSelector(item); 865 866 if (option is IMemberUnitOption memberOption) 867 { 868 if (pseudoDeclarers.Contains(memberOption.member) || !pseudoDeclarers.Contains(memberOption.pseudoDeclarer)) 869 { 870 yield return item; 871 } 872 } 873 else 874 { 875 yield return item; 876 } 877 } 878 } 879 880 #endregion 881 } 882}