com.wallstop-studios.unity-helpers 2.0.0-rc81.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/.editorconfig +1 -1
  2. package/.gitattributes +1 -1
  3. package/.githooks/pre-commit +31 -5
  4. package/.githooks/pre-push +50 -0
  5. package/.github/dependabot.yml +24 -2
  6. package/.github/scripts/check-markdown-links.ps1 +77 -0
  7. package/.github/scripts/check_markdown_links.py +89 -0
  8. package/.github/scripts/check_markdown_url_encoding.py +74 -0
  9. package/.github/scripts/validate_markdown_links.py +194 -0
  10. package/.github/workflows/csharpier-autofix.yml +152 -0
  11. package/.github/workflows/format-on-demand.yml +305 -0
  12. package/.github/workflows/lint-doc-links.yml +8 -5
  13. package/.github/workflows/markdown-json.yml +6 -2
  14. package/.github/workflows/prettier-autofix.yml +195 -0
  15. package/.github/workflows/update-dotnet-tools.yml +80 -0
  16. package/.github/workflows/yaml-format-lint.yml +41 -0
  17. package/.lychee.toml +4 -4
  18. package/.markdownlint.jsonc +21 -0
  19. package/.pre-commit-config.yaml +11 -3
  20. package/.yamllint.yaml +31 -0
  21. package/AGENTS.md +5 -1
  22. package/CHANGELOG.md +11 -0
  23. package/CONTRIBUTING.md +49 -0
  24. package/CONTRIBUTING.md.meta +7 -0
  25. package/EDITOR_TOOLS_GUIDE.md +4 -0
  26. package/Editor/AnimationEventEditor.cs +337 -160
  27. package/Editor/Core/Helper/AnimationEventHelpers.cs +178 -152
  28. package/Editor/CustomEditors/PersistentDirectoryGUI.cs +20 -11
  29. package/Editor/CustomEditors/TexturePlatformOverrideEntryDrawer.cs +11 -2
  30. package/Editor/FitTextureSizeWindow.cs +43 -19
  31. package/Editor/PersistentDirectorySettings.cs +64 -12
  32. package/Editor/PrefabChecker.cs +72 -5
  33. package/Editor/Sprites/AnimationCopier.cs +132 -56
  34. package/Editor/Sprites/AnimationCreator.cs +63 -22
  35. package/Editor/Sprites/AnimationViewerWindow.cs +42 -6
  36. package/Editor/Sprites/TexturePlatformNameHelper.cs +50 -39
  37. package/Editor/Sprites/TextureResizerWizard.cs +23 -1
  38. package/Editor/Sprites/TextureSettingsApplierWindow.cs +148 -85
  39. package/Editor/Tools/ImageBlurTool.cs +81 -10
  40. package/Editor/Utils/EditorUi.cs +1 -1
  41. package/Editor/Utils/ScriptableObjectSingletonCreator.cs +1 -1
  42. package/GETTING_STARTED.md +40 -56
  43. package/RANDOM_PERFORMANCE.md +12 -12
  44. package/README.md +395 -2407
  45. package/RELATIONAL_COMPONENTS.md +92 -83
  46. package/Runtime/AssemblyInfo.cs +2 -0
  47. package/Runtime/Core/Attributes/NotNullAttribute.cs +1 -3
  48. package/Runtime/Core/Attributes/RelationalComponentAssigner.cs +50 -5
  49. package/Runtime/Core/DataStructure/CyclicBuffer.cs +0 -1
  50. package/Runtime/Core/Extension/RandomExtensions.cs +68 -0
  51. package/Runtime/Core/Extension/WallstopStudiosLogger.cs +16 -0
  52. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +4 -1
  53. package/Runtime/Core/Helper/ReflectionHelpers.cs +21 -10
  54. package/Runtime/Core/Helper/SpriteHelpers.cs +3 -1
  55. package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +45 -1
  56. package/Runtime/Core/Serialization/JsonConverters/GameObjectConverter.cs +13 -5
  57. package/Runtime/Core/Serialization/JsonConverters/ResolutionConverter.cs +1 -1
  58. package/Runtime/Core/Serialization/JsonConverters/TypeConverter.cs +1 -1
  59. package/Runtime/Core/Serialization/Serializer.cs +101 -0
  60. package/Runtime/Integrations/VContainer/AssemblyInfo.cs +9 -0
  61. package/Runtime/Integrations/VContainer/AssemblyInfo.cs.meta +3 -0
  62. package/Runtime/Integrations/VContainer/ObjectResolverRelationalExtensions.cs +96 -0
  63. package/Runtime/Integrations/VContainer/RelationalComponentEntryPoint.cs +90 -10
  64. package/Runtime/Integrations/VContainer/RelationalComponentsBuilderExtensions.cs +13 -1
  65. package/Runtime/Integrations/VContainer/RelationalObjectPools.cs +114 -0
  66. package/Runtime/Integrations/VContainer/RelationalObjectPools.cs.meta +11 -0
  67. package/Runtime/Integrations/VContainer/RelationalSceneAssignmentOptions.cs +16 -4
  68. package/Runtime/Integrations/VContainer/RelationalSceneLoadListener.cs +241 -0
  69. package/Runtime/Integrations/VContainer/RelationalSceneLoadListener.cs.meta +11 -0
  70. package/Runtime/Integrations/Zenject/AssemblyInfo.cs +9 -0
  71. package/Runtime/Integrations/Zenject/AssemblyInfo.cs.meta +3 -0
  72. package/Runtime/Integrations/Zenject/DiContainerRelationalExtensions.cs +69 -2
  73. package/Runtime/Integrations/Zenject/RelationalComponentSceneInitializer.cs +89 -12
  74. package/Runtime/Integrations/Zenject/RelationalComponentsInstaller.cs +23 -1
  75. package/Runtime/Integrations/Zenject/RelationalMemoryPools.cs +44 -0
  76. package/Runtime/Integrations/Zenject/RelationalMemoryPools.cs.meta +11 -0
  77. package/Runtime/Integrations/Zenject/RelationalSceneAssignmentOptions.cs +16 -10
  78. package/Runtime/Integrations/Zenject/RelationalSceneLoadListener.cs +243 -0
  79. package/Runtime/Integrations/Zenject/RelationalSceneLoadListener.cs.meta +11 -0
  80. package/Runtime/Tags/AttributeMetadataCache.cs +1 -4
  81. package/Runtime/Utils/Buffers.cs +4 -4
  82. package/Runtime/Utils/ScriptableObjectSingleton.cs +0 -1
  83. package/Runtime/Utils/SetTextureImportData.cs +3 -1
  84. package/Runtime/Utils/TextureScale.cs +10 -2
  85. package/Runtime/Visuals/UGUI/EnhancedImage.cs +6 -0
  86. package/Runtime/Visuals/UIToolkit/LayeredImage.cs +4 -1
  87. package/SERIALIZATION.md +15 -0
  88. package/SPATIAL_TREE_2D_PERFORMANCE.md +85 -82
  89. package/SPATIAL_TREE_3D_PERFORMANCE.md +94 -91
  90. package/Samples~/DI - VContainer/README.md +232 -51
  91. package/Samples~/DI - VContainer/Scripts/GameLifetimeScope.cs +22 -4
  92. package/Samples~/DI - VContainer/Scripts/RelationalConsumer.cs +5 -2
  93. package/Samples~/DI - VContainer/Scripts/Spawner.cs +113 -4
  94. package/Samples~/DI - Zenject/README.md +217 -53
  95. package/Samples~/DI - Zenject/Scripts/RelationalConsumer.cs +3 -0
  96. package/Samples~/DI - Zenject/Scripts/RelationalConsumerPool.cs +37 -0
  97. package/Samples~/DI - Zenject/Scripts/RelationalConsumerPool.cs.meta +12 -0
  98. package/Samples~/DI - Zenject/Scripts/SpawnerZenject.cs +74 -3
  99. package/Samples~/Random - PRNG/README.md +2 -1
  100. package/Samples~/Relational Components - Basic/README.md +3 -1
  101. package/Samples~/Serialization - JSON/README.md +2 -1
  102. package/Samples~/Spatial Structures - 2D and 3D/README.md +2 -1
  103. package/Samples~/UGUI - EnhancedImage/README.md +2 -1
  104. package/Samples~/UI Toolkit - MultiFile Selector (Editor)/README.md +2 -1
  105. package/THIRD_PARTY_NOTICES.md +1 -1
  106. package/Tests/Editor/Attributes/AnimationEventHelpersTests.cs +16 -0
  107. package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +3 -3
  108. package/Tests/Editor/Integrations/VContainer/VContainerRelationalEntryPointTests.cs +6 -2
  109. package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs +170 -0
  110. package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs.meta +11 -0
  111. package/Tests/Editor/Integrations/VContainer/WallstopStudios.UnityHelpers.Tests.Editor.VContainer.asmdef +2 -1
  112. package/Tests/Editor/Integrations/Zenject/WallstopStudios.UnityHelpers.Tests.Editor.Zenject.asmdef +3 -2
  113. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs +131 -0
  114. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs.meta +11 -0
  115. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalInitializerTests.cs +6 -2
  116. package/Tests/Editor/PersistentDirectorySettingsTests.cs +59 -0
  117. package/Tests/Editor/PersistentDirectorySettingsTests.cs.meta +11 -0
  118. package/Tests/Editor/PrefabCheckerReportTests.cs +32 -0
  119. package/Tests/Editor/PrefabCheckerReportTests.cs.meta +11 -0
  120. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +64 -0
  121. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs.meta +11 -0
  122. package/Tests/Editor/Sprites/AnimationCopierWindowTests.cs +1 -1
  123. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +38 -0
  124. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs.meta +11 -0
  125. package/Tests/Editor/Sprites/ScriptableSpriteAtlasEditorTests.cs +1 -1
  126. package/Tests/Editor/Sprites/SpriteCropperAdditionalTests.cs +12 -12
  127. package/Tests/Editor/Sprites/SpriteCropperTests.cs +9 -9
  128. package/Tests/Editor/Sprites/SpritePivotAdjusterTests.cs +3 -3
  129. package/Tests/Editor/Sprites/TexturePlatformNameHelperTests.cs +18 -0
  130. package/Tests/Editor/Sprites/TextureResizerWizardTests.cs +5 -5
  131. package/Tests/Editor/Sprites/TextureSettingsApplierAPITests.cs +3 -3
  132. package/Tests/Editor/Sprites/TextureSettingsApplierWizardAdditionalTests.cs +4 -4
  133. package/Tests/Editor/Sprites/TextureSettingsApplierWizardTests.cs +4 -4
  134. package/Tests/Editor/Tools/ImageBlurToolTests.cs +22 -110
  135. package/Tests/Editor/Utils/CommonTestBase.cs +43 -1
  136. package/Tests/Editor/Utils/ScriptableObjectSingletonCreatorTests.cs +5 -5
  137. package/Tests/Editor/Windows/FitTextureSizeWindowTests.cs +66 -74
  138. package/Tests/Runtime/Attributes/RelationalComponentInitializerTests.cs +4 -15
  139. package/Tests/Runtime/DataStructures/SpatialTree3DBoundsConsistencyTests.cs +29 -29
  140. package/Tests/Runtime/Integrations/VContainer/RelationalComponentsVContainerTests.cs +259 -218
  141. package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs +86 -0
  142. package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs.meta +11 -0
  143. package/Tests/Runtime/Integrations/Zenject/RelationalComponentsZenjectTests.cs +255 -227
  144. package/Tests/Runtime/Performance/SpatialTree2DPerformanceTests.cs +5 -0
  145. package/Tests/Runtime/Performance/SpatialTree3DPerformanceTests.cs +3 -0
  146. package/Tests/Runtime/Serialization/JsonConverterAdditionalTests.cs +30 -0
  147. package/Tests/Runtime/Serialization/JsonConverterAdditionalTests.cs.meta +11 -0
  148. package/Tests/Runtime/Serialization/JsonConverterTests.cs +8 -12
  149. package/Tests/Runtime/Serialization/JsonSerializationTest.cs +16 -5
  150. package/Tests/Runtime/Serialization/SerializerAdditionalTests.cs +12 -0
  151. package/Tests/Runtime/Serialization/SerializerFileIoTests.cs +105 -0
  152. package/Tests/Runtime/Serialization/SerializerFileIoTests.cs.meta +11 -0
  153. package/Tests/Runtime/Serialization/UnityEngineObjectJsonTests.cs +247 -0
  154. package/Tests/Runtime/Serialization/UnityEngineObjectJsonTests.cs.meta +11 -0
  155. package/Tests/Runtime/TestUtils/CommonTestBase.cs +88 -0
  156. package/Tests/Runtime/Utils/CoroutineHandlerTests.cs +1 -1
  157. package/Tests/Runtime/Utils/LZMAComprehensiveTests.cs +1 -1
  158. package/Tests/Runtime/Utils/LZMATests.cs +1 -1
  159. package/Tests/Runtime/Utils/MatchColliderToSpriteTests.cs +1 -1
  160. package/Tests/Runtime/Visuals/EnhancedImageTests.cs +25 -56
  161. package/Tests/Runtime/Visuals/VisualsTestHelpers.cs +1 -8
  162. package/package-lock.json.meta +7 -0
  163. package/package.json +8 -4
  164. package/scripts/check-eol.ps1 +4 -5
  165. package/scripts/lint-tests.ps1 +156 -0
  166. package/scripts/lint-tests.ps1.meta +7 -0
  167. package/scripts/normalize-eol.ps1 +6 -9
  168. package/.github/workflows/csharpier.yml +0 -135
