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
@@ -20,7 +20,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Utils
20
20
  Track(instance.gameObject);
21
21
 
22
22
  Assert.IsTrue(CoroutineHandler.HasInstance);
23
- Assert.IsNotNull(instance);
23
+ Assert.IsTrue(instance != null);
24
24
  yield return null;
25
25
  }
26
26
 
@@ -11,7 +11,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Utils
11
11
  public void RoundtripVariousSizes()
12
12
  {
13
13
  Random random = new(12345);
14
- int[] sizes = new[] { 0, 1, 3, 5, 32, 64, 257, 1024, 4096 };
14
+ int[] sizes = { 0, 1, 3, 5, 32, 64, 257, 1024, 4096 };
15
15
  foreach (int length in sizes)
16
16
  {
17
17
  byte[] data = new byte[length];
@@ -24,7 +24,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Utils
24
24
  [Test]
25
25
  public void DecompressingGarbageThrows()
26
26
  {
27
- byte[] garbage = new byte[] { 1, 2, 3, 4, 5 };
27
+ byte[] garbage = { 1, 2, 3, 4, 5 };
28
28
  Assert.Throws<Exception>(() => LZMA.Decompress(garbage));
29
29
  }
30
30
  }
@@ -24,7 +24,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Utils
24
24
  );
25
25
 
26
26
  // Define a physics shape for the sprite (a simple rectangle)
