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
@@ -5,7 +5,6 @@ namespace WallstopStudios.UnityHelpers.Editor
5
5
  using UnityEngine;
6
6
  using UnityEditor;
7
7
  using System.Collections.Generic;
8
- using System.Linq;
9
8
  using System.IO;
10
9
  using UnityEngine.Serialization;
11
10
  using WallstopStudios.UnityHelpers.Core.Helper;
@@ -119,12 +118,29 @@ namespace WallstopStudios.UnityHelpers.Editor
119
118
  string[] guids = AssetDatabase.FindAssets(
120
119
  "t:" + nameof(PersistentDirectorySettings)
121
120
  );
122
- List<string> candidatePaths = guids
123
- .Select(AssetDatabase.GUIDToAssetPath)
124
- .Where(p => !string.IsNullOrWhiteSpace(p))
125
- .Select(SanitizePath)
126
- .Distinct(StringComparer.OrdinalIgnoreCase)
127
- .ToList();
121
+ List<string> candidatePaths = new();
122
+ using (
123
+ WallstopStudios
124
+ .UnityHelpers.Utils.SetBuffers<string>.GetHashSetPool(
125
+ StringComparer.OrdinalIgnoreCase
126
+ )
127
+ .Get(out HashSet<string> seen)
128
+ )
129
+ {
130
+ for (int i = 0; i < guids.Length; i++)
131
+ {
132
+ string path = AssetDatabase.GUIDToAssetPath(guids[i]);
133
+ if (string.IsNullOrWhiteSpace(path))
134
+ {
135
+ continue;
136
+ }
137
+ string sanitized = SanitizePath(path);
138
+ if (seen.Add(sanitized))
139
+ {
140
+ candidatePaths.Add(sanitized);
141
+ }
142
+ }
143
+ }
128
144
 
129
145
  PersistentDirectorySettings target =
130
146
  AssetDatabase.LoadAssetAtPath<PersistentDirectorySettings>(targetAssetPath);
@@ -292,12 +308,48 @@ namespace WallstopStudios.UnityHelpers.Editor
292
308
  return Array.Empty<DirectoryUsageData>();
293
309
  }
294
310
 
295
- DirectoryUsageData[] sortedDirectories = context
296
- .directories.OrderByDescending(directoryData => directoryData.count)
297
- .ThenByDescending(directoryData => directoryData.lastUsedTicks)
298
- .ToArray();
311
+ List<DirectoryUsageData> list = context.directories;
312
+ if (list == null || list.Count == 0)
313
+ {
314
+ return Array.Empty<DirectoryUsageData>();
315
+ }
316
+
317
+ DirectoryUsageData[] sortedDirectories = new DirectoryUsageData[list.Count];
318
+ for (int i = 0; i < list.Count; i++)
319
+ {
320
+ sortedDirectories[i] = list[i];
321
+ }
322
+ Array.Sort(
323
+ sortedDirectories,
324
+ static (a, b) =>
325
+ {
326
+ int cmp = b.count.CompareTo(a.count);
327
+ if (cmp != 0)
328
+ {
329
+ return cmp;
330
+ }
331
+ return b.lastUsedTicks.CompareTo(a.lastUsedTicks);
332
+ }
333
+ );
299
334
 
300
- return topOnly ? sortedDirectories.Take(topN).ToArray() : sortedDirectories;
335
+ if (topOnly)
336
+ {
337
+ int n =
338
+ topN < 0
339
+ ? 0
340
+ : (topN > sortedDirectories.Length ? sortedDirectories.Length : topN);
341
+ if (n == sortedDirectories.Length)
342
+ {
343
+ return sortedDirectories;
344
+ }
345
+ DirectoryUsageData[] result = new DirectoryUsageData[n];
346
+ for (int i = 0; i < n; i++)
347
+ {
348
+ result[i] = sortedDirectories[i];
349
+ }
350
+ return result;
351
+ }
352
+ return sortedDirectories;
301
353
  }
302
354
 
