com.wallstop-studios.unity-helpers 2.0.0 → 2.0.2

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 (135) hide show
  1. package/.github/workflows/format-on-demand.yml +2 -2
  2. package/.github/workflows/markdown-json.yml +1 -1
  3. package/.github/workflows/npm-publish.yml +1 -1
  4. package/.github/workflows/prettier-autofix.yml +4 -4
  5. package/.github/workflows/yaml-format-lint.yml +1 -1
  6. package/Docs/EFFECTS_SYSTEM.md +1316 -0
  7. package/{EFFECTS_SYSTEM_TUTORIAL.md → Docs/EFFECTS_SYSTEM_TUTORIAL.md} +1 -1
  8. package/{GETTING_STARTED.md → Docs/GETTING_STARTED.md} +10 -8
  9. package/{GLOSSARY.md → Docs/GLOSSARY.md} +4 -4
  10. package/Docs/HELPER_UTILITIES.md +885 -0
  11. package/Docs/HELPER_UTILITIES.md.meta +7 -0
  12. package/{INDEX.md → Docs/INDEX.md} +107 -62
  13. package/Docs/MATH_AND_EXTENSIONS.md +1039 -0
  14. package/{RANDOM_PERFORMANCE.md → Docs/RANDOM_PERFORMANCE.md} +15 -15
  15. package/{RELATIONAL_COMPONENTS.md → Docs/RELATIONAL_COMPONENTS.md} +21 -3
  16. package/{SPATIAL_TREES_2D_GUIDE.md → Docs/SPATIAL_TREES_2D_GUIDE.md} +2 -2
  17. package/{SPATIAL_TREES_3D_GUIDE.md → Docs/SPATIAL_TREES_3D_GUIDE.md} +1 -1
  18. package/{SPATIAL_TREE_2D_PERFORMANCE.md → Docs/SPATIAL_TREE_2D_PERFORMANCE.md} +64 -64
  19. package/{SPATIAL_TREE_3D_PERFORMANCE.md → Docs/SPATIAL_TREE_3D_PERFORMANCE.md} +64 -64
  20. package/Docs/UTILITY_COMPONENTS.md +906 -0
  21. package/Docs/UTILITY_COMPONENTS.md.meta +7 -0
  22. package/Docs/VISUAL_COMPONENTS.md +337 -0
  23. package/Docs/VISUAL_COMPONENTS.md.meta +7 -0
  24. package/Editor/Sprites/AnimationCopier.cs +3 -3
  25. package/README.md +69 -62
  26. package/Runtime/AssemblyInfo.cs +2 -0
  27. package/Runtime/Core/DataStructure/KDTree3D.cs +1 -1
  28. package/Runtime/Core/DataStructure/OctTree3D.cs +1 -1
  29. package/Runtime/Core/Extension/AsyncOperationExtensions.cs +122 -0
  30. package/Runtime/Core/Helper/Logging/UnityLogTagFormatter.cs +76 -90
  31. package/Runtime/Core/Serialization/ProtobufUnitySurrogates.cs +24 -29
  32. package/Runtime/Integrations/Reflex/AssemblyInfo.cs +7 -0
  33. package/Runtime/Integrations/Reflex/AssemblyInfo.cs.meta +11 -0
  34. package/Runtime/Integrations/Reflex/ContainerRelationalExtensions.cs +198 -0
  35. package/Runtime/Integrations/Reflex/ContainerRelationalExtensions.cs.meta +11 -0
  36. package/Runtime/Integrations/Reflex/RelationalComponentsInstaller.cs +86 -0
  37. package/Runtime/Integrations/Reflex/RelationalComponentsInstaller.cs.meta +11 -0
  38. package/Runtime/Integrations/Reflex/RelationalReflexSceneBootstrapper.cs +316 -0
  39. package/Runtime/Integrations/Reflex/RelationalReflexSceneBootstrapper.cs.meta +11 -0
  40. package/Runtime/Integrations/Reflex/RelationalSceneAssignmentOptions.cs +86 -0
  41. package/Runtime/Integrations/Reflex/RelationalSceneAssignmentOptions.cs.meta +11 -0
  42. package/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Integration.Reflex.asmdef +20 -0
  43. package/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Integration.Reflex.asmdef.meta +7 -0
  44. package/Runtime/Integrations/Reflex.meta +8 -0
  45. package/Runtime/Utils/ScriptableObjectSingleton.cs +1 -1
  46. package/Samples~/DI - Reflex/README.md +527 -0
  47. package/Samples~/DI - Reflex/README.md.meta +7 -0
  48. package/Samples~/DI - Reflex/Scripts/ReflexPaletteService.cs +36 -0
  49. package/Samples~/DI - Reflex/Scripts/ReflexPaletteService.cs.meta +11 -0
  50. package/Samples~/DI - Reflex/Scripts/ReflexRelationalConsumer.cs +79 -0
  51. package/Samples~/DI - Reflex/Scripts/ReflexRelationalConsumer.cs.meta +11 -0
  52. package/Samples~/DI - Reflex/Scripts/ReflexSampleInstaller.cs +30 -0
  53. package/Samples~/DI - Reflex/Scripts/ReflexSampleInstaller.cs.meta +11 -0
  54. package/Samples~/DI - Reflex/Scripts/ReflexSpawner.cs +79 -0
  55. package/Samples~/DI - Reflex/Scripts/ReflexSpawner.cs.meta +11 -0
  56. package/Samples~/DI - Reflex/Scripts/Samples.UnityHelpers.DI.Reflex.asmdef +26 -0
  57. package/Samples~/DI - Reflex/Scripts/Samples.UnityHelpers.DI.Reflex.asmdef.meta +9 -0
  58. package/Samples~/DI - Reflex/Scripts.meta +8 -0
  59. package/Samples~/DI - Reflex.meta +8 -0
  60. package/Samples~/DI - VContainer/README.md +6 -5
  61. package/Samples~/DI - Zenject/README.md +6 -5
  62. package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +29 -31
  63. package/Tests/Editor/Integrations/Reflex/ReflexIntegrationCompilationTests.cs +41 -0
  64. package/Tests/Editor/Integrations/Reflex/ReflexIntegrationCompilationTests.cs.meta +11 -0
  65. package/Tests/Editor/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Editor.Reflex.asmdef +27 -0
  66. package/Tests/Editor/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Editor.Reflex.asmdef.meta +7 -0
  67. package/Tests/Editor/Integrations/Reflex.meta +8 -0
  68. package/Tests/Editor/Integrations/VContainer/VContainerRelationalEntryPointTests.cs +15 -16
  69. package/Tests/Editor/Integrations/VContainer/VContainerRelationalHelpersTests.cs +7 -13
  70. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalHelpersTests.cs +7 -11
  71. package/Tests/Editor/Integrations/Zenject/ZenjectRelationalInitializerTests.cs +19 -21
  72. package/Tests/Editor/PersistentDirectorySettingsTests.cs +0 -1
  73. package/Tests/Editor/Sprites/AnimationCopierFilterTests.cs +0 -1
  74. package/Tests/Editor/Sprites/AnimationViewerWindowTests.cs +2 -2
  75. package/Tests/Editor/Tools/ImageBlurToolTests.cs +1 -1
  76. package/Tests/Editor/Utils/CommonTestBase.cs +17 -0
  77. package/Tests/Editor/Utils/ScriptableObjectSingletonCreatorTests.cs +1 -1
  78. package/Tests/Runtime/Extensions/AsyncOperationExtensionsTests.cs +179 -0
  79. package/Tests/Runtime/Extensions/RandomExtensionTests.cs +55 -0
  80. package/Tests/Runtime/Extensions/UnityLogTagFormatterEdgeTests.cs +84 -0
  81. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +445 -0
  82. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs.meta +11 -0
  83. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Runtime.Reflex.asmdef +28 -0
  84. package/Tests/Runtime/Integrations/Reflex/WallstopStudios.UnityHelpers.Tests.Runtime.Reflex.asmdef.meta +7 -0
  85. package/Tests/Runtime/Integrations/Reflex.meta +8 -0
  86. package/Tests/Runtime/Integrations/VContainer/RelationalComponentsVContainerTests.cs +24 -29
  87. package/Tests/Runtime/Integrations/VContainer/RelationalObjectPoolsVContainerTests.cs +8 -3
  88. package/Tests/Runtime/Integrations/Zenject/RelationalComponentsZenjectTests.cs +10 -20
  89. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +1 -1
  90. package/Tests/Runtime/Performance/SpatialTree2DPerformanceTests.cs +1 -1
  91. package/Tests/Runtime/Performance/SpatialTree3DPerformanceTests.cs +1 -1
  92. package/Tests/Runtime/Serialization/JsonRoundtripComprehensiveTests.cs +4 -9
  93. package/Tests/Runtime/Serialization/ProtoRoundtripComprehensiveTests.cs +13 -13
  94. package/Tests/Runtime/TestUtils/CommonTestBase.cs +11 -0
  95. package/Tests/Runtime/TestUtils/ReflexTestSupport.cs +111 -0
  96. package/Tests/Runtime/TestUtils/ReflexTestSupport.cs.meta +12 -0
  97. package/Tests/Runtime/Utils/MatchColliderToSpriteTests.cs +4 -4
  98. package/Tests/TestUtils.meta +8 -0
  99. package/package.json +6 -1
  100. package/EFFECTS_SYSTEM.md +0 -242
  101. package/MATH_AND_EXTENSIONS.md +0 -316
  102. /package/{CHANGELOG.md → Docs/CHANGELOG.md} +0 -0
  103. /package/{CHANGELOG.md.meta → Docs/CHANGELOG.md.meta} +0 -0
  104. /package/{CONTRIBUTING.md → Docs/CONTRIBUTING.md} +0 -0
  105. /package/{CONTRIBUTING.md.meta → Docs/CONTRIBUTING.md.meta} +0 -0
  106. /package/{DATA_STRUCTURES.md → Docs/DATA_STRUCTURES.md} +0 -0
  107. /package/{DATA_STRUCTURES.md.meta → Docs/DATA_STRUCTURES.md.meta} +0 -0
  108. /package/{EDITOR_TOOLS_GUIDE.md → Docs/EDITOR_TOOLS_GUIDE.md} +0 -0
  109. /package/{EDITOR_TOOLS_GUIDE.md.meta → Docs/EDITOR_TOOLS_GUIDE.md.meta} +0 -0
  110. /package/{EFFECTS_SYSTEM.md.meta → Docs/EFFECTS_SYSTEM.md.meta} +0 -0
  111. /package/{EFFECTS_SYSTEM_TUTORIAL.md.meta → Docs/EFFECTS_SYSTEM_TUTORIAL.md.meta} +0 -0
  112. /package/{GETTING_STARTED.md.meta → Docs/GETTING_STARTED.md.meta} +0 -0
  113. /package/{GLOSSARY.md.meta → Docs/GLOSSARY.md.meta} +0 -0
  114. /package/{HULLS.md → Docs/HULLS.md} +0 -0
  115. /package/{HULLS.md.meta → Docs/HULLS.md.meta} +0 -0
  116. /package/{INDEX.md.meta → Docs/INDEX.md.meta} +0 -0
  117. /package/{LICENSE.md → Docs/LICENSE.md} +0 -0
  118. /package/{LICENSE.md.meta → Docs/LICENSE.md.meta} +0 -0
  119. /package/{MATH_AND_EXTENSIONS.md.meta → Docs/MATH_AND_EXTENSIONS.md.meta} +0 -0
  120. /package/{RANDOM_PERFORMANCE.md.meta → Docs/RANDOM_PERFORMANCE.md.meta} +0 -0
  121. /package/{REFLECTION_HELPERS.md → Docs/REFLECTION_HELPERS.md} +0 -0
  122. /package/{REFLECTION_HELPERS.md.meta → Docs/REFLECTION_HELPERS.md.meta} +0 -0
  123. /package/{RELATIONAL_COMPONENTS.md.meta → Docs/RELATIONAL_COMPONENTS.md.meta} +0 -0
  124. /package/{SERIALIZATION.md → Docs/SERIALIZATION.md} +0 -0
  125. /package/{SERIALIZATION.md.meta → Docs/SERIALIZATION.md.meta} +0 -0
  126. /package/{SINGLETONS.md → Docs/SINGLETONS.md} +0 -0
  127. /package/{SINGLETONS.md.meta → Docs/SINGLETONS.md.meta} +0 -0
  128. /package/{SPATIAL_TREES_2D_GUIDE.md.meta → Docs/SPATIAL_TREES_2D_GUIDE.md.meta} +0 -0
  129. /package/{SPATIAL_TREES_3D_GUIDE.md.meta → Docs/SPATIAL_TREES_3D_GUIDE.md.meta} +0 -0
  130. /package/{SPATIAL_TREE_2D_PERFORMANCE.md.meta → Docs/SPATIAL_TREE_2D_PERFORMANCE.md.meta} +0 -0
  131. /package/{SPATIAL_TREE_3D_PERFORMANCE.md.meta → Docs/SPATIAL_TREE_3D_PERFORMANCE.md.meta} +0 -0
  132. /package/{SPATIAL_TREE_SEMANTICS.md → Docs/SPATIAL_TREE_SEMANTICS.md} +0 -0
  133. /package/{SPATIAL_TREE_SEMANTICS.md.meta → Docs/SPATIAL_TREE_SEMANTICS.md.meta} +0 -0
  134. /package/{THIRD_PARTY_NOTICES.md → Docs/THIRD_PARTY_NOTICES.md} +0 -0
  135. /package/{THIRD_PARTY_NOTICES.md.meta → Docs/THIRD_PARTY_NOTICES.md.meta} +0 -0