@@ -54,7 +54,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
54
54
  private const string DirToolName = "SpriteAnimationEditor";
55
55
  private const string DirContextKey = "Clips";
56
56
 
57
- private sealed class EditorLayerData
57
+ internal sealed class EditorLayerData
58
58
  {
59
59
  public AnimationClip SourceClip { get; }
60
60
  public List<Sprite> Sprites { get; }
@@ -65,7 +65,17 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
65
65
  public EditorLayerData(AnimationClip clip)
66
66
  {
67
67
  SourceClip = clip;
68
- Sprites = clip.GetSpritesFromClip()?.ToList();
68
+ Sprites = new List<Sprite>();
69
+ if (clip != null)
70
+ {
71
+ foreach (Sprite s in clip.GetSpritesFromClip())
72
+ {
73
+ if (s != null)
74
+ {
75
+ Sprites.Add(s);
76
+ }
77
+ }
78
+ }
69
79
  OriginalClipFps =
70
80
  clip.frameRate > 0 ? clip.frameRate : AnimatedSpriteLayer.FrameRate;
71
81
 
@@ -442,7 +452,16 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
442
452
  AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(assetPath);
443
453
  if (clip != null)
444
454
  {
445
- if (_loadedEditorLayers.All(layer => layer.SourceClip != clip))
455
+ bool noneMatch = true;
456
+ for (int i = 0; i < _loadedEditorLayers.Count; i++)
457
+ {
458
+ if (_loadedEditorLayers[i]?.SourceClip == clip)
459
+ {
460
+ noneMatch = false;
461
+ break;
462
+ }
463
+ }
464
+ if (noneMatch)
446
465
  {
447
466
  AddEditorLayer(clip);
448
467
  clipsAddedCount++;
@@ -500,7 +519,15 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
500
519
  {
501
520
  if (obj is AnimationClip clip)
502
521
  {
503
- bool alreadyExists = _loadedEditorLayers.Any(layer => layer.SourceClip == clip);
522
+ bool alreadyExists = false;
523
+ for (int i = 0; i < _loadedEditorLayers.Count; i++)
524
+ {
525
+ if (_loadedEditorLayers[i]?.SourceClip == clip)
526
+ {
527
+ alreadyExists = true;
528
+ break;
529
+ }
530
+ }
504
531
  if (!alreadyExists)
505
532
  {
506
533
  AddEditorLayer(clip);
@@ -615,7 +642,16 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
615
642
 
616
643
  private void AddEditorLayer(AnimationClip clip)
617
644
  {
618
- if (clip == null || _loadedEditorLayers.Any(layer => layer.SourceClip == clip))
645
+ bool exists = false;
646
+ for (int i = 0; i < _loadedEditorLayers.Count; i++)
647
+ {
648
+ if (_loadedEditorLayers[i]?.SourceClip == clip)
649
+ {
650
+ exists = true;
651
+ break;
652
+ }
653
+ }
654
+ if (clip == null || exists)
619
655
  {
620
656
  string clipName = clip != null ? clip.name : "<null>";
621
657
  this.LogWarn($"Clip '{clipName}' is null or already loaded.");
@@ -649,7 +685,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
649
685
 
650
686
  if (_activeEditorLayer == layerToRemove)
651
687
  {
652
- SetActiveEditorLayer(_loadedEditorLayers.FirstOrDefault());
688
+ SetActiveEditorLayer(_loadedEditorLayers.Count > 0 ? _loadedEditorLayers[0] : null);
653
689
  }
654
690
 
655
691
  RebuildLoadedClipsUI();
@@ -3,11 +3,42 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
3
3
  #if UNITY_EDITOR
4
4
  using System;
5
5
  using System.Collections.Generic;
6
+ using UnityEngine.SceneManagement;
6
7
  using UnityEditor;
8
+ using UnityEditor.SceneManagement;
9
+ using WallstopStudios.UnityHelpers.Utils;
7
10
 
8
11
  internal static class TexturePlatformNameHelper
9
12
  {
10
13
  private static string[] _cached;
14
+ private static readonly Dictionary<BuildTargetGroup, string> Map = new()
15
+ {
16
+ { BuildTargetGroup.Standalone, "Standalone" },
17
+ { BuildTargetGroup.iOS, "iPhone" },
18
+ { BuildTargetGroup.tvOS, "tvOS" },
19
+ { BuildTargetGroup.Android, "Android" },
20
+ { BuildTargetGroup.WebGL, "WebGL" },
21
+ { BuildTargetGroup.PS4, "PS4" },
22
+ { BuildTargetGroup.PS5, "PS5" },
23
+ { BuildTargetGroup.XboxOne, "XboxOne" },
24
+ { BuildTargetGroup.Switch, "Switch" },
25
+ };
26
+
27
+ static TexturePlatformNameHelper()
28
+ {
29
+ // Proactively invalidate cache on common editor lifecycle events where domain
30
+ // reload may be disabled. This keeps results correct while enabling caching.
31
+ AssemblyReloadEvents.beforeAssemblyReload += ClearCache;
32
+ EditorApplication.playModeStateChanged += _ => ClearCache();
33
+ EditorSceneManager.activeSceneChangedInEditMode += (_, _) => ClearCache();
34
+ SceneManager.sceneLoaded += (_, _) => ClearCache();
35
+ EditorApplication.projectChanged += ClearCache;
36
+ }
37
+
38
+ private static void ClearCache()
39
+ {
40
+ _cached = null;
41
+ }
11
42
 
12
43
  public static string[] GetKnownPlatformNames()
13
44
  {
@@ -16,62 +47,42 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
16
47
  return _cached;
17
48
  }
18
49
 
19
- List<string> names = new(16) { "DefaultTexturePlatform" };
20
-
21
- // Map BuildTargetGroup to importer platform names
22
- Dictionary<BuildTargetGroup, string> map = new()
23
- {
24
- { BuildTargetGroup.Standalone, "Standalone" },
25
- #if UNITY_IOS || UNITY_TVOS || true
26
- { BuildTargetGroup.iOS, "iPhone" },
27
- { BuildTargetGroup.tvOS, "tvOS" },
28
- #endif
29
- { BuildTargetGroup.Android, "Android" },
30
- { BuildTargetGroup.WebGL, "WebGL" },
31
- #if UNITY_PS4 || true
32
- { BuildTargetGroup.PS4, "PS4" },
33
- #endif
34
- #if UNITY_PS5 || true
35
- { BuildTargetGroup.PS5, "PS5" },
36
- #endif
37
- #if UNITY_XBOXONE || true
38
- { BuildTargetGroup.XboxOne, "XboxOne" },
39
- #endif
40
- #if UNITY_SWITCH || true
41
- { BuildTargetGroup.Switch, "Switch" },
42
- #endif
43
- };
50
+ // Use a pooled set during build to avoid duplicate checks (O(1) membership) with minimal allocations.
51
+ using PooledResource<HashSet<string>> setLease = Buffers<string>.HashSet.Get(
52
+ out HashSet<string> set
53
+ );
54
+ _ = set.Add("DefaultTexturePlatform");
44
55
 
45
- Array values = Enum.GetValues(typeof(BuildTargetGroup));
46
- for (int i = 0; i < values.Length; i++)
56
+ // Enum.GetValues returns a typed array; cast once to avoid boxing per element.
57
+ BuildTargetGroup[] groups = (BuildTargetGroup[])
58
+ Enum.GetValues(typeof(BuildTargetGroup));
59
+ for (int i = 0; i < groups.Length; i++)
47
60
  {
48
- BuildTargetGroup g = (BuildTargetGroup)values.GetValue(i);
61
+ BuildTargetGroup g = groups[i];
49
62
  if (g == BuildTargetGroup.Unknown)
50
63
  {
51
64
  continue;
52
65
  }
53
66
 
54
- // Old/obsolete groups may exist; include them as ToString fallback
55
- if (map.TryGetValue(g, out string name))
67
+ if (Map.TryGetValue(g, out string mapped))
56
68
  {
57
- if (!names.Contains(name))
58
- {
59
- names.Add(name);
60
- }
69
+ _ = set.Add(mapped);
61
70
  }
62
71
  else
63
72
  {
64
73
  string n = g.ToString();
65
- if (!string.IsNullOrEmpty(n) && !names.Contains(n))
74
+ if (!string.IsNullOrEmpty(n))
66
75
  {
67
- names.Add(n);
76
+ _ = set.Add(n);
68
77
  }
69
78
  }
70
79
  }
71
80
 
72
- names.Sort(StringComparer.Ordinal);
73
- _cached = names.ToArray();
74
- return _cached;
81
+ string[] arr = new string[set.Count];
82
+ set.CopyTo(arr);
83
+ Array.Sort(arr, StringComparer.Ordinal);
84
+ _cached = arr;
85
+ return arr;
75
86
  }
76
87
  }
77
88
  #endif
@@ -122,7 +122,29 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
122
122
  }
123
123
  }
124
124
 
125
- textures = textures.Where(t => t != null).Distinct().OrderBy(t => t.name).ToList();
125
+ // Remove nulls, de-dupe, and order by name without LINQ
126
+ using (Buffers<Texture2D>.HashSet.Get(out HashSet<Texture2D> distinct))
127
+ using (Buffers<Texture2D>.List.Get(out List<Texture2D> ordered))
128
+ {
129
+ for (int i = 0; i < textures.Count; i++)
130
+ {
131
+ Texture2D t = textures[i];
132
+ if (t == null)
133
+ {
134
+ continue;
135
+ }
136
+ if (distinct.Add(t))
137
+ {
138
+ ordered.Add(t);
139
+ }
140
+ }
141
+
142
+ ordered.Sort(
143
+ static (a, b) => string.Compare(a.name, b.name, StringComparison.Ordinal)
144
+ );
145
+ textures.Clear();
146
+ textures.AddRange(ordered);
147
+ }
126
148
 
127
149
  if (textures.Count <= 0 || numResizes <= 0)
128
150
  {
@@ -4,7 +4,6 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
4
4
  using System;
5
5
  using System.Collections.Generic;
6
6
  using System.IO;
7
- using System.Linq;
8
7
  using UnityEditor;
9
8
  using UnityEngine;
10
9
  using CustomEditors;
@@ -74,20 +73,8 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
74
73
 
75
74
  // Optional: named per-platform overrides
76
75
  public List<PlatformOverrideEntry> platformOverrides = new();
77
- private static readonly string[] KnownPlatforms = new[]
78
- {
79
- "DefaultTexturePlatform",
80
- "Standalone",
81
- "iPhone",
82
- "Android",
83
- "WebGL",
84
- "tvOS",
85
- "XboxOne",
86
- "PS4",
87
- "PS5",
88
- "Switch",
89
- };
90
76
  private int _addPlatformIndex;
77
+ private readonly Dictionary<int, int> _replaceSelectionByIndex = new();
91
78
 
92
79
  // Flow options
93
80
  public bool requireChangesBeforeApply = true; // If true, stats are checked and apply is skipped if nothing changes.
@@ -210,6 +197,37 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
210
197
  $"Unknown platform name '{name}'. It will be passed to Unity importer as-is.",
211
198
  MessageType.Info
212
199
  );
200
+
201
+ // Quick fix UX: allow replacing with a known platform directly
202
+ EditorGUILayout.BeginHorizontal();
203
+ EditorGUILayout.LabelField("Replace With", GUILayout.Width(90));
204
+ int currentChoice = 0;
205
+ if (_replaceSelectionByIndex.TryGetValue(i, out int tmp))
206
+ {
207
+ currentChoice = tmp;
208
+ }
209
+ currentChoice = EditorGUILayout.Popup(currentChoice, knownNames);
210
+ _replaceSelectionByIndex[i] = currentChoice;
211
+ if (GUILayout.Button("Replace", GUILayout.Width(80)))
212
+ {
213
+ if (0 <= currentChoice && currentChoice < knownNames.Length)
214
+ {
215
+ platformOverrides[i].platformName = knownNames[currentChoice];
216
+ Repaint();
217
+ }
218
+ }
219
+ // Heuristic quick button for common typo: iOS -> iPhone
220
+ if (string.Equals(name, "iOS", StringComparison.OrdinalIgnoreCase))
221
+ {
222
+ int idx = Array.IndexOf(knownNames, "iPhone");
223
+ if (idx >= 0 && GUILayout.Button("Use iPhone", GUILayout.Width(90)))
224
+ {
225
+ platformOverrides[i].platformName = "iPhone";
226
+ _replaceSelectionByIndex[i] = idx;
227
+ Repaint();
228
+ }
229
+ }
230
+ EditorGUILayout.EndHorizontal();
213
231
  }
214
232
  }
215
233
 
@@ -294,13 +312,17 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
294
312
  return;
295
313
  }
296
314
 
297
- if (
298
- platformOverrides.Any(p =>
299
- string.Equals(p.platformName?.Trim(), name, StringComparison.Ordinal)
300
- )
301
- )
315
+ for (int i = 0; i < (platformOverrides?.Count ?? 0); i++)
302
316
  {
303
- return;
317
+ PlatformOverrideEntry p = platformOverrides[i];
318
+ string existing = p?.platformName;
319
+ if (
320
+ !string.IsNullOrEmpty(existing)
321
+ && string.Equals(existing.Trim(), name, StringComparison.Ordinal)
322
+ )
323
+ {
324
+ return;
325
+ }
304
326
  }
305
327
  platformOverrides.Add(new PlatformOverrideEntry { platformName = name });
306
328
  }
@@ -362,86 +384,127 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
362
384
  private List<string> GetTargetTexturePaths()
363
385
  {
364
386
  // Build extension filter (normalize)
365
- HashSet<string> allowedExtensions = new(StringComparer.OrdinalIgnoreCase);
366
- foreach (string extRaw in spriteFileExtensions ?? Enumerable.Empty<string>())
367
- {
368
- if (string.IsNullOrWhiteSpace(extRaw))
369
- {
370
- continue;
371
- }
372
-
373
- string ext = extRaw.StartsWith(".") ? extRaw : "." + extRaw;
374
- _ = allowedExtensions.Add(ext);
375
- }
376
-
377
- // Collect folders
378
- List<string> folderAssetPaths = new();
379
- foreach (Object directory in directories ?? Enumerable.Empty<Object>())
387
+ using (
388
+ SetBuffers<string>
389
+ .GetHashSetPool(StringComparer.OrdinalIgnoreCase)
390
+ .Get(out HashSet<string> allowedExtensions)
391
+ )
380
392
  {
381
- if (directory == null)
393
+ if (spriteFileExtensions != null)
382
394
  {
383
- continue;
384
- }
385
-
386
- string assetPath = AssetDatabase.GetAssetPath(directory);
387
- if (!string.IsNullOrWhiteSpace(assetPath) && AssetDatabase.IsValidFolder(assetPath))
388
- {
389
- folderAssetPaths.Add(assetPath);
390
- }
391
- }
395
+ foreach (string extRaw in spriteFileExtensions)
396
+ {
397
+ if (string.IsNullOrWhiteSpace(extRaw))
398
+ {
399
+ continue;
400
+ }
392
401
 
393
- HashSet<string> unique = new(StringComparer.OrdinalIgnoreCase);
394
- if (folderAssetPaths.Count > 0)
395
- {
396
- using PooledResource<string[]> folderLease = WallstopFastArrayPool<string>.Get(
397
- folderAssetPaths.Count,
398
- out string[] folders
399
- );
400
- for (int i = 0; i < folderAssetPaths.Count; i++)
401
- {
402
- folders[i] = folderAssetPaths[i];
402
+ string ext = extRaw.StartsWith(".") ? extRaw : "." + extRaw;
403
+ _ = allowedExtensions.Add(ext);
404
+ }
403
405
  }
404
406
 
405
- string[] guids = AssetDatabase.FindAssets("t:Texture2D", folders);
406
- for (int i = 0; i < guids.Length; i++)
407
+ // Collect folders
408
+ using (Buffers<string>.List.Get(out List<string> folderAssetPaths))
407
409
  {
408
- string p = AssetDatabase.GUIDToAssetPath(guids[i]);
409
- if (string.IsNullOrWhiteSpace(p))
410
+ if (directories != null)
410
411
  {
411
- continue;
412
+ foreach (Object directory in directories)
413
+ {
414
+ if (directory == null)
415
+ {
416
+ continue;
417
+ }
418
+
419
+ string assetPath = AssetDatabase.GetAssetPath(directory);
420
+ if (
421
+ !string.IsNullOrWhiteSpace(assetPath)
422
+ && AssetDatabase.IsValidFolder(assetPath)
423
+ )
424
+ {
425
+ folderAssetPaths.Add(assetPath);
426
+ }
427
+ }
412
428
  }
413
429
 
414
- string ext = Path.GetExtension(p);
415
- if (allowedExtensions.Count > 0 && !allowedExtensions.Contains(ext))
430
+ using (
431
+ SetBuffers<string>
432
+ .GetHashSetPool(StringComparer.OrdinalIgnoreCase)
433
+ .Get(out HashSet<string> unique)
434
+ )
416
435
  {
417
- continue;
418
- }
436
+ if (folderAssetPaths.Count > 0)
437
+ {
438
+ using PooledResource<string[]> folderLease =
439
+ WallstopFastArrayPool<string>.Get(
440
+ folderAssetPaths.Count,
441
+ out string[] folders
442
+ );
443
+ for (int i = 0; i < folderAssetPaths.Count; i++)
444
+ {
445
+ folders[i] = folderAssetPaths[i];
446
+ }
419
447
 
420
- _ = unique.Add(p);
421
- }
422
- }
448
+ string[] guids = AssetDatabase.FindAssets("t:Texture2D", folders);
449
+ for (int i = 0; i < guids.Length; i++)
450
+ {
451
+ string p = AssetDatabase.GUIDToAssetPath(guids[i]);
452
+ if (string.IsNullOrWhiteSpace(p))
453
+ {
454
+ continue;
455
+ }
456
+
457
+ string ext = Path.GetExtension(p);
458
+ if (allowedExtensions.Count > 0 && !allowedExtensions.Contains(ext))
459
+ {
460
+ continue;
461
+ }
462
+
463
+ _ = unique.Add(p);
464
+ }
465
+ }
423
466
 
424
- foreach (
425
- Texture2D t in textures?.Where(t => t != null).Distinct()
426
- ?? Enumerable.Empty<Texture2D>()
427
- )
428
- {
429
- string p = AssetDatabase.GetAssetPath(t);
430
- if (string.IsNullOrWhiteSpace(p))
431
- {
432
- continue;
433
- }
467
+ // De-dupe textures and skip nulls without LINQ
468
+ using (Buffers<Texture2D>.HashSet.Get(out HashSet<Texture2D> texSet))
469
+ {
470
+ if (textures != null)
471
+ {
472
+ for (int ti = 0; ti < textures.Count; ti++)
473
+ {
474
+ Texture2D t = textures[ti];
475
+ if (t == null)
476
+ {
477
+ continue;
478
+ }
479
+ if (!texSet.Add(t))
480
+ {
481
+ continue;
482
+ }
483
+
484
+ string p = AssetDatabase.GetAssetPath(t);
485
+ if (string.IsNullOrWhiteSpace(p))
486
+ {
487
+ continue;
488
+ }
489
+
490
+ string ext = Path.GetExtension(p);
491
+ if (
492
+ allowedExtensions.Count > 0
493
+ && !allowedExtensions.Contains(ext)
494
+ )
495
+ {
496
+ continue;
497
+ }
498
+
499
+ _ = unique.Add(p);
500
+ }
501
+ }
502
+ }
434
503
 
435
- string ext = Path.GetExtension(p);
436
- if (allowedExtensions.Count > 0 && !allowedExtensions.Contains(ext))
437
- {
438
- continue;
504
+ return new List<string>(unique);
505
+ }
439
506
  }
440
-
441
- _ = unique.Add(p);
442
507
  }
443
-
444
- return new List<string>(unique);
445
508
  }
446
509
 
447
510
  public void CalculateStats()
@@ -3,12 +3,10 @@ namespace WallstopStudios.UnityHelpers.Editor.Tools
3
3
  using System;
4
4
  using System.Collections.Generic;
5
5
  using System.IO;
6
- using System.Linq;
7
6
  using System.Threading.Tasks;
8
7
  using UnityEditor;
9
8
  using UnityEngine;
10
9
  using WallstopStudios.UnityHelpers.Core.Extension;
11
- using WallstopStudios.UnityHelpers.Core.Helper;
12
10
  using WallstopStudios.UnityHelpers.Editor.CustomEditors;
13
11
  using WallstopStudios.UnityHelpers.Editor.Utils;
14
12
  using Object = UnityEngine.Object;
@@ -92,16 +90,39 @@ namespace WallstopStudios.UnityHelpers.Editor.Tools
92
90
  nameof(ImageBlurTool)
93
91
  );