303
355
  private static void MergeSettings(
@@ -6,7 +6,6 @@ namespace WallstopStudios.UnityHelpers.Editor
6
6
  using System.Collections.Generic;
7
7
  using System.Diagnostics;
8
8
  using System.IO;
9
- using System.Linq;
10
9
  using System.Reflection;
11
10
  using UnityEditor;
12
11
  using UnityEditorInternal;
@@ -117,6 +116,10 @@ namespace WallstopStudios.UnityHelpers.Editor
117
116
  {
118
117
  ExportLastReport();
119
118
  }
119
+ if (GUILayout.Button("Export Report (CSV)"))
120
+ {
121
+ ExportLastReportCsv();
122
+ }
120
123
  }
121
124
  }
122
125
  finally
@@ -1267,18 +1270,31 @@ namespace WallstopStudios.UnityHelpers.Editor
1267
1270
  }
1268
1271
 
1269
1272
  [Serializable]
1270
- private sealed class ScanReport
1273
+ internal sealed class ScanReport
1271
1274
  {
1272
1275
  public readonly string[] folders;
1273
1276
  public readonly List<Item> items = new();
1274
1277
 
1275
1278
  public ScanReport(IEnumerable<string> folders)
1276
1279
  {
1277
- this.folders = folders?.ToArray() ?? Array.Empty<string>();
1280
+ if (folders == null)
1281
+ {
1282
+ this.folders = Array.Empty<string>();
1283
+ return;
1284
+ }
1285
+ // Manual copy to avoid LINQ
1286
+ using (Buffers<string>.List.Get(out List<string> list))
1287
+ {
1288
+ foreach (string s in folders)
1289
+ {
1290
+ list.Add(s);
1291
+ }
1292
+ this.folders = list.ToArray();
1293
+ }
1278
1294
  }
1279
1295
 
1280
1296
  [Serializable]
1281
- public sealed class Item
1297
+ internal sealed class Item
1282
1298
  {
1283
1299
  public string path;
1284
1300
  public string[] messages;
@@ -1286,7 +1302,11 @@ namespace WallstopStudios.UnityHelpers.Editor
1286
1302
 
1287
1303
  public void Add(string path, List<string> messages)
1288
1304
  {
1289
- items.Add(new Item { path = path, messages = messages.ToArray() });
1305
+ string[] arr =
1306
+ messages != null && messages.Count > 0
1307
+ ? messages.ToArray()
1308
+ : Array.Empty<string>();
1309
+ items.Add(new Item { path = path, messages = arr });
1290
1310
  }
1291
1311
  }
1292
1312
 
@@ -1324,6 +1344,53 @@ namespace WallstopStudios.UnityHelpers.Editor
1324
1344
  this.LogError($"Failed to save report: {e.Message}");
1325
1345
  }
1326
1346
  }
1347
+
1348
+ private void ExportLastReportCsv()
1349
+ {
1350
+ if (_lastReport == null || _lastReport.items.Count == 0)
1351
+ {
1352
+ EditorUi.Info("Prefab Checker", "No report data to export.");
1353
+ return;
1354
+ }
1355
+ string defaultPath = Application.dataPath + "/PrefabCheckerReport.csv";
1356
+ string savePath = EditorUi.Suppress
1357
+ ? defaultPath
1358
+ : EditorUtility.SaveFilePanel(
1359
+ "Save Prefab Checker Report (CSV)",
1360
+ Application.dataPath,
1361
+ "PrefabCheckerReport",
1362
+ "csv"
1363
+ );
1364
+ if (string.IsNullOrWhiteSpace(savePath))
1365
+ {
1366
+ return;
1367
+ }
1368
+
1369
+ try
1370
+ {
1371
+ using StreamWriter sw = new(savePath);
1372
+ sw.WriteLine("Path,Message");
1373
+ foreach (ScanReport.Item item in _lastReport.items)
1374
+ {
1375
+ string path = item.path?.Replace('"', '\'') ?? string.Empty;
1376
+ if (item.messages == null || item.messages.Length == 0)
1377
+ {
1378
+ sw.WriteLine($"\"{path}\",\"\"");
1379
+ continue;
1380
+ }
1381
+ foreach (string m in item.messages)
1382
+ {
1383
+ string msg = (m ?? string.Empty).Replace('"', '\'');
1384
+ sw.WriteLine($"\"{path}\",\"{msg}\"");
1385
+ }
1386
+ }
1387
+ this.Log($"Saved report to: {savePath}");
1388
+ }
1389
+ catch (Exception e)
1390
+ {
1391
+ this.LogError($"Failed to save CSV: {e.Message}");
1392
+ }
1393
+ }
1327
1394
  }