@@ -0,0 +1,1039 @@
1
+ # Core Math & Extensions
2
+
3
+ ## TL;DR — Why Use These
4
+
5
+ - Small helpers that fix everyday math and Unity annoyances: safe modulo, wrapped indices, robust equality, fast bounds math, color utilities, and more.
6
+ - Copy/paste examples and diagrams show intent; use as building blocks in hot paths.
7
+
8
+ This guide summarizes the math primitives and extension helpers in this package and shows how to apply them effectively, with examples, performance notes, and practical scenarios.
9
+
10
+ Contents
11
+
12
+ - [Numeric helpers](#numeric-helpers) — Positive modulo, wrapped arithmetic, approximate equality, clamping
13
+ - [Geometry](#geometry) — Lines, ranges, parabolas, point-in-polygon, polyline simplification
14
+ - [Unity extensions](#unity-extensions) — Rect/Bounds conversions, RectTransform bounds, camera bounds, bounds aggregation
15
+ - [Color utilities](#color-utilities) — Averaging (LAB/HSV/Weighted/Dominant), hex conversion
16
+ - [Collections](#collections) — IEnumerable helpers, buffering, infinite sequences
17
+ - [Strings](#strings) — Casing, encoding/decoding, distance
18
+ - [Direction helpers](#directions) — Enum conversions and operations
19
+ - [Enum helpers](#enum-helpers) — Zero-allocation flag checks, cached names, display names
20
+ - [Random generators](#random-generators) — Weighted selection, vector generation, subset sampling
21
+ - [Async/Coroutine interop](#async-coroutine-interop) — Bridge Unity AsyncOperation with async/await
22
+ - [Best Practices](#best-practices)
23
+
24
+ <a id="numeric-helpers"></a>
25
+
26
+ ## Numeric Helpers
27
+
28
+ - Positive modulo and wrap-around arithmetic
29
+ - Use `PositiveMod` to ensure non-negative modulo results for indices and cyclic counters.
30
+ - Use `WrappedAdd`/`WrappedIncrement` for ring buffer indexes and cursor navigation.
31
+
32
+ Example:
33
+
34
+ ```csharp
35
+ using WallstopStudios.UnityHelpers.Core.Helper;
36
+
37
+ int i = -1;
38
+ i = i.PositiveMod(5); // 4
39
+ i = i.WrappedAdd(2, 5); // 1
40
+
41
+ float angle = -30f;
42
+ float normalized = angle.PositiveMod(360f); // 330f
43
+ ```
44
+
45
+ Diagram (wrap-around on a ring of size 5):
46
+
47
+ ```text
48
+ Index: 0 1 2 3 4
49
+ ↖ ↙
50
+ \ +2 from 4 => 1
51
+
52
+ Start at 4, add 2 → 6 → 6 % 5 = 1
53
+ ```
54
+
55
+ - Approximate equality
56
+ - `float.Approximately(rhs, tolerance)` and `double.Approximately` add a magnitude-scaled fudge factor.
57
+
58
+ Example:
59
+
60
+ ```csharp
61
+ bool close = 0.1f.Approximately(0.10001f, 0.0001f); // true
62
+ ```
63
+
64
+ - Generic `Clamp`
65
+ - `Clamp<T>(min, max)` works for any `IComparable<T>`.
66
+
67
+ <a id="geometry"></a>
68
+
69
+ ## Geometry
70
+
71
+ ### Line2D — 2D line segment operations
72
+
73
+ **Why it exists:** Provides fast, battle-tested 2D line segment math for collision detection, ray-casting, and geometric queries.
74
+
75
+ **When to use:**
76
+
77
+ - Ray-casting for bullets, lasers, or line-of-sight checks
78
+ - Detecting if paths cross obstacles
79
+ - Click detection near edges or borders
80
+ - Finding closest points on paths or walls
81
+
82
+ **When NOT to use:**
83
+
84
+ - For 3D geometry (use Line3D instead)
85
+ - For curves or arcs (lines are always straight)
86
+
87
+ Example:
88
+
89
+ ```csharp
90
+ using WallstopStudios.UnityHelpers.Core.Math;
91
+ var a = new Line2D(new Vector2(0,0), new Vector2(2,0));
92
+ var b = new Line2D(new Vector2(1,-1), new Vector2(1,1));
93
+ bool hit = a.Intersects(b); // true
94
+ ```
95
+
96
+ Diagram (segment intersection):
97
+
98
+ ```text
99
+ y↑ b.to (1,1)
100
+ | │
101
+ | │ b
102
+ | a ────────┼────────▶ x
103
+ | (1,0)× ← intersection
104
+ | │
105
+ | │
106
+ +─────────────┼────────
107
+ b.from (1,-1)
108
+ ```
109
+
110
+ **Getting the exact intersection point:**
111
+
112
+ ```csharp
113
+ var wall = new Line2D(new Vector2(0, 0), new Vector2(10, 0));
114
+ var ray = new Line2D(playerPos, targetPos);
115
+
116
+ if (wall.TryGetIntersectionPoint(ray, out Vector2 hitPoint))
117
+ {
118
+ // Spawn bullet impact effect at exact hitPoint
119
+ Instantiate(sparksPrefab, hitPoint, Quaternion.identity);
120
+ }
121
+ ```
122
+
123
+ **Circle intersection (bullets hitting circular enemies):**
124
+
125
+ ```csharp
126
+ var bulletPath = new Line2D(bulletStart, bulletEnd);
127
+ var enemy = new Circle(enemyPosition, enemyRadius);
128
+
129
+ if (bulletPath.Intersects(enemy))
130
+ {
131
+ // Bullet hit the enemy
132
+ enemy.TakeDamage(bulletDamage);
133
+ }
134
+ ```
135
+
136
+ **Closest point on line (snapping to paths):**
137
+
138
+ ```csharp
139
+ var path = new Line2D(pathStart, pathEnd);
140
+ Vector2 snappedPosition = path.ClosestPointOnLine(mouseWorldPos);
141
+ // Use for UI snapping, path following, or grid alignment
142
+ ```
143
+
144
+ **Performance tip:** Use `DistanceSquaredToPoint` instead of `DistanceToPoint` when comparing distances (avoids expensive square root):
145
+
146
+ ```csharp
147
+ // Fast distance comparison (no sqrt)
148
+ float distSq = line.DistanceSquaredToPoint(point);
149
+ if (distSq < thresholdSquared)
150
+ {
151
+ // Point is within threshold
152
+ }
153
+ ```
154
+
155
+ ---
156
+
157
+ ### Line3D — 3D line segment operations
158
+
159
+ **Why it exists:** Extends Line2D concepts to 3D space with sophisticated algorithms for sphere intersection, bounding box clipping, and skew line distance.
160
+
161
+ **When to use:**
162
+
163
+ - 3D ray-casting for weapons, lasers, or grappling hooks
164
+ - Visibility checks between 3D objects
165
+ - Cable/rope collision detection
166
+ - Finding closest approach between moving objects
167
+
168
+ **When NOT to use:**
169
+
170
+ - For 2D games (use Line2D for better performance)
171
+ - For complex curved paths (lines are always straight)
172
+
173
+ Basic operations:
174
+
175
+ ```csharp
176
+ using WallstopStudios.UnityHelpers.Core.Math;
177
+
178
+ var ray = new Line3D(gunBarrel.position, hitPoint);
179
+ var enemyBounds = new BoundingBox3D(enemy.bounds);
180
+
181
+ // Check if ray hits enemy bounding box
182
+ if (ray.Intersects(enemyBounds))
183
+ {
184
+ enemy.TakeDamage(bulletDamage);
185
+ }
186
+ ```
187
+
188
+ **Closest points between two 3D lines (skew lines):**
189
+
190
+ _Problem:_ In 3D, two lines might not actually intersect (imagine two pipes that pass by each other). This finds the closest approach.
191
+
192
+ ```csharp
193
+ var ropeA = new Line3D(ropeAStart, ropeAEnd);
194
+ var ropeB = new Line3D(ropeBStart, ropeBEnd);
195
+
196
+ if (ropeA.TryGetClosestPoints(ropeB, out Vector3 pointOnA, out Vector3 pointOnB))
197
+ {
198
+ float separation = Vector3.Distance(pointOnA, pointOnB);
199
+ if (separation < 0.1f)
200
+ {
201
+ // Ropes are touching or tangled
202
+ }
203
+ }
204
+ ```
205
+
206
+ **Sphere intersection (force fields, explosions):**
207
+
208
+ ```csharp
209
+ var laserBeam = new Line3D(laserStart, laserEnd);
210
+ var shield = new Sphere(shieldCenter, shieldRadius);
211
+
212
+ if (laserBeam.Intersects(shield))
213
+ {
214
+ float distance = laserBeam.DistanceToSphere(shield);
215
+ // distance == 0 means line passes through sphere
216
+ // distance > 0 means line misses sphere
217
+ }
218
+ ```
219
+
220
+ ---
221
+
222
+ ### Range<T> — Numeric ranges with flexible boundaries
223
+
224
+ **Why it exists:** Solves the "is this value in a valid range" problem with clear, readable code and support for different boundary conditions.
225
+
226
+ **When to use:**
227
+
228
+ - Validating user input (is health between 0-100?)
229
+ - Time windows (is this event during business hours?)
230
+ - Array bounds checking with custom inclusivity
231
+ - Overlap detection (do these time slots conflict?)
232
+
233
+ **When NOT to use:**
234
+
235
+ - For single comparisons (just use `if (x >= min && x <= max)`)
236
+ - When you don't care about boundary inclusivity
237
+
238
+ Example:
239
+
240
+ ```csharp
241
+ using WallstopStudios.UnityHelpers.Core.Math;
242
+ var r = Range<int>.Inclusive(0, 10);
243
+ bool inside = r.Contains(10); // true (10 is included)
244
+ ```
245
+
246
+ **Choosing the right inclusivity:**
247
+
248
+ ```csharp
249
+ // [0, 10] - both endpoints included (closed interval)
250
+ var healthRange = Range<int>.Inclusive(0, 10);
251
+ healthRange.Contains(0); // true
252
+ healthRange.Contains(10); // true
253
+
254
+ // [0, 10) - start included, end excluded (common for indices)
255
+ var arrayRange = Range<int>.InclusiveExclusive(0, 10);
256
+ arrayRange.Contains(0); // true
257
+ arrayRange.Contains(10); // false (typical for array[0..10))
258
+
259
+ // (0, 1) - neither endpoint included (open interval)
260
+ var normalized = Range<float>.Exclusive(0f, 1f);
261
+ normalized.Contains(0f); // false
262
+ normalized.Contains(0.5f); // true
263
+ normalized.Contains(1f); // false
264
+ ```
265
+
266
+ **Overlap detection:**
267
+
268
+ ```csharp
269
+ var morningShift = Range<int>.Inclusive(9, 13); // 9am-1pm
270
+ var afternoonShift = Range<int>.Inclusive(13, 17); // 1pm-5pm
271
+
272
+ bool conflict = morningShift.Overlaps(afternoonShift); // true (overlap at 1pm)
273
+ ```
274
+
275
+ **Date ranges:**
276
+
277
+ ```csharp
278
+ var january = Range<DateTime>.Inclusive(
279
+ new DateTime(2025, 1, 1),
280
+ new DateTime(2025, 1, 31)
281
+ );
282
+
283
+ if (january.Contains(someDate))
284
+ {
285
+ // Event happened in January
286
+ }
287
+ ```
288
+
289
+ ---
290
+
291
+ ### Parabola — Projectile trajectories and smooth curves
292
+
293
+ **Why it exists:** Provides easy-to-use parabolic math for projectile motion, jump arcs, and smooth animation curves without writing quadratic equations by hand.
294
+
295
+ **When to use:**
296
+
297
+ - Throwing/shooting projectiles (grenades, arrows, basketballs)
298
+ - Character jump arcs
299
+ - Camera dolly movements along smooth paths
300
+ - Particle fountain effects
301
+
302
+ **When NOT to use:**
303
+
304
+ - For straight-line motion (use Vector3.Lerp)
305
+ - For complex curves with multiple peaks (parabola has only one peak)
306
+ - When gravity/physics simulation is already handling it
307
+
308
+ Example:
309
+
310
+ ```csharp
311
+ using WallstopStudios.UnityHelpers.Core.Math;
312
+
313
+ var p = new Parabola(maxHeight: 5f, length: 10f);
314
+ if (p.TryGetValueAtNormalized(0.5f, out float y))
315
+ {
316
+ // y == 5 (at the peak)
317
+ }
318
+ ```
319
+
320
+ Diagram (normalized parabola):
321
+
322
+ ```text
323
+ y↑ * vertex (0.5, 5)
324
+ | *
325
+ | *
326
+ | *
327
+ | *
328
+ |* *
329
+ +────────*────────▶ x (t from 0..1)
330
+ 0 0.5 1
331
+ ```
332
+
333
+ **Custom coefficients (when you have a specific equation):**
334
+
335
+ ```csharp
336
+ // Create parabola from equation y = -0.5x² + 5x
337
+ var p = Parabola.FromCoefficients(a: -0.5f, b: 5f, length: 10f);
338
+ ```
339
+
340
+ **Performance tip:** Use `GetValueAtUnchecked` when you know the input is in range (skips bounds checking):
341
+
342
+ ```csharp
343
+ // In a tight loop updating many projectiles
344
+ for (int i = 0; i < projectiles.Length; i++)
345
+ {
346
+ float x = projectiles[i].distanceTraveled;
347
+ if (x >= 0 && x <= parabola.Length)
348
+ {
349
+ float y = parabola.GetValueAtUnchecked(x); // No bounds check
350
+ projectiles[i].position.y = y;
351
+ }
352
+ }
353
+ ```
354
+
355
+ **Normalized vs Absolute coordinates:**
356
+
357
+ ```csharp
358
+ // Normalized: Use when working with 0-1 interpolation (animations)
359
+ float t = animationTime / totalDuration; // 0-1
360
+ parabola.TryGetValueAtNormalized(t, out float y);
361
+
362
+ // Absolute: Use when working with world-space coordinates
363
+ float worldX = transform.position.x;
364
+ parabola.TryGetValueAt(worldX, out float worldY);
365
+ ```
366
+
367
+ ---
368
+
369
+ ### Point-in-Polygon — Test if points are inside shapes
370
+
371
+ **Why it exists:** Detects whether a point lies inside an irregular polygon, solving the "did the player click this shape" problem.
372
+
373
+ **When to use:**
374
+
375
+ - Click detection in irregular UI shapes or game zones
376
+ - Testing if characters are inside territory boundaries
377
+ - Checking if waypoints are in walkable areas
378
+ - Testing if 3D points project inside mesh faces
379
+
380
+ **When NOT to use:**
381
+
382
+ - For circles (use `Vector2.Distance(point, center) <= radius`)
383
+ - For rectangles (use `Rect.Contains`)
384
+ - For complex 3D volumes (use Collider.bounds or raycasts)
385
+
386
+ **Important:** This uses the ray-casting algorithm — it counts how many times a ray from the point crosses polygon edges. Odd count = inside, even count = outside.
387
+
388
+ 2D polygon test:
389
+
390
+ ```csharp
391
+ using WallstopStudios.UnityHelpers.Core.Math;
392
+
393
+ Vector2[] zoneShape = new Vector2[]
394
+ {
395
+ new(0, 0), new(10, 0), new(10, 5), new(5, 10), new(0, 5)
396
+ };
397
+
398
+ Vector2 clickPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
399
+
400
+ if (PointPolygonCheck.IsPointInsidePolygon(clickPos, zoneShape))
401
+ {
402
+ Debug.Log("Clicked inside the zone!");
403
+ }
404
+ ```
405
+
406
+ 3D polygon with plane projection:
407
+
408
+ ```csharp
409
+ // Test if 3D point is inside a 3D triangle (projects onto plane)
410
+ Vector3[] triangleFace = new Vector3[]
411
+ {
412
+ new(0, 0, 0), new(5, 0, 0), new(2.5f, 5, 0)
413
+ };
414
+ Vector3 faceNormal = Vector3.forward; // Must be normalized
415
+
416
+ Vector3 testPoint = new Vector3(2.5f, 2f, 1f); // Will project onto z=0 plane
417
+
418
+ if (PointPolygonCheck.IsPointInsidePolygon(testPoint, triangleFace, faceNormal))
419
+ {
420
+ Debug.Log("Point projects inside triangle");
421
+ }
422
+ ```
423
+
424
+ **Zero-allocation version for hot paths:**
425
+
426
+ ```csharp
427
+ // Use ReadOnlySpan to avoid heap allocations
428
+ Span<Vector2> vertices = stackalloc Vector2[4]
429
+ {
430
+ new(0, 0), new(1, 0), new(1, 1), new(0, 1)
431
+ };
432
+
433
+ bool inside = PointPolygonCheck.IsPointInsidePolygon(clickPos, vertices);
434
+ // No GC allocations, great for per-frame checks
435
+ ```
436
+
437
+ **Edge cases to know:**
438
+
439
+ - Points exactly on polygon edges may return inconsistent results (floating-point precision issues)
440
+ - Assumes simple (non-self-intersecting) polygons
441
+ - Winding order (clockwise vs counter-clockwise) doesn't matter
442
+ - For 3D: all polygon vertices must be coplanar for accurate results
443
+
444
+ ---
445
+
446
+ - Polyline simplification (Douglas–Peucker)
447
+ - `Simplify` (float epsilon) and `SimplifyPrecise` (double tolerance) reduce vertex count while preserving shape.
448
+
449
+ Example:
450
+
451
+ ```csharp
452
+ using WallstopStudios.UnityHelpers.Core.Helper;
453
+ List<Vector2> simplified = LineHelper.Simplify(points, epsilon: 0.1f);
454
+ ```
455
+
456
+ Diagram (original vs simplified):
457
+
458
+ ```text
459
+ Original: *----*--*---*--*-----*
460
+ Simplified: *-----------*--------*
461
+
462
+ Fewer vertices within epsilon of the original polyline.
463
+ ```
464
+
465
+ Visual:
466
+ ![Polyline Simplification](Docs/Images/polyline_simplify.svg)
467
+
468
+ Convex hull (monotone chain / Jarvis examples used by helpers):
469
+
470
+ ```text
471
+ Points: · · ·
472
+ · · ·
473
+ · ·
474
+
475
+ Hull: ┌───────────┐
476
+ │ │
477
+ └───────┬───┘
478
+ └─┐
479
+ ```
480
+
481
+ Visual:
482
+ ![Convex Hull](Docs/Images/convex_hull.svg)
483
+
484
+ Edge Cases Gallery
485
+
486
+ ![Geometry Edge Cases](Docs/Images/geometry_edge_cases.svg)
487
+
488
+ <a id="unity-extensions"></a>
489
+
490
+ ## Unity Extensions
491
+
492
+ - Rect/Bounds conversions, RectTransform world bounds
493
+ - Camera `OrthographicBounds`
494
+ - Bounds aggregation from collections
495
+
496
+ Example:
497
+
498
+ ```csharp
499
+ Rect r = rectTransform.GetWorldRect();
500
+ Bounds view = Camera.main.OrthographicBounds();
501
+ ```
502
+
503
+ Diagrams:
504
+
505
+ - RectTransform world rect (axis-aligned bounds of rotated UI):
506
+
507
+ ```text
508
+ • corner ┌───────────────┐
509
+ ╲ │ AABB (r) │
510
+ ╲ rotated │ ┌──────┐ │
511
+ ╲ rectangle │ ╱│ UI ╱│ │
512
+ • │ ╱ └────╱─┘ │
513
+ └───────────────┘
514
+ ```
515
+
516
+ - Orthographic camera bounds (centered on camera):
517
+
518
+ ```text
519
+ ┌──────── view (Bounds) ────────┐
520
+ │ height=2*size │
521
+ │ ┌────────────────┐ │
522
+ near ───▶│ │ camera FOV │ │◀── far
523
+ │ └────────────────┘ │
524
+ └────────────────────────────────┘
525
+ ```
526
+
527
+ <a id="color-utilities"></a>
528
+
529
+ ## Color Utilities
530
+
531
+ - Averaging methods:
532
+ - LAB: perceptually accurate
533
+ - HSV: preserves vibrancy
534
+ - Weighted: luminance-aware
535
+ - Dominant: bucket-based mode
536
+
537
+ Example:
538
+
539
+ ```csharp
540
+ using WallstopStudios.UnityHelpers.Core.Extension;
541
+ Color avg = sprite.GetAverageColor(ColorAveragingMethod.LAB);
542
+ string html = avg.ToHex();
543
+ ```
544
+
545
+ Dominant color example (bucket-based):
546
+
547
+ ```csharp
548
+ // Emphasize palette extraction (posterized sprites, UI swatches)
549
+ var dominant = pixels.GetAverageColor(ColorAveragingMethod.Dominant, alphaCutoff: 0.05f);
550
+ ```
551
+
552
+ Diagram (dominant buckets):
553
+
554
+ ```text
555
+ RGB space buckets → counts
556
+ [R][G][B] … [R+Δ][G][B] … [R][G+Δ][B] …
557
+ ↑ pick max bucket centroid as dominant
558
+ ```
559
+
560
+ <a id="collections"></a>
561
+
562
+ ## Collections
563
+
564
+ ### IEnumerable Helpers
565
+
566
+ **Infinite cycling:**
567
+
568
+ ```csharp
569
+ using WallstopStudios.UnityHelpers.Core.Extension;
570
+
571
+ // Cycle through elements endlessly (great for repeating patterns)
572
+ var colors = new[] { Color.red, Color.blue, Color.green };
573
+ foreach (var color in colors.Infinite())
574
+ {
575
+ // Loops forever: red, blue, green, red, blue, green...
576
+ if (shouldStop) break;
577
+ }
578
+ ```
579
+
580
+ **Partition into chunks:**
581
+
582
+ ```csharp
583
+ // Split large collections into fixed-size batches
584
+ var items = Enumerable.Range(0, 100);
585
+ foreach (var batch in items.Partition(10))
586
+ {
587
+ // Process 10 items at a time
588
+ ProcessBatch(batch); // batch is a List<int> of size 10
589
+ }
590
+
591
+ // Zero-allocation version for hot paths
592
+ using (var batchBuffer = items.PartitionPooled(10))
593
+ {
594
+ foreach (var batch in batchBuffer)
595
+ {
596
+ // batch is reused from pool, no allocations
597
+ }
598
+ } // Automatically returns buffer to pool
599
+ ```
600
+
601
+ **Shuffled (non-destructive):**
602
+
603
+ ```csharp
604
+ // Get shuffled copy without modifying original
605
+ var shuffled = items.Shuffled();
606
+ // Original list unchanged
607
+ ```
608
+
609
+ ### IList Operations
610
+
611
+ **Remove O(1) by swapping with last element:**
612
+
613
+ ```csharp
614
+ // Fast removal when order doesn't matter (particle systems, entity lists)
615
+ List<Enemy> enemies = GetActiveEnemies();
616
+ enemies.RemoveAtSwapBack(3); // Swaps enemy[3] with last enemy, then removes
617
+ // Much faster than List.RemoveAt which shifts all elements
618
+ ```
619
+
620
+ **Partition (split by predicate):**
621
+
622
+ ```csharp
623
+ var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
624
+ var (evens, odds) = numbers.Partition(n => n % 2 == 0);
625
+ // evens: [2, 4, 6]
626
+ // odds: [1, 3, 5]
627
+ ```
628
+
629
+ **Custom sorting:**
630
+
631
+ ```csharp
632
+ // GhostSort: Faster hybrid sort for medium-sized lists
633
+ largeList.GhostSort(); // Uses IComparable<T>
634
+
635
+ // Custom comparison function
636
+ list.Sort((a, b) => a.priority.CompareTo(b.priority));
637
+ ```
638
+
639
+ ### Dictionary Helpers
640
+
641
+ **Thread-safe get-or-create:**
642
+
643
+ ```csharp
644
+ using WallstopStudios.UnityHelpers.Core.Extension;
645
+
646
+ // Thread-safe for ConcurrentDictionary
647
+ var value = dict.GetOrAdd(key, () => new ExpensiveObject());
648
+
649
+ // Read-only version (doesn't modify dict)
650
+ var value = readOnlyDict.GetOrElse(key, defaultValue);
651
+ ```
652
+
653
+ **Merge dictionaries:**
654
+
655
+ ```csharp
656
+ var defaults = new Dictionary<string, int> { ["health"] = 100, ["mana"] = 50 };
657
+ var overrides = new Dictionary<string, int> { ["health"] = 150 };
658
+
659
+ var merged = defaults.Merge(overrides);
660
+ // Result: { ["health"] = 150, ["mana"] = 50 }
661
+ ```
662
+
663
+ **Deep equality:**
664
+
665
+ ```csharp
666
+ // Compare dictionary contents (not just references)
667
+ bool same = dict1.ContentEquals(dict2); // Compares all key-value pairs
668
+ ```
669
+
670
+ ### Bounds from Collections
671
+
672
+ Bounds from points example:
673
+
674
+ ```csharp
675
+ using WallstopStudios.UnityHelpers.Core.Extension;
676
+
677
+ // Compute BoundsInt for occupied grid cells
678
+ Vector3Int[] positions = GetOccupiedCells();
679
+ BoundsInt? area = positions.GetBounds(inclusive: false);
680
+ if (area is BoundsInt b)
681
+ {
682
+ // b contains all positions
683
+ }
684
+ ```
685
+
686
+ Bounds aggregation example:
687
+
688
+ ```csharp
689
+ // Merge many Bounds (e.g., from Renderers)
690
+ Renderer[] renderers = GetComponentsInChildren<Renderer>();
691
+ Bounds? merged = renderers.Select(r => r.bounds).GetBounds();
692
+ if (merged is Bounds totalBounds)
693
+ {
694
+ // totalBounds encompasses all renderers
695
+ }
696
+ ```
697
+
698
+ <a id="strings"></a>
699
+
700
+ ## Strings
701
+
702
+ ### Case Conversions
703
+
704
+ **Why it exists:** Automatically convert between common programming case styles without writing regex or manual parsing.
705
+
706
+ ```csharp
707
+ using WallstopStudios.UnityHelpers.Core.Extension;
708
+
709
+ string input = "XMLHttpRequest";
710
+
711
+ input.ToPascalCase(); // "XmlHttpRequest"
712
+ input.ToCamelCase(); // "xmlHttpRequest"
713
+ input.ToSnakeCase(); // "xml_http_request"
714
+ input.ToKebabCase(); // "xml-http-request"
715
+ input.ToTitleCase(); // "Xml Http Request"
716
+ ```
717
+
718
+ Smart tokenization handles mixed cases intelligently.
719
+
720
+ ### String Utilities
721
+
722
+ **Levenshtein Distance (edit distance):**
723
+
724
+ ```csharp
725
+ // Calculate how many edits to transform one string into another
726
+ string a = "kitten";
727
+ string b = "sitting";
728
+ int distance = a.LevenshteinDistance(b); // 3 edits
729
+ // Use for: fuzzy matching, spell correction, search suggestions
730
+ ```
731
+
732
+ **Base64 encoding:**
733
+
734
+ ```csharp
735
+ string text = "Hello, World!";
736
+ string encoded = text.ToBase64(); // "SGVsbG8sIFdvcmxkIQ=="
737
+ string decoded = encoded.FromBase64(); // "Hello, World!"
738
+ ```
739
+
740
+ **String analysis:**
741
+
742
+ ```csharp
743
+ bool isNum = "12345".IsNumeric(); // true
744
+ bool isAlpha = "Hello".IsAlphabetic(); // true
745
+ bool isAlphaNum = "Hello123".IsAlphanumeric(); // true
746
+ ```
747
+
748
+ **Truncate with ellipsis:**
749
+
750
+ ```csharp
751
+ string long = "This is a very long string";
752
+ string short = long.Truncate(10); // "This is a..."
753
+ ```
754
+
755
+ ### Encoding Helpers
756
+
757
+ ```csharp
758
+ // Quick UTF-8 conversions
759
+ byte[] bytes = "Hello".GetBytes();
760
+ string text = bytes.GetString();
761
+ ```
762
+
763
+ <a id="directions"></a>
764
+
765
+ ## Directions
766
+
767
+ - Conversions between enum and vectors; splitting flag sets; combining
768
+
769
+ Example:
770
+
771
+ ```csharp
772
+ using WallstopStudios.UnityHelpers.Core.Extension;
773
+ Vector2Int v = Direction.NorthWest.AsVector2Int(); // (-1, 1)
774
+ ```
775
+
776
+ <a id="enum-helpers"></a>
777
+
778
+ ## Enum Helpers
779
+
780
+ **Why it exists:** Standard C# enum operations cause boxing allocations and are slow in hot paths. These helpers solve performance problems.
781
+
782
+ ### Zero-Allocation Flag Checking
783
+
784
+ **The problem:** Standard `HasFlag()` boxes both enums, causing GC pressure.
785
+
786
+ ```csharp
787
+ using WallstopStudios.UnityHelpers.Core.Extension;
788
+
789
+ [Flags]
790
+ public enum Permissions
791
+ {
792
+ None = 0,
793
+ Read = 1,
794
+ Write = 2,
795
+ Execute = 4
796
+ }
797
+
798
+ Permissions userPerms = Permissions.Read | Permissions.Write;
799
+
800
+ // ❌ BAD: Causes boxing allocations
801
+ if (userPerms.HasFlag(Permissions.Write)) { }
802
+
803
+ // ✅ GOOD: Zero allocations
804
+ if (userPerms.HasFlagNoAlloc(Permissions.Write)) { }
805
+ ```
806
+
807
+ Use `HasFlagNoAlloc` in:
808
+
809
+ - Per-frame checks
810
+ - Hot loops
811
+ - Frequently-called methods
812
+ - Performance-critical code paths
813
+
814
+ ### Fast Enum-to-String Conversion
815
+
816
+ **The problem:** `enum.ToString()` is slow (reflection) and allocates every call.
817
+
818
+ ```csharp
819
+ public enum GameState { MainMenu, Playing, Paused, GameOver }
820
+
821
+ GameState state = GameState.Playing;
822
+
823
+ // ❌ SLOW: Uses reflection every time
824
+ string name = state.ToString();
825
+
826
+ // ✅ FAST: Cached in array/dictionary after first call
827
+ string cached = state.ToCachedName();
828
+ // Subsequent calls are O(1) lookups with zero allocation
829
+ ```
830
+
831
+ Performance: ToCachedName is ~100x faster after the first call.
832
+
833
+ ### Display Names for UI
834
+
835
+ **The problem:** Enum values often need different names in UI than in code.
836
+
837
+ ```csharp
838
+ using WallstopStudios.UnityHelpers.Core.Attribute;
839
+
840
+ public enum Difficulty
841
+ {
842
+ [EnumDisplayName("Easy Mode")]
843
+ Easy,
844
+
845
+ [EnumDisplayName("Normal")]
846
+ Medium,
847
+
848
+ [EnumDisplayName("NIGHTMARE MODE!!!")]
849
+ Hard
850
+ }
851
+
852
+ Difficulty current = Difficulty.Hard;
853
+ string displayName = current.ToDisplayName(); // "NIGHTMARE MODE!!!"
854
+ // Falls back to enum name if attribute not present
855
+ ```
856
+
857
+ Use for:
858
+
859
+ - Dropdown labels in UI
860
+ - Localization keys
861
+ - User-facing text that doesn't match code names
862
+
863
+ <a id="random-generators"></a>
864
+
865
+ ## Random Generators
866
+
867
+ **Why it exists:** Unity's `Random` class is limited and not suitable for all scenarios. These extensions provide rich random generation.
868
+
869
+ ### Weighted Random Selection
870
+
871
+ **The problem:** Selecting items based on probability weights (loot tables, spawn chances).
872
+
873
+ ```csharp
874
+ using WallstopStudios.UnityHelpers.Core.Extension;
875
+
876
+ // Items with different drop chances
877
+ var loot = new[]
878
+ {
879
+ (item: "Common Sword", weight: 50),
880
+ (item: "Rare Shield", weight: 30),
881
+ (item: "Epic Helmet", weight: 15),
882
+ (item: "Legendary Ring", weight: 5)
883
+ };
884
+
885
+ IRandom rng = PRNG.Instance;
886
+ string drop = rng.NextWeighted(loot); // More likely to get Common Sword
887
+
888
+ // Get index instead of value
889
+ int dropIndex = rng.NextWeightedIndex(loot.Select(x => x.weight));
890
+ ```
891
+
892
+ ### Vector and Quaternion Generation
893
+
894
+ **Uniform random vectors:**
895
+
896
+ ```csharp
897
+ // Random point in rectangle
898
+ Vector2 point = rng.NextVector2(minX, maxX, minY, maxY);
899
+
900
+ // Random point inside circle
901
+ Vector2 inCircle = rng.NextVector2InRange(radius);
902
+
903
+ // Random point ON sphere surface (uniform distribution)
904
+ Vector3 onSphere = rng.NextVector3OnSphere(radius);
905
+ // Uses Marsaglia's method for true uniform distribution
906
+
907
+ // Random rotation (uniform distribution)
908
+ Quaternion rotation = rng.NextQuaternion();
909
+ // Uses Shoemake's algorithm
910
+ ```
911
+
912
+ ### Color Generation
913
+
914
+ ```csharp
915
+ // Random opaque color
916
+ Color color = rng.NextColor();
917
+
918
+ // Random color in HSV range (for similar hues)
919
+ Color tint = rng.NextColorInRange(
920
+ baseColor: Color.red,
921
+ hueVariance: 0.1f,
922
+ saturationVariance: 0.2f,
923
+ valueVariance: 0.2f
924
+ );
925
+ ```
926
+
927
+ ### Subset Sampling
928
+
929
+ **Reservoir sampling** — Pick k random items from a large collection without loading it all into memory:
930
+
931
+ ```csharp
932
+ // Select 5 random enemies from potentially huge list
933
+ IEnumerable<Enemy> allEnemies = GetAllEnemiesInWorld();
934
+ List<Enemy> randomFive = rng.NextSubset(allEnemies, k: 5);
935
+ // O(n) time, uses reservoir sampling for uniform probability
936
+ ```
937
+
938
+ ### Random Utilities
939
+
940
+ ```csharp
941
+ bool coinFlip = rng.NextBool(); // 50/50
942
+ bool biasedFlip = rng.NextBool(0.7f); // 70% true
943
+ int sign = rng.NextSign(); // Randomly -1 or +1
944
+ ```
945
+
946
+ <a id="async-coroutine-interop"></a>
947
+
948
+ ## Async/Coroutine Interop
949
+
950
+ **Why it exists:** Unity's `AsyncOperation` and coroutines don't natively support modern async/await patterns. This bridges the gap.
951
+
952
+ ### Await AsyncOperation (Unity < 2023.1)
953
+
954
+ **The problem:** Unity's AsyncOperations (scene loading, asset loading) don't support `await`.
955
+
956
+ ```csharp
957
+ using WallstopStudios.UnityHelpers.Core.Extension;
958
+ using UnityEngine.SceneManagement;
959
+
960
+ // ✅ Now you can await scene loading
961
+ async Task LoadGameScene()
962
+ {
963
+ var operation = SceneManager.LoadSceneAsync("GameLevel");
964
+ await operation; // Just works!
965
+
966
+ Debug.Log("Scene loaded!");
967
+ }
968
+ ```
969
+
970
+ **Note:** Unity 2023.1+ has built-in await support, but this works in older versions.
971
+
972
+ ### Convert AsyncOperation to Task
973
+
974
+ ```csharp
975
+ // As Task
976
+ Task task = asyncOperation.AsTask();
977
+ await task;
978
+
979
+ // As ValueTask (better performance for short operations)
980
+ ValueTask valueTask = asyncOperation.AsValueTask();
981
+ await valueTask;
982
+ ```
983
+
984
+ ### Run Task as Coroutine
985
+
986
+ **The problem:** You have async/await code (from a library, or your own), but need to run it in a Unity coroutine context.
987
+
988
+ ```csharp
989
+ using WallstopStudios.UnityHelpers.Core.Extension;
990
+
991
+ async Task<string> DownloadDataAsync()
992
+ {
993
+ // Some async operation (HttpClient, database, etc.)
994
+ await Task.Delay(1000);
995
+ return "Downloaded data";
996
+ }
997
+
998
+ // In MonoBehaviour
999
+ IEnumerator Start()
1000
+ {
1001
+ // ✅ Convert Task to IEnumerator
1002
+ return DownloadDataAsync().AsCoroutine();
1003
+ }
1004
+ ```
1005
+
1006
+ ### Chain Continuations
1007
+
1008
+ ```csharp
1009
+ // Chain operations on ValueTask
1010
+ await myValueTask.WithContinuation(() => Debug.Log("Done!"));
1011
+ ```
1012
+
1013
+ **When to use:**
1014
+
1015
+ - Integrating third-party async libraries with Unity
1016
+ - Mixing async/await code with existing coroutine systems
1017
+ - Background operations that need to update Unity objects on completion
1018
+ - Modernizing legacy coroutine code
1019
+
1020
+ **When NOT to use:**
1021
+
1022
+ - Unity 2023.1+ (use built-in await support)
1023
+ - Simple fire-and-forget operations (just use coroutines)
1024
+ - When you have control over both ends (just use all-async or all-coroutines)
1025
+
1026
+ ## Best Practices
1027
+
1028
+ - Use `PositiveMod` instead of `%` for indices and angles when negatives are possible.
1029
+ - Prefer `SimplifyPrecise` for offline tooling; use `Simplify` during gameplay for speed.
1030
+ - Choose color averaging method per goal: LAB for perceptual palette, Weighted for speed, Dominant for swatches.
1031
+ - Favor IReadOnlyList/HashSet specializations to minimize allocations; pooled buffers are used where applicable.
1032
+ - Run Unity-dependent extensions (e.g., `RectTransform`, `Camera`, `Grid`) on the main thread.
1033
+
1034
+ ## Related Docs
1035
+
1036
+ - Random performance details — [Random Performance](RANDOM_PERFORMANCE.md)
1037
+ - Serialization formats — [Serialization Guide](SERIALIZATION.md)
1038
+ - Effects system — [Effects System](EFFECTS_SYSTEM.md)
1039
+ - Relational Components — [Relational Components](RELATIONAL_COMPONENTS.md)