94
92
 
95
- if (
96
- _serializedObject.ApplyModifiedProperties()
97
- || !_lastSeenImageSources.SequenceEqual(imageSources)
98
- )
93
+ bool changed = _serializedObject.ApplyModifiedProperties();
94
+ if (!changed)
95
+ {
96
+ int aCount = _lastSeenImageSources.Count;
97
+ int bCount = imageSources.Count;
98
+ if (aCount != bCount)
99
+ {
100
+ changed = true;
101
+ }
102
+ else
103
+ {
104
+ for (int i = 0; i < aCount; i++)
105
+ {
106
+ if (!ReferenceEquals(_lastSeenImageSources[i], imageSources[i]))
107
+ {
108
+ changed = true;
109
+ break;
110
+ }
111
+ }
112
+ }
113
+ }
114
+ if (changed)
99
115
  {
100
116
  _lastSeenImageSources.Clear();
101
117
  _lastSeenImageSources.AddRange(imageSources);
102
118
  _manualTextures.Clear();
103
- foreach (Object directory in imageSources.Where(Objects.NotNull))
119
+ for (int i = 0; i < imageSources.Count; i++)
104
120
  {
121
+ Object directory = imageSources[i];
122
+ if (directory == null)
123
+ {
124
+ continue;
125
+ }
105
126
  string path = AssetDatabase.GetAssetPath(directory);
106
127
  if (string.IsNullOrWhiteSpace(path))
107
128
  {
@@ -171,9 +192,30 @@ namespace WallstopStudios.UnityHelpers.Editor.Tools
171
192
  _scrollPosition,
172
193
  GUILayout.Height(200)
173
194
  );
174
- foreach (Texture2D tex in _manualTextures.Concat(_orderedTextures).Distinct())
195
+ using (
196
+ WallstopStudios.UnityHelpers.Utils.Buffers<Texture2D>.HashSet.Get(
197
+ out HashSet<Texture2D> seen
198
+ )
199
+ )
175
200
  {
176
- EditorGUILayout.ObjectField(tex.name, tex, typeof(Texture2D), false);
201
+ for (int i = 0; i < _manualTextures.Count; i++)
202
+ {
203
+ Texture2D t = _manualTextures[i];
204
+ if (t == null || !seen.Add(t))
205
+ {
206
+ continue;
207
+ }
208
+ EditorGUILayout.ObjectField(t.name, t, typeof(Texture2D), false);
209
+ }
210
+ for (int i = 0; i < _orderedTextures.Count; i++)
211
+ {
212
+ Texture2D t = _orderedTextures[i];
213
+ if (t == null || !seen.Add(t))
214
+ {
215
+ continue;
216
+ }
217
+ EditorGUILayout.ObjectField(t.name, t, typeof(Texture2D), false);
218
+ }
177
219
  }
178
220
  EditorGUILayout.EndScrollView();
179
221
 
@@ -223,7 +265,36 @@ namespace WallstopStudios.UnityHelpers.Editor.Tools
223
265
  private void ApplyBlurToSelectedTextures()
224
266
  {
225
267
  int processedCount = 0;
226
- Texture2D[] toProcess = _manualTextures.Concat(_orderedTextures).Distinct().ToArray();
268
+ Texture2D[] toProcess;
269
+ using (
270
+ WallstopStudios.UnityHelpers.Utils.Buffers<Texture2D>.HashSet.Get(
271
+ out HashSet<Texture2D> seen
272
+ )
273
+ )
274
+ using (
275
+ WallstopStudios.UnityHelpers.Utils.Buffers<Texture2D>.List.Get(
276
+ out List<Texture2D> combined
277
+ )
278
+ )
279
+ {
280
+ for (int i = 0; i < _manualTextures.Count; i++)
281
+ {
282
+ Texture2D t = _manualTextures[i];
283
+ if (t != null && seen.Add(t))
284
+ {
285
+ combined.Add(t);
286
+ }
287
+ }
288
+ for (int i = 0; i < _orderedTextures.Count; i++)
289
+ {
290
+ Texture2D t = _orderedTextures[i];
291
+ if (t != null && seen.Add(t))
292
+ {
293
+ combined.Add(t);
294
+ }
295
+ }
296
+ toProcess = combined.ToArray();
297
+ }
227
298
  foreach (Texture2D originalTexture in toProcess)
228
299
  {
229
300
  string assetPath = AssetDatabase.GetAssetPath(originalTexture);
@@ -99,7 +99,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
99
99
 
100
100
  PropertyInfo prop = t.GetProperty(
101
101
  "CurrentContext",
102
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static
102
+ BindingFlags.Public | BindingFlags.Static
103
103
  );
104
104
  if (prop == null)
105
105
  {
@@ -47,7 +47,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Utils
47
47
  // Collect candidate types once and detect simple name collisions (same class name, different namespaces)
48
48
  List<Type> allCandidates = new();
49
49
  foreach (
50
- Type t in WallstopStudios.UnityHelpers.Core.Helper.ReflectionHelpers.GetTypesDerivedFrom(
50
+ Type t in ReflectionHelpers.GetTypesDerivedFrom(
51
51
  typeof(UnityHelpers.Utils.ScriptableObjectSingleton<>),
52
52
  includeAbstract: false
53
53
  )