1328
1395
  #endif
1329
1396
  }
@@ -119,16 +119,16 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
119
119
  private bool _unchangedFoldout;
120
120
  private bool _orphansFoldout;
121
121
  private Vector2 _previewScroll;
122
- private string _filterText = string.Empty;
123
- private bool _filterUseRegex;
124
- private bool _sortAscending = true;
122
+ internal string _filterText = string.Empty;
123
+ internal bool _filterUseRegex;
124
+ internal bool _sortAscending = true;
125
125
 
126
- private enum AnimationStatus
126
+ public enum AnimationStatus
127
127
  {
128
- Unknown,
129
- New,
130
- Changed,
131
- Unchanged,
128
+ Unknown = 0,
129
+ New = 1,
130
+ Changed = 2,
131
+ Unchanged = 3,
132
132
  }
133
133
 
134
134
  internal enum CopyMode
@@ -138,7 +138,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
138
138
  New,
139
139
  }
140
140
 
141
- private sealed class AnimationFileInfo
141
+ internal sealed class AnimationFileInfo
142
142
  {
143
143
  public string RelativePath { get; set; }
144
144
  public string FullPath { get; set; }
@@ -291,12 +291,37 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
291
291
  bool canAnalyze = ArePathsValid();
292
292
  bool analysisDone = !_analysisNeeded;
293
293
 
294
- int selectedNew = _newAnimations.Count(a => a.Selected);
295
- int selectedChanged = _changedAnimations.Count(a => a.Selected);
294
+ int selectedNew = 0;
295
+ foreach (AnimationFileInfo newAnimation in _newAnimations)
296
+ {
297
+ if (newAnimation.Selected)
298
+ {
299
+ ++selectedNew;
300
+ }
301
+ }
302
+
303
+ int selectedChanged = 0;
304
+ foreach (AnimationFileInfo changedAnimation in _changedAnimations)
305
+ {
306
+ if (changedAnimation.Selected)
307
+ {
308
+ ++selectedChanged;
309
+ }
310
+ }
311
+
312
+ int selectedUnchanged = 0;
313
+ foreach (AnimationFileInfo unchangedAnimation in _unchangedAnimations)
314
+ {
315
+ if (unchangedAnimation.Selected)
316
+ {
317
+ ++selectedUnchanged;
318
+ }
319
+ }
320
+
296
321
  int selectedAll =
297
322
  selectedNew
298
323
  + selectedChanged
299
- + (_includeUnchangedInCopyAll ? _unchangedAnimations.Count(a => a.Selected) : 0);
324
+ + (_includeUnchangedInCopyAll ? selectedUnchanged : 0);
300
325
 
301
326
  bool canCopyNew = canAnalyze && analysisDone && selectedNew > 0;
302
327
  bool canCopyChanged = canAnalyze && analysisDone && selectedChanged > 0;
@@ -342,14 +367,8 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
342
367
  if (GUILayout.Button($"Copy All ({totalToCopyAll})"))
343
368
  {
344
369
  string overwriteWarning =
345
- selectedChanged
346
- + (
347
- _includeUnchangedInCopyAll
348
- ? _unchangedAnimations.Count(a => a.Selected)
349
- : 0
350
- )
351
- > 0
352
- ? $" This will overwrite {selectedChanged + (_includeUnchangedInCopyAll ? _unchangedAnimations.Count(a => a.Selected) : 0)} existing files."
370
+ selectedChanged + (_includeUnchangedInCopyAll ? selectedUnchanged : 0) > 0
371
+ ? $" This will overwrite {selectedChanged + (_includeUnchangedInCopyAll ? selectedUnchanged : 0)} existing files."
353
372
  : "";
354
373
  if (
355
374
  Confirm(
@@ -372,8 +391,8 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
372
391
 
373
392
  bool canAnalyze = ArePathsValid();
374
393
  bool analysisDone = !_analysisNeeded;
375
- bool hasUnchanged = _unchangedAnimations.Any();
376
- bool hasOrphans = _destinationOrphans.Any();
394
+ bool hasUnchanged = _unchangedAnimations.Count > 0;
395
+ bool hasOrphans = _destinationOrphans.Count > 0;
377
396
 
378
397
  _dryRun = EditorGUILayout.ToggleLeft("Dry Run (no changes)", _dryRun);
379
398
 
@@ -943,9 +962,15 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
943
962
  return;
944
963
  }
945
964
 
946
- List<AnimationFileInfo> animationsToDelete = _unchangedAnimations
947
- .Where(a => a is { Selected: true })
948
- .ToList();
965
+ List<AnimationFileInfo> animationsToDelete = new List<AnimationFileInfo>();
966
+ for (int i = 0; i < _unchangedAnimations.Count; i++)
967
+ {
968
+ AnimationFileInfo a = _unchangedAnimations[i];
969
+ if (a != null && a.Selected)
970
+ {
971
+ animationsToDelete.Add(a);
972
+ }
973
+ }
949
974
 
950
975
  if (animationsToDelete.Count == 0)
951
976
  {
@@ -1192,9 +1217,8 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
1192
1217
  return;
1193
1218
  }
1194
1219
  IEnumerable<AnimationFileInfo> filtered = ApplyFilterAndSort(items);
1195
- AnimationFileInfo[] animationFileInfos =
1196
- filtered as AnimationFileInfo[] ?? filtered.ToArray();
1197
- int count = animationFileInfos.Length;
1220
+ IList<AnimationFileInfo> animationFileInfos = filtered.AsList();
1221
+ int count = animationFileInfos.Count;
1198
1222
  if (count == 0)
1199
1223
  {
1200
1224
  using (new EditorGUILayout.HorizontalScope())
@@ -1217,36 +1241,41 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
1217
1241
  {
1218
1242
  if (GUILayout.Button("Select All", GUILayout.Width(100)))
1219
1243
  {
1220
- foreach (AnimationFileInfo info in animationFileInfos)
1244
+ for (int i = 0; i < animationFileInfos.Count; i++)
1221
1245
  {
1246
+ AnimationFileInfo info = animationFileInfos[i];
1222
1247
  info.Selected = true;
1223
1248
  }
1224
1249
  }
1225
1250
  if (GUILayout.Button("Select None", GUILayout.Width(100)))
1226
1251
  {
1227
- foreach (AnimationFileInfo info in animationFileInfos)
1252
+ for (int i = 0; i < animationFileInfos.Count; i++)
1228
1253
  {
1254
+ AnimationFileInfo info = animationFileInfos[i];
1229
1255
  info.Selected = false;
1230
1256
  }
1231
1257
  }
1232
1258
  if (GUILayout.Button("Select Filtered", GUILayout.Width(120)))
1233
1259
  {
1234
- foreach (AnimationFileInfo info in animationFileInfos)
1260
+ for (int i = 0; i < animationFileInfos.Count; i++)
1235
1261
  {
1262
+ AnimationFileInfo info = animationFileInfos[i];
1236
1263
  info.Selected = true;
1237
1264
  }
1238
1265
  }
1239
1266
  if (GUILayout.Button("Clear Filtered", GUILayout.Width(120)))
1240
1267
  {
1241
- foreach (AnimationFileInfo info in animationFileInfos)
1268
+ for (int i = 0; i < animationFileInfos.Count; i++)
1242
1269
  {
1270
+ AnimationFileInfo info = animationFileInfos[i];
1243
1271
  info.Selected = false;
1244
1272
  }
1245
1273
  }
1246
1274
  }
1247
1275
 
1248
- foreach (AnimationFileInfo info in animationFileInfos)
1276
+ for (int i = 0; i < animationFileInfos.Count; i++)
1249
1277
  {
1278
+ AnimationFileInfo info = animationFileInfos[i];
1250
1279
  using (new EditorGUILayout.HorizontalScope())
1251
1280
  {
1252
1281
  info.Selected = EditorGUILayout.Toggle(info.Selected, GUILayout.Width(20));
@@ -1272,38 +1301,77 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
1272
1301
  }
1273
1302
  }
1274
1303
 
1275
- private IEnumerable<AnimationFileInfo> ApplyFilterAndSort(List<AnimationFileInfo> items)
1304
+ internal IEnumerable<AnimationFileInfo> ApplyFilterAndSort(List<AnimationFileInfo> items)
1276
1305
  {
1277
- IEnumerable<AnimationFileInfo> query = items;
1278
- if (!string.IsNullOrWhiteSpace(_filterText))
1306
+ if (items == null || items.Count == 0)
1307
+ {
1308
+ yield break;
1309
+ }
1310
+
1311
+ using (Buffers<AnimationFileInfo>.List.Get(out List<AnimationFileInfo> filtered))
1279
1312
  {
1280
- if (_filterUseRegex)
1313
+ if (string.IsNullOrWhiteSpace(_filterText))
1314
+ {
1315
+ for (int i = 0; i < items.Count; i++)
1316
+ {
1317
+ AnimationFileInfo it = items[i];
1318
+ if (it != null)
1319
+ {
1320
+ filtered.Add(it);
1321
+ }
1322
+ }
1323
+ }
1324
+ else if (_filterUseRegex)
1281
1325
  {
1282
1326
  try
1283
1327
  {
1284
- Regex rx = new(_filterText, RegexOptions.IgnoreCase);
1285
- query = query.Where(i =>
1286
- i is { FileName: not null } && rx.IsMatch(i.FileName)
1287
- );
1328
+ Regex rx = new Regex(_filterText, RegexOptions.IgnoreCase);
1329
+ for (int i = 0; i < items.Count; i++)
1330
+ {
1331
+ AnimationFileInfo it = items[i];
1332
+ if (it != null && it.FileName != null && rx.IsMatch(it.FileName))
1333
+ {
1334
+ filtered.Add(it);
1335
+ }
1336
+ }
1288
1337
  }
1289
1338
  catch (Exception ex)
1290
1339
  {
1291
1340
  this.LogWarn($"Invalid regex '{_filterText}': {ex.Message}");
1292
- query = Enumerable.Empty<AnimationFileInfo>();
1293
1341
  }
1294
1342
  }
1295
1343
  else
1296
1344
  {
1297
- query = query.Where(i =>
1298
- i is { FileName: not null }
1299
- && i.FileName.IndexOf(_filterText, StringComparison.OrdinalIgnoreCase) >= 0
1300
- );
1345
+ for (int i = 0; i < items.Count; i++)
1346
+ {
1347
+ AnimationFileInfo it = items[i];
1348
+ if (
1349
+ it != null
1350
+ && it.FileName != null
1351
+ && it.FileName.IndexOf(_filterText, StringComparison.OrdinalIgnoreCase)
1352
+ >= 0
1353
+ )
1354
+ {
1355
+ filtered.Add(it);
1356
+ }
1357
+ }
1358
+ }
1359
+
1360
+ filtered.Sort(
1361
+ (a, b) =>
1362
+ {
1363
+ string an = a?.FileName ?? string.Empty;
1364
+ string bn = b?.FileName ?? string.Empty;
1365
+ int cmp = string.Compare(an, bn, StringComparison.OrdinalIgnoreCase);
1366
+ return _sortAscending ? cmp : -cmp;
1367
+ }
1368
+ );
1369
+
1370
+ for (int i = 0; i < filtered.Count; i++)
1371
+ {
1372
+ yield return filtered[i];
1301
1373
  }
1302
1374
  }
1303
- query = _sortAscending
1304
- ? query.OrderBy(i => i.FileName, StringComparer.OrdinalIgnoreCase)
1305
- : query.OrderByDescending(i => i.FileName, StringComparer.OrdinalIgnoreCase);
1306
- return query;
1307
1375
  }
1308
1376
 
1309
1377
  internal void MirrorDeleteDestinationAnimations()
@@ -1313,9 +1381,15 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
1313
1381
  return;
1314
1382
  }
1315
1383
 
1316
- List<AnimationFileInfo> toDelete = _destinationOrphans
1317
- .Where(a => a is { Selected: true })
1318
- .ToList();
1384
+ List<AnimationFileInfo> toDelete = new List<AnimationFileInfo>();
1385
+ for (int i = 0; i < _destinationOrphans.Count; i++)
1386
+ {
1387
+ AnimationFileInfo a = _destinationOrphans[i];
1388
+ if (a != null && a.Selected)
1389
+ {
1390
+ toDelete.Add(a);
1391
+ }
1392
+ }
1319
1393
  if (toDelete.Count == 0)
1320
1394
  {
1321
1395
  Info("Nothing to Delete", "No destination orphans are selected.");
@@ -1456,15 +1530,17 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
1456
1530
  bool useSource
1457
1531
  )
1458
1532
  {
1459
- AnimationFileInfo[] arr = ApplyFilterAndSort(list.ToList()).ToArray();
1460
- sb.AppendLine($"== {inputTitle} ({arr.Length}) ==");
1461
- foreach (AnimationFileInfo info in arr)
1533
+ var arr = ApplyFilterAndSort(list.ToList()).AsList();
1534
+ sb.AppendLine($"== {inputTitle} ({arr.Count}) ==");
1535
+ for (int i = 0; i < arr.Count; i++)
1462
1536
  {
1537
+ AnimationFileInfo info = arr[i];
1463
1538
  string path = useSource ? info.RelativePath : info.DestinationRelativePath;
1464
1539
  sb.AppendLine(
1465
1540
  $"[{(info.Selected ? 'x' : ' ')}] {info.FileName} -> {path}"
1466
1541
  );
1467
1542
  }
1543
+
1468
1544
  sb.AppendLine();
1469
1545
  }
1470
1546
 
@@ -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 System.Text.RegularExpressions;
9
8
  using UnityEditor;
10
9
  using UnityEngine;
@@ -485,9 +484,17 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
485
484
  bool matchesSearch = true;
486
485
  if (searchTerms.Length > 0)
487
486
  {
488
- matchesSearch = searchTerms.All(term =>
489
- currentName.Contains(term, StringComparison.OrdinalIgnoreCase)
490
- );
487
+ for (int si = 0; si < searchTerms.Length; si++)
488
+ {
489
+ if (
490
+ currentName.IndexOf(searchTerms[si], StringComparison.OrdinalIgnoreCase)
491
+ < 0
492
+ )
493
+ {
494
+ matchesSearch = false;
495
+ break;
496
+ }
497
+ }
491
498
  }
492
499
 
493
500
  if (matchesSearch)
@@ -614,10 +621,21 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
614
621
  );
615
622
  }
616
623
 
617
- bool canBulkName =
618
- animationData is { Count: > 0 }
619
- && animationData.Any(data => data.frames?.Count > 0)
620
- && !string.IsNullOrWhiteSpace(text);
624
+ bool canBulkName = animationData is { Count: > 0 } && !string.IsNullOrWhiteSpace(text);
625
+ if (canBulkName)
626
+ {
627
+ bool anyFrames = false;
628
+ for (int i = 0; i < animationData.Count; i++)
629
+ {
630
+ List<Sprite> fr = animationData[i]?.frames;
631
+ if (fr != null && fr.Count > 0)
632
+ {
633
+ anyFrames = true;
634
+ break;
635
+ }
636
+ }
637
+ canBulkName = anyFrames;
638
+ }
621
639
 
622
640
  using (new EditorGUI.DisabledScope(!canBulkName))
623
641
  {
@@ -694,16 +712,25 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
694
712
  }
695
713
  }