27
- Vector2[] physicsShape = new[]
27
+ Vector2[] physicsShape =
28
28
  {
29
29
  new Vector2(-0.5f, -0.5f),
30
30
  new Vector2(-0.5f, 0.5f),
@@ -1,6 +1,5 @@
1
1
  namespace WallstopStudios.UnityHelpers.Tests.Visuals
2
2
  {
3
- using System.Reflection;
4
3
  using NUnit.Framework;
5
4
  using UnityEngine;
6
5
  using WallstopStudios.UnityHelpers.Core.Helper;
@@ -17,10 +16,10 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
17
16
  EnhancedImage image = CreateEnhancedImage(out Material baseMaterial);
18
17
  image.color = new Color(0.2f, 0.4f, 0.6f, 0.8f);
19
18
 
20
- InvokeLifecycle(image, "Start");
19
+ image.InvokeStartForTests();
21
20
 
22
21
  Material cached = image.material;
23
- Assert.That(cached, Is.Not.Null);
22
+ Assert.IsTrue(cached != null);
24
23
  Assert.AreNotSame(baseMaterial, cached);
25
24
  Assert.IsTrue(cached.GetColor("_Color").Approximately(image.color));
26
25
  }
@@ -29,13 +28,13 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
29
28
  public void HdrColorAboveSdrOverridesMaterialColor()
30
29
  {
31
30
  EnhancedImage image = CreateEnhancedImage(out _);
32
- InvokeLifecycle(image, "Start");
31
+ image.InvokeStartForTests();
33
32
 
34
33
  Color hdr = new(2f, 0.5f, 0.25f, 1f);
35
34
  image.HdrColor = hdr;
36
35
 
37
36
  Material cached = image.material;
38
- Assert.That(cached, Is.Not.Null);
37
+ Assert.IsTrue(cached != null);
39
38
  Assert.IsTrue(cached.GetColor("_Color").Approximately(hdr));
40
39
  }
41
40
 
@@ -45,11 +44,9 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
45
44
  EnhancedImage image = CreateEnhancedImage(out _);
46
45
  Texture2D mask = Track(new Texture2D(4, 4, TextureFormat.RGBA32, false, false));
47
46
 
48
- typeof(EnhancedImage)
49
- .GetField("_shapeMask", BindingFlags.Instance | BindingFlags.NonPublic)
50
- ?.SetValue(image, mask);
47
+ image._shapeMask = mask;
51
48
 
52
- InvokeLifecycle(image, "Start");
49
+ image.InvokeStartForTests();
53
50
 
54
51
  Texture maskInMaterial = image.material.GetTexture("_ShapeMask");
55
52
  Assert.That(maskInMaterial, Is.SameAs(mask));
@@ -59,21 +56,15 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
59
56
  public void OnDestroyReleasesCachedMaterialInstance()
60
57
  {
61
58
  EnhancedImage image = CreateEnhancedImage(out _);
62
- InvokeLifecycle(image, "Start");
59
+ image.InvokeStartForTests();
63
60
 
64
- FieldInfo cachedField = typeof(EnhancedImage).GetField(
65
- "_cachedMaterialInstance",
66
- BindingFlags.Instance | BindingFlags.NonPublic
67
- );
68
- Assert.That(cachedField, Is.Not.Null);
69
-
70
- Material cachedBefore = (Material)cachedField.GetValue(image);
71
- Assert.That(cachedBefore, Is.Not.Null);
61
+ Material cachedBefore = image.CachedMaterialInstanceForTests;
62
+ Assert.IsTrue(cachedBefore != null);
72
63
 
73
- InvokeLifecycle(image, "OnDestroy");
64
+ image.InvokeOnDestroyForTests();
74
65
 
75
- Material cachedAfter = (Material)cachedField.GetValue(image);
76
- Assert.That(cachedAfter, Is.Null);
66
+ Material cachedAfter = image.CachedMaterialInstanceForTests;
67
+ Assert.IsTrue(cachedAfter == null);
77
68
  Assert.That(cachedBefore == null, Is.True);
78
69
  }
79
70
 
@@ -83,17 +74,11 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
83
74
  EnhancedImage image = CreateEnhancedImage(out _);
84
75
  image.material = null;
85
76
 
86
- InvokeLifecycle(image, "Start");
77
+ image.InvokeStartForTests();
87
78
 
88
- FieldInfo field = typeof(EnhancedImage).GetField(
89
- "_cachedMaterialInstance",
90
- BindingFlags.Instance | BindingFlags.NonPublic
91
- );
92
- Assert.That(field, Is.Not.Null);
93
- Material cached = (Material)field.GetValue(image);
94
- Assert.That(
95
- cached,
96
- Is.Null,
79
+ Material cached = image.CachedMaterialInstanceForTests;
80
+ Assert.IsTrue(
81
+ cached == null,
97
82
  "Expected no instance to be created when material is null."
98
83
  );
99
84
  }
@@ -105,10 +90,10 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
105
90
  image.color = new Color(0.1f, 0.2f, 0.3f, 0.9f);
106
91
  image.HdrColor = new Color(0.4f, 0.4f, 0.4f, 0.4f);
107
92
 
108
- InvokeLifecycle(image, "Start");
93
+ image.InvokeStartForTests();
109
94
 
110
95
  Material cached = image.material;
111
- Assert.That(cached, Is.Not.Null);
96
+ Assert.IsTrue(cached != null);
112
97
  Assert.AreNotSame(baseMaterial, cached);
113
98
  Assert.IsTrue(
114
99
  cached.GetColor("_Color").Approximately(image.color),
@@ -120,9 +105,9 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
120
105
  public void MaterialInstanceIsReusedAcrossUpdates()
121
106
  {
122
107
  EnhancedImage image = CreateEnhancedImage(out _);
123
- InvokeLifecycle(image, "Start");
108
+ image.InvokeStartForTests();
124
109
  Material first = image.material;
125
- Assert.That(first, Is.Not.Null);
110
+ Assert.IsTrue(first != null);
126
111
 
127
112
  image.HdrColor = new Color(1.1f, 0.2f, 0.3f, 1f);
128
113
  Material second = image.material;
@@ -137,11 +122,11 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
137
122
  public void StartDoesNotDuplicateExistingInstance()
138
123
  {
139
124
  EnhancedImage image = CreateEnhancedImage(out _);
140
- InvokeLifecycle(image, "Start");
125
+ image.InvokeStartForTests();
141
126
  Material first = image.material;
142
- Assert.That(first, Is.Not.Null);
127
+ Assert.IsTrue(first != null);
143
128
 
144
- InvokeLifecycle(image, "Start");
129
+ image.InvokeStartForTests();
145
130
  Material second = image.material;
146
131
  Assert.That(
147
132
  second,
@@ -153,11 +138,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
153
138
  private EnhancedImage CreateEnhancedImage(out Material baseMaterial)
154
139
  {
155
140
  Shader shader = Shader.Find("UI/Default");
156
- Assert.That(
157
- shader,
158
- Is.Not.Null,
159
- "Expected UI/Default shader to be available for tests."
160
- );
141
+ Assert.IsTrue(shader != null, "Expected UI/Default shader to be available for tests.");
161
142
 
162
143
  baseMaterial = Track(new Material(shader));
163
144
 
@@ -167,18 +148,6 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
167
148
  return image;
168
149
  }
169
150
 
170
- private static void InvokeLifecycle(EnhancedImage image, string methodName)
171
- {
172
- MethodInfo method = typeof(EnhancedImage).GetMethod(
173
- methodName,
174
- BindingFlags.Instance | BindingFlags.NonPublic
175
- );
176
- Assert.That(
177
- method,
178
- Is.Not.Null,
179
- $"Expected method {methodName} to exist on EnhancedImage."
180
- );
181
- method.Invoke(image, null);
182
- }
151
+ // No reflection lifecycle helpers needed; use internal wrappers on EnhancedImage
183
152
  }
184
153
  }
@@ -67,14 +67,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Visuals
67
67
 
68
68
  public static Texture2D[] GetComputedTextures(LayeredImage image, List<Object> tracked)
69
69
  {
70
- Texture2D[] computed = (Texture2D[])
71
- typeof(LayeredImage)
72
- .GetField(
73
- "_computed",
74
- System.Reflection.BindingFlags.NonPublic
75
- | System.Reflection.BindingFlags.Instance
76
- )
77
- .GetValue(image);
70
+ Texture2D[] computed = image?.ComputedTexturesForTests;
78
71
 
79
72
  foreach (Texture2D texture in computed)
80
73
  {
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: eb975aaa9a7b55842a63ac0105034aa4
3
+ TextScriptImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.unity-helpers",
3
- "version": "2.0.0-rc81.9",
3
+ "version": "2.0.0",
4
4
  "displayName": "Unity Helpers",
5
5
  "description": "Various Unity Helper Library",
6
6
  "dependencies": {},
@@ -84,12 +84,16 @@
84
84
  "format:md:check": "prettier --check \"**/*.{md,markdown}\"",
85
85
  "format:json": "prettier --write \"**/*.{json,asmdef,asmref}\"",
86
86
  "format:json:check": "prettier --check \"**/*.{json,asmdef,asmref}\"",
87
+ "format:yaml": "prettier --write \"**/*.{yml,yaml}\"",
88
+ "format:yaml:check": "prettier --check \"**/*.{yml,yaml}\"",
87
89
  "lint:markdown": "markdownlint \"**/*.md\" \"**/*.markdown\" --config .markdownlint.json --ignore-path .markdownlintignore",
88
- "validate:content": "npm run lint:docs && npm run lint:markdown && npm run format:md:check && npm run format:json:check",
90
+ "validate:content": "npm run lint:docs && npm run lint:markdown && npm run format:md:check && npm run format:json:check && npm run format:yaml:check",
91
+ "validate:prepush": "npm run validate:content && npm run eol:check && npm run lint:tests",
92
+ "lint:tests": "pwsh -NoProfile -File scripts/lint-tests.ps1 -VerboseOutput",
89
93
  "prepublishOnly": "npm run validate:content"
90
94
  },
91
95
  "devDependencies": {
92
- "markdownlint-cli": "^0.40.0",
93
- "prettier": "^3.3.3"
96
+ "markdownlint-cli": "^0.45.0",
97
+ "prettier": "3.6.2"
94
98
  }
95
99
  }
@@ -24,10 +24,10 @@ $bomIssues = New-Object System.Collections.Generic.List[string]
24
24
 
25
25
  foreach ($path in Get-TrackedFiles) {
26
26
  try { $bytes = [System.IO.File]::ReadAllBytes($path) } catch { continue }
27
- # BOM
27
+ # BOM (flag any file that contains a UTF-8 BOM — we require NO BOM)
28
28
  $hasBom = $false
29
29
  if ($bytes.Length -ge 3) { $hasBom = ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) }
30
- if (-not $hasBom) { $bomIssues.Add($path) | Out-Null }
30
+ if ($hasBom) { $bomIssues.Add($path) | Out-Null }
31
31
  # CRLF check
32
32
  $hasLfOnly = $false
33
33
  for ($i = 0; $i -lt $bytes.Length; $i++) {
@@ -41,12 +41,11 @@ if ($VerboseOutput) {
41
41
  Write-Host "LF-only or mixed EOL files:"; $lfIssues | Sort-Object -Unique | ForEach-Object { " - $_" }
42
42
  }
43
43
  if ($bomIssues.Count -gt 0) {
44
- Write-Host "Missing UTF-8 BOM:"; $bomIssues | Sort-Object -Unique | ForEach-Object { " - $_" }
44
+ Write-Host "Contains UTF-8 BOM (disallowed):"; $bomIssues | Sort-Object -Unique | ForEach-Object { " - $_" }
45
45
  }
46
46
  }
47
47
 
48
48
  Write-Host "LF issues: $($lfIssues | Sort-Object -Unique | Measure-Object | % Count)"
49
- Write-Host "Missing BOM: $($bomIssues | Sort-Object -Unique | Measure-Object | % Count)"
49
+ Write-Host "Files with BOM: $($bomIssues | Sort-Object -Unique | Measure-Object | % Count)"
50
50
 
51
51
  if ($lfIssues.Count -gt 0 -or $bomIssues.Count -gt 0) { exit 3 }
52
-
@@ -0,0 +1,156 @@
1
+ Param(
2
+ [switch]$VerboseOutput
3
+ )
4
+
5
+ Set-StrictMode -Version Latest
6
+ $ErrorActionPreference = 'Stop'
7
+
8
+ function Write-Info($msg) {
9
+ if ($VerboseOutput) { Write-Host "[lint-tests] $msg" -ForegroundColor Cyan }
10
+ }
11
+
12
+ # Heuristics and allowlists
13
+ $testRoots = @('Tests')
14
+ $allowedHelperFiles = @(
15
+ 'Tests/Runtime/Visuals/VisualsTestHelpers.cs'
16
+ )
17
+
18
+ $destroyPattern = [regex]'(?<!UNH-SUPPRESS).*\b(?:UnityEngine\.)?Object\.(?:DestroyImmediate|Destroy)\s*\((?<arg>[^)]*)\)'
19
+ $createAssignObjectPattern = [regex]'(?<var>\b\w+)\s*=\s*new\s+(?<type>GameObject|Texture2D|Material|Mesh|Camera)\s*\('
20
+ $createInlineTrackPattern = [regex]'\bTrack\s*\(\s*new\s+(?:GameObject|Texture2D|Material|Mesh|Camera)\s*\('
21
+ $createSoAssignPattern = [regex]'(?<var>\b\w+)\s*=\s*ScriptableObject\.CreateInstance\s*<'
22
+
23
+ # Returns true if line contains an allowlisted helper file path
24
+ function Is-AllowlistedFile([string]$relPath) {
25
+ foreach ($a in $allowedHelperFiles) {
26
+ if ($relPath -replace '\\','/' -ieq $a) { return $true }
27
+ }
28
+ return $false
29
+ }
30
+
31
+ function Get-RelativePath([string]$path) {
32
+ $root = (Get-Location).Path
33
+ return ($path.Substring($root.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar))
34
+ }
35
+
36
+ $violations = @()
37
+
38
+ foreach ($root in $testRoots) {
39
+ if (-not (Test-Path $root)) { continue }
40
+ Get-ChildItem -Recurse -Include *.cs -Path $root | ForEach-Object {
41
+ $file = $_.FullName
42
+ $rel = Get-RelativePath $file
43
+ $content = Get-Content $file
44
+ $text = $content -join "`n"
45
+
46
+ if (Is-AllowlistedFile $rel) {
47
+ return
48
+ }
49
+
50
+ # Skip meta or non-source
51
+ if ($file -like '*.meta') { return }
52
+
53
+ # Check destroy calls; allow if argument var was tracked earlier in file
54
+ $lineIndex = 0
55
+ foreach ($line in $content) {
56
+ $lineIndex++
57
+ if ($destroyPattern.IsMatch($line)) {
58
+ $m = $destroyPattern.Match($line)
59
+ $arg = ($m.Groups['arg'].Value).Trim()
60
+ # Extract variable token before any commas or closing paren
61
+ $varName = $arg -replace ',.*','' -replace '\)',''
62
+ $allowed = $false
63
+ if (-not [string]::IsNullOrWhiteSpace($varName)) {
64
+ # Search up to 100 lines above for Track(varName)
65
+ $searchStart = [Math]::Max(0, $lineIndex - 100)
66
+ for ($i = $searchStart; $i -lt $lineIndex; $i++) {
67
+ if ($content[$i] -match "Track\s*\(\s*$varName\b") { $allowed = $true; break }
68
+ }
69
+ }
70
+ if (-not $allowed) {
71
+ $violations += (@{
72
+ Path=$rel; Line=$lineIndex; Message="UNH001: Avoid direct destroy in tests; track object and let teardown clean up (or add // UNH-SUPPRESS)"
73
+ })
74
+ }
75
+ }
76
+ }
77
+
78
+ # Check untracked new allocations via assignment (var = new Type(...))
79
+ $assignMatches = $createAssignObjectPattern.Matches($text)
80
+ foreach ($am in $assignMatches) {
81
+ $var = $am.Groups['var'].Value
82
+ if ([string]::IsNullOrWhiteSpace($var)) { continue }
83
+ # Find the index of this match in terms of line
84
+ $prefix = $text.Substring(0, $am.Index)
85
+ $lineNo = ($prefix -split "`n").Length
86
+ # Look ahead 10 lines for Track(var)
87
+ $endLine = [Math]::Min($content.Count, $lineNo + 10)
88
+ $found = $false
89
+ for ($j = $lineNo; $j -le $endLine; $j++) {
90
+ if ($content[$j-1] -match "Track\s*\(\s*$var\b") { $found = $true; break }
91
+ }
92
+ if (-not $found) {
93
+ $violations += (@{
94
+ Path=$rel; Line=$lineNo; Message="UNH002: Unity object allocation should be tracked: add Track($var)"
95
+ })
96
+ }
97
+ }
98
+
99
+ # Check inline Track(new ...) OK; but find bare inline new ... in args without Track
100
+ if ($text -match '\bnew\s+(GameObject|Texture2D|Material|Mesh|Camera)\s*\(') {
101
+ # If Track(new ...) not present at all, flag a generic warning at file level
102
+ if (-not $createInlineTrackPattern.IsMatch($text)) {
103
+ # locate first occurrence for line number
104
+ $m = [regex]::Match($text, '\bnew\s+(GameObject|Texture2D|Material|Mesh|Camera)\s*\(')
105
+ $lineNo = (($text.Substring(0, $m.Index)) -split "`n").Length
106
+ $violations += (@{
107
+ Path=$rel; Line=$lineNo; Message="UNH002: Inline Unity object creation should be passed to Track(new …)"
108
+ })
109
+ }
110
+ }
111
+
112
+ # Check ScriptableObject.CreateInstance<T>() assigned, ensure tracked
113
+ $soMatches = $createSoAssignPattern.Matches($text)
114
+ foreach ($sm in $soMatches) {
115
+ $var = $sm.Groups['var'].Value
116
+ if ([string]::IsNullOrWhiteSpace($var)) { continue }
117
+ $prefix = $text.Substring(0, $sm.Index)
118
+ $lineNo = ($prefix -split "`n").Length
119
+ $found = $false
120
+ $endLine = [Math]::Min($content.Count, $lineNo + 10)
121
+ for ($j = $lineNo; $j -le $endLine; $j++) {
122
+ if ($content[$j-1] -match "Track\s*\(\s*$var\b") { $found = $true; break }
123
+ }
124
+ if (-not $found) {
125
+ $violations += (@{
126
+ Path=$rel; Line=$lineNo; Message="UNH002: ScriptableObject instance should be tracked: add Track($var)"
127
+ })
128
+ }
129
+ }
130
+
131
+ # Enforce CommonTestBase inheritance only if file creates Unity objects and is under Runtime/ or Editor/
132
+ $createsUnity = ($assignMatches.Count -gt 0) -or ($text -match '\bnew\s+(GameObject|Texture2D|Material|Mesh|Camera)\s*\(') -or ($soMatches.Count -gt 0)
133
+ if ($createsUnity) {
134
+ $usesBase = ($text -match ':\s*CommonTestBase')
135
+ if (-not $usesBase) {
136
+ # Only enforce for test classes; skip helper-only files
137
+ if ($text -match '\bnamespace\s+WallstopStudios') {
138
+ $violations += (@{
139
+ Path=$rel; Line=1; Message="UNH003: Test classes creating Unity objects should inherit CommonTestBase (Editor or Runtime variant)"
140
+ })
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ if ($violations.Count -gt 0) {
148
+ Write-Host "Test lifecycle lint failed:" -ForegroundColor Red
149
+ foreach ($v in $violations) {
150
+ Write-Host ("{0}:{1}: {2}" -f $v.Path, $v.Line, $v.Message) -ForegroundColor Yellow
151
+ }
152
+ exit 1
153
+ } else {
154
+ Write-Info "No issues found in test code."
155
+ }
156
+
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: 7e3d3f61549d89e47b9d98c36a04b816
3
+ DefaultImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
@@ -28,7 +28,7 @@ function To-CrLf([string]$text) {
28
28
 
29
29
  $changed = 0
30
30
  $eolFixed = 0
31
- $bomAdded = 0
31
+ $bomRemoved = 0
32
32
  $modified = New-Object System.Collections.Generic.List[string]
33
33
 
34
34
  $tracked = Get-TrackedFiles
@@ -50,15 +50,13 @@ foreach ($path in $tracked) {
50
50
 
51
51
  $fileChanged = $false
52
52
  if ($normalized -ne $text) { $fileChanged = $true; $eolFixed++ }
53
- if (-not $hasBom) { $fileChanged = $true; $bomAdded++ }
53
+ # Remove BOM if present (we enforce UTF-8 without BOM)
54
+ if ($hasBom) { $fileChanged = $true; $bomRemoved++ }
54
55
 
55
56
  if ($fileChanged) {
56
57
  if (-not $DryRun) {
57
- $out = New-Object System.Collections.Generic.List[byte]
58
- # UTF-8 BOM
59
- $out.AddRange([byte[]](0xEF,0xBB,0xBF))
60
- $out.AddRange([System.Text.Encoding]::UTF8.GetBytes($normalized))
61
- [System.IO.File]::WriteAllBytes($path, $out.ToArray())
58
+ # Write UTF-8 without BOM
59
+ [System.IO.File]::WriteAllBytes($path, [System.Text.Encoding]::UTF8.GetBytes($normalized))
62
60
  }
63
61
  $changed++
64
62
  $modified.Add($path) | Out-Null
@@ -66,6 +64,5 @@ foreach ($path in $tracked) {
66
64
  }
67
65
  }
68
66
 
69
- Write-Host "Files fixed: $changed (EOL:$eolFixed, BOM:$bomAdded)"
67
+ Write-Host "Files fixed: $changed (EOL:$eolFixed, BOMRemoved:$bomRemoved)"
70
68
  if ($DryRun -and $changed -gt 0) { exit 2 }
71
-
@@ -1,135 +0,0 @@
1
- name: CSharpier
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- pull_request:
8
-
9
- permissions:
10
- contents: read
11
- pull-requests: write
12
-
13
- jobs:
14
- format-check:
15
- runs-on: ubuntu-latest
16
- steps:
17
- - name: Checkout
18
- uses: actions/checkout@v4
19
- with:
20
- fetch-depth: 0
21
-
22
- - name: Setup .NET SDK
23
- uses: actions/setup-dotnet@v4
24
- with:
25
- dotnet-version: '8.0.x'
26
-
27
- - name: Restore dotnet tools
28
- run: dotnet tool restore
29
-
30
- - name: CSharpier check
31
- id: csharpier_check
32
- run: |
33
- dotnet tool run csharpier check .
34
- continue-on-error: true
35
-
36
- - name: Generate CSharpier diff
37
- if: ${{ steps.csharpier_check.outcome == 'failure' }}
38
- run: |
39
- echo "Formatting issues detected. Generating diff..."
40
- dotnet tool run csharpier format .
41
- git config user.email "actions@github.com"
42
- git config user.name "github-actions[bot]"
43
- git diff > csharpier.patch || true
44
- # Provide a concise summary, too
45
- git status --porcelain > csharpier_changed_files.txt
46
-
47
- - name: Skip PR comment on fork
48
- if: ${{ github.event_name == 'pull_request' && steps.csharpier_check.outcome == 'failure' && github.event.pull_request.head.repo.fork }}
49
- run: |
50
- echo "Forked PR detected; skipping sticky PR comment due to restricted permissions. Full diff uploaded as artifact."
51
- {
52
- echo "## CSharpier Formatting Issues Detected"
53
- echo ""
54
- echo "- PR is from a fork; comment skipped due to token restrictions."
55
- echo "- Full diff uploaded as artifact 'csharpier-diff'."
56
- } >> $GITHUB_STEP_SUMMARY
57
-
58
- - name: Read diff for comment
59
- if: ${{ github.event_name == 'pull_request' && steps.csharpier_check.outcome == 'failure' && !github.event.pull_request.head.repo.fork }}
60
- id: read_diff
61
- shell: bash
62
- run: |
63
- # Limit diff size in comment to ~60KB to avoid API limits
64
- max=60000
65
- content=$(cat csharpier.patch)
66
- if [ ${#content} -gt $max ]; then
67
- echo "diff<<EOF" >> $GITHUB_OUTPUT
68
- echo "(Diff too large for inline comment. See attached artifact 'csharpier-diff'.)" >> $GITHUB_OUTPUT
69
- echo EOF >> $GITHUB_OUTPUT
70
- else
71
- echo "diff<<EOF" >> $GITHUB_OUTPUT
72
- printf '%s\n' "$content" >> $GITHUB_OUTPUT
73
- echo EOF >> $GITHUB_OUTPUT
74
- fi
75
-
76
- - name: Post CSharpier suggestion comment
77
- if: ${{ github.event_name == 'pull_request' && steps.csharpier_check.outcome == 'failure' && !github.event.pull_request.head.repo.fork }}
78
- uses: marocchino/sticky-pull-request-comment@v2
79
- with:
80
- header: csharpier-formatting
81
- message: |
82
- CSharpier found formatting changes. This job is failing until formatting is applied.
83
-
84
- To apply locally:
85
- 1) dotnet tool restore
86
- 2) dotnet tool run csharpier format .
87
-
88
- Diff of proposed changes:
89
-
90
- ```diff
91
- ${{ steps.read_diff.outputs.diff }}
92
- ```
93
- env:
94
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
95
-
96
- - name: Attach full patch as artifact
97
- if: ${{ steps.csharpier_check.outcome == 'failure' }}
98
- uses: actions/upload-artifact@v4
99
- with:
100
- name: csharpier-diff
101
- path: |
102
- csharpier.patch
103
- csharpier_changed_files.txt
104
-
105
- - name: Job summary (formatting required)
106
- if: ${{ steps.csharpier_check.outcome == 'failure' }}
107
- run: |
108
- {
109
- echo "## CSharpier Formatting Required"
110
- echo ""
111
- echo "- The formatter detected changes that need to be applied."
112
- echo "- Apply locally: dotnet tool restore && dotnet tool run csharpier format ."
113
- echo "- Changed files and full patch are uploaded as 'csharpier-diff' artifact."
114
- } >> $GITHUB_STEP_SUMMARY
115
- if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.fork }}" = "true" ]; then
116
- echo "- PR comment was skipped because this is a fork." >> $GITHUB_STEP_SUMMARY
117
- fi
118
-
119
- - name: Fail if formatting required
120
- if: ${{ steps.csharpier_check.outcome == 'failure' }}
121
- run: |
122
- echo "CSharpier formatting required. Failing the job." 1>&2
123
- exit 1
124
-
125
- - name: Success summary
126
- if: ${{ steps.csharpier_check.outcome == 'success' }}
127
- run: |
128
- echo "CSharpier check passed."
129
- {
130
- echo "## CSharpier Check Passed"
131
- echo ""
132
- echo "- No formatting changes required."
133
- } >> $GITHUB_STEP_SUMMARY
134
-
135
-