696
714
 
697
- if (
698
- !canBulkName
699
- && animationData is { Count: > 0 }
700
- && animationData.Any(data => data.frames?.Count > 0)
701
- )
715
+ if (!canBulkName && animationData is { Count: > 0 })
702
716
  {
703
- EditorGUILayout.HelpBox(
704
- "Enter text in the 'Text' field above to enable bulk naming operations.",
705
- MessageType.Info
706
- );
717
+ bool anyFrames = false;
718
+ for (int i = 0; i < animationData.Count; i++)
719
+ {
720
+ List<Sprite> fr = animationData[i]?.frames;
721
+ if (fr != null && fr.Count > 0)
722
+ {
723
+ anyFrames = true;
724
+ break;
725
+ }
726
+ }
727
+ if (anyFrames)
728
+ {
729
+ EditorGUILayout.HelpBox(
730
+ "Enter text in the 'Text' field above to enable bulk naming operations.",
731
+ MessageType.Info
732
+ );
733
+ }
707
734
  }
708
735
 
709
736
  EditorGUILayout.Space();
@@ -747,7 +774,16 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
747
774
  foreach (AnimationData data in animationData)
748
775
  {
749
776
  string lowerName = (data.animationName ?? string.Empty).ToLowerInvariant();
750
- if (searchTerms.All(term => lowerName.Contains(term)))
777
+ bool allMatch = true;
778
+ for (int i = 0; i < searchTerms.Length; i++)
779
+ {
780
+ if (lowerName.IndexOf(searchTerms[i], StringComparison.Ordinal) < 0)
781
+ {
782
+ allMatch = false;
783
+ break;
784
+ }
785
+ }
786
+ if (allMatch)
751
787
  {
752
788
  dataToCreate.Add(data);
753
789
  }
@@ -987,8 +1023,13 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
987
1023
  }
988
1024
 
989
1025
  List<string> searchPaths = new();
990
- foreach (Object source in animationSources.Where(Objects.NotNull))
1026
+ for (int i = 0; i < animationSources.Count; i++)
991
1027
  {
1028
+ Object source = animationSources[i];
1029
+ if (source == null)
1030
+ {
1031
+ continue;
1032
+ }
992
1033
  string path = AssetDatabase.GetAssetPath(source);
993
1034
  if (!string.IsNullOrWhiteSpace(path) && AssetDatabase.IsValidFolder(path))
994
1035
  {
@@ -1291,7 +1332,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
1291
1332
  continue;
1292
1333
  }
1293
1334
 
1294
- bool hasAnyIndex = entries.Any(e => e.index >= 0);
1335
+ bool hasAnyIndex = entries.Exists(e => e.index >= 0);
1295
1336
  if (strictNumericOrdering)
1296
1337
  {
1297
1338
  if (hasAnyIndex)
@@ -1445,7 +1486,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
1445
1486
  string dir = kvp.Key;
1446
1487
  foreach ((string baseKey, List<(int index, Sprite sprite)> entries) in kvp.Value)
1447
1488
  {
1448
- bool hasAnyIndex = entries.Any(e => e.index >= 0);
1489
+ bool hasAnyIndex = entries.Exists(e => e.index >= 0);
1449
1490
  if (strictNumericOrdering)
1450
1491
  {
1451
1492
  if (hasAnyIndex)
@@ -1517,7 +1558,7 @@ namespace WallstopStudios.UnityHelpers.Editor.Sprites
1517
1558
  folder = folderName,
1518
1559
  baseName = baseKey,
1519
1560
  count = entries.Count,
1520
- hasIndex = entries.Any(e => e.index >= 0),
1561
+ hasIndex = entries.Exists(e => e.index >= 0),
1521
1562
  };
1522
1563
  _autoParsePreview.Add(rec);
1523
1564
  }