com.wallstop-studios.unity-helpers 2.0.4 → 2.1.1
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.
- package/Docs/DATA_STRUCTURES.md +7 -7
- package/Docs/EFFECTS_SYSTEM.md +836 -8
- package/Docs/EFFECTS_SYSTEM_TUTORIAL.md +77 -18
- package/Docs/HULLS.md +2 -2
- package/Docs/ILIST_SORTING_PERFORMANCE.md +92 -0
- package/Docs/ILIST_SORTING_PERFORMANCE.md.meta +7 -0
- package/Docs/INDEX.md +10 -1
- package/Docs/Images/random_generators.svg +7 -7
- package/Docs/RANDOM_PERFORMANCE.md +18 -15
- package/Docs/REFLECTION_HELPERS.md +1 -1
- package/Docs/RELATIONAL_COMPONENTS.md +51 -6
- package/Docs/SERIALIZATION.md +1 -1
- package/Docs/SINGLETONS.md +2 -2
- package/Docs/SPATIAL_TREES_2D_GUIDE.md +3 -3
- package/Docs/SPATIAL_TREES_3D_GUIDE.md +3 -3
- package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
- package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
- package/Docs/SPATIAL_TREE_SEMANTICS.md +7 -7
- package/Editor/Core/Helper/AnimationEventHelpers.cs +1 -1
- package/Editor/CustomDrawers/WShowIfPropertyDrawer.cs +131 -41
- package/Editor/Utils/ScriptableObjectSingletonCreator.cs +175 -18
- package/README.md +42 -18
- package/Runtime/Core/Extension/IListExtensions.cs +720 -12
- package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +2 -3
- package/Runtime/Core/Random/AbstractRandom.cs +52 -5
- package/Runtime/Core/Random/DotNetRandom.cs +3 -3
- package/Runtime/Core/Random/FlurryBurstRandom.cs +285 -0
- package/Runtime/Core/Random/FlurryBurstRandom.cs.meta +3 -0
- package/Runtime/Core/Random/IllusionFlow.cs +3 -3
- package/Runtime/Core/Random/LinearCongruentialGenerator.cs +3 -3
- package/Runtime/Core/Random/PcgRandom.cs +6 -6
- package/Runtime/Core/Random/PhotonSpinRandom.cs +387 -0
- package/Runtime/Core/Random/PhotonSpinRandom.cs.meta +3 -0
- package/Runtime/Core/Random/RomuDuo.cs +3 -3
- package/Runtime/Core/Random/SplitMix64.cs +3 -3
- package/Runtime/Core/Random/SquirrelRandom.cs +6 -4
- package/Runtime/Core/Random/StormDropRandom.cs +271 -0
- package/Runtime/Core/Random/StormDropRandom.cs.meta +3 -0
- package/Runtime/Core/Random/UnityRandom.cs +3 -3
- package/Runtime/Core/Random/WyRandom.cs +6 -4
- package/Runtime/Core/Random/XorShiftRandom.cs +3 -3
- package/Runtime/Core/Random/XoroShiroRandom.cs +3 -3
- package/Runtime/Tags/Attribute.cs +144 -24
- package/Runtime/Tags/AttributeEffect.cs +119 -16
- package/Runtime/Tags/AttributeMetadataCache.cs +312 -3
- package/Runtime/Tags/AttributeModification.cs +59 -29
- package/Runtime/Tags/AttributesComponent.cs +20 -0
- package/Runtime/Tags/EffectBehavior.cs +171 -0
- package/Runtime/Tags/EffectBehavior.cs.meta +4 -0
- package/Runtime/Tags/EffectHandle.cs +5 -0
- package/Runtime/Tags/EffectHandler.cs +385 -39
- package/Runtime/Tags/EffectStackKey.cs +79 -0
- package/Runtime/Tags/EffectStackKey.cs.meta +4 -0
- package/Runtime/Tags/PeriodicEffectDefinition.cs +102 -0
- package/Runtime/Tags/PeriodicEffectDefinition.cs.meta +4 -0
- package/Runtime/Tags/PeriodicEffectRuntimeState.cs +40 -0
- package/Runtime/Tags/PeriodicEffectRuntimeState.cs.meta +4 -0
- package/Samples~/DI - Zenject/README.md +0 -2
- package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs +285 -0
- package/Tests/Editor/Attributes/WShowIfPropertyDrawerTests.cs.meta +11 -0
- package/Tests/Editor/Core/Attributes/RelationalComponentAssignerTests.cs +2 -2
- package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs +192 -0
- package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs.meta +11 -0
- package/{node_modules.meta → Tests/Editor/Tags.meta} +1 -1
- package/Tests/Editor/Utils/ScriptableObjectSingletonTests.cs +41 -0
- package/Tests/Runtime/Extensions/IListExtensionTests.cs +187 -1
- package/Tests/Runtime/Helper/ObjectsTests.cs +3 -3
- package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +2 -2
- package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs +346 -0
- package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs.meta +11 -0
- package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
- package/Tests/Runtime/Random/FlurryBurstRandomTests.cs +12 -0
- package/Tests/Runtime/Random/FlurryBurstRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Random/PhotonSpinRandomTests.cs +12 -0
- package/Tests/Runtime/Random/PhotonSpinRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Random/RandomProtoSerializationTests.cs +14 -0
- package/Tests/Runtime/Random/RandomTestBase.cs +39 -4
- package/Tests/Runtime/Random/StormDropRandomTests.cs +12 -0
- package/Tests/Runtime/Random/StormDropRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Serialization/JsonSerializationTest.cs +4 -3
- package/Tests/Runtime/Serialization/ProtoInterfaceResolutionEdgeTests.cs +2 -2
- package/Tests/Runtime/Serialization/ProtoRootRegistrationTests.cs +1 -1
- package/Tests/Runtime/Serialization/ProtoSerializeBehaviorTests.cs +1 -1
- package/Tests/Runtime/Tags/AttributeEffectTests.cs +135 -0
- package/Tests/Runtime/Tags/AttributeEffectTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/AttributeModificationTests.cs +137 -0
- package/Tests/Runtime/Tags/AttributeTests.cs +192 -0
- package/Tests/Runtime/Tags/AttributeTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/EffectBehaviorTests.cs +184 -0
- package/Tests/Runtime/Tags/EffectBehaviorTests.cs.meta +3 -0
- package/Tests/Runtime/Tags/EffectHandlerTests.cs +618 -0
- package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs +89 -0
- package/Tests/Runtime/Tags/Helpers/RecordingEffectBehavior.cs.meta +4 -0
- package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs +92 -0
- package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs.meta +3 -0
- package/package.json +1 -1
- package/scripts/lint-doc-links.ps1 +156 -11
- package/Tests/Runtime/Tags/AttributeDataTests.cs +0 -312
- /package/Tests/Runtime/Tags/{AttributeDataTests.cs.meta → AttributeModificationTests.cs.meta} +0 -0
|
@@ -34,6 +34,8 @@ namespace WallstopStudios.UnityHelpers.Tests.Editor.Utils
|
|
|
34
34
|
yield return null;
|
|
35
35
|
DeleteAssetIfExists("Assets/Resources/CustomPath/CustomPathSingleton.asset");
|
|
36
36
|
yield return null;
|
|
37
|
+
DeleteFolderIfEmpty("Assets/Resources/CustomPath");
|
|
38
|
+
yield return null;
|
|
37
39
|
|
|
38
40
|
// For nested test types, Unity cannot create valid .asset files (no script file).
|
|
39
41
|
// Instead, create in-memory instances so the singleton loader can discover them via FindObjectsOfTypeAll.
|
|
@@ -81,6 +83,43 @@ namespace WallstopStudios.UnityHelpers.Tests.Editor.Utils
|
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
private static void DeleteFolderIfEmpty(string folderPath)
|
|
87
|
+
{
|
|
88
|
+
if (string.IsNullOrWhiteSpace(folderPath))
|
|
89
|
+
{
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!AssetDatabase.IsValidFolder(folderPath))
|
|
94
|
+
{
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
string[] subFolders = AssetDatabase.GetSubFolders(folderPath);
|
|
99
|
+
if (subFolders != null && subFolders.Length > 0)
|
|
100
|
+
{
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
string[] assetGuids = AssetDatabase.FindAssets(string.Empty, new[] { folderPath });
|
|
105
|
+
for (int i = 0; i < assetGuids.Length; i++)
|
|
106
|
+
{
|
|
107
|
+
string guid = assetGuids[i];
|
|
108
|
+
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
|
109
|
+
if (
|
|
110
|
+
!string.IsNullOrEmpty(assetPath)
|
|
111
|
+
&& !string.Equals(assetPath, folderPath, System.StringComparison.Ordinal)
|
|
112
|
+
)
|
|
113
|
+
{
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
AssetDatabase.DeleteAsset(folderPath);
|
|
119
|
+
AssetDatabase.SaveAssets();
|
|
120
|
+
AssetDatabase.Refresh();
|
|
121
|
+
}
|
|
122
|
+
|
|
84
123
|
private static TType CreateInMemoryInstance<TType>()
|
|
85
124
|
where TType : ScriptableObject
|
|
86
125
|
{
|
|
@@ -144,6 +183,8 @@ namespace WallstopStudios.UnityHelpers.Tests.Editor.Utils
|
|
|
144
183
|
AssetDatabase.SaveAssets();
|
|
145
184
|
AssetDatabase.Refresh();
|
|
146
185
|
yield return null;
|
|
186
|
+
DeleteFolderIfEmpty("Assets/Resources/CustomPath");
|
|
187
|
+
yield return null;
|
|
147
188
|
// Prefer public API surface over reflection to clean up the cached instance
|
|
148
189
|
if (TestSingleton.HasInstance)
|
|
149
190
|
{
|
|
@@ -17,6 +17,14 @@ namespace WallstopStudios.UnityHelpers.Tests.Extensions
|
|
|
17
17
|
public int Compare(int x, int y) => x.CompareTo(y);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
private sealed class StableTupleComparer : IComparer<ValueTuple<int, int>>
|
|
21
|
+
{
|
|
22
|
+
public int Compare(ValueTuple<int, int> x, ValueTuple<int, int> y)
|
|
23
|
+
{
|
|
24
|
+
return x.Item1.CompareTo(y.Item1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
private readonly struct IntEqualityComparer : IEqualityComparer<int>
|
|
21
29
|
{
|
|
22
30
|
public bool Equals(int x, int y) => x == y;
|
|
@@ -135,7 +143,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Extensions
|
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
[Test]
|
|
138
|
-
public void
|
|
146
|
+
public void GhostSort()
|
|
139
147
|
{
|
|
140
148
|
for (int i = 0; i < NumTries; ++i)
|
|
141
149
|
{
|
|
@@ -153,6 +161,120 @@ namespace WallstopStudios.UnityHelpers.Tests.Extensions
|
|
|
153
161
|
}
|
|
154
162
|
}
|
|
155
163
|
|
|
164
|
+
[Test]
|
|
165
|
+
public void MeteorSort()
|
|
166
|
+
{
|
|
167
|
+
for (int i = 0; i < NumTries; ++i)
|
|
168
|
+
{
|
|
169
|
+
int[] input = Enumerable
|
|
170
|
+
.Range(0, 100)
|
|
171
|
+
.Select(_ => PRNG.Instance.Next(int.MinValue, int.MaxValue))
|
|
172
|
+
.ToArray();
|
|
173
|
+
int[] conventionalSorted = input.ToArray();
|
|
174
|
+
Array.Sort(conventionalSorted);
|
|
175
|
+
|
|
176
|
+
int[] meteorSorted = input.ToArray();
|
|
177
|
+
meteorSorted.MeteorSort(new IntComparer());
|
|
178
|
+
Assert.That(conventionalSorted, Is.EqualTo(meteorSorted));
|
|
179
|
+
Assert.That(input.OrderBy(x => x), Is.EqualTo(meteorSorted));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
[Test]
|
|
184
|
+
public void PatternDefeatingQuickSort()
|
|
185
|
+
{
|
|
186
|
+
for (int i = 0; i < NumTries; ++i)
|
|
187
|
+
{
|
|
188
|
+
int[] input = Enumerable
|
|
189
|
+
.Range(0, 100)
|
|
190
|
+
.Select(_ => PRNG.Instance.Next(int.MinValue, int.MaxValue))
|
|
191
|
+
.ToArray();
|
|
192
|
+
int[] conventionalSorted = input.ToArray();
|
|
193
|
+
Array.Sort(conventionalSorted);
|
|
194
|
+
|
|
195
|
+
int[] pdqSorted = input.ToArray();
|
|
196
|
+
pdqSorted.PatternDefeatingQuickSort(new IntComparer());
|
|
197
|
+
Assert.That(conventionalSorted, Is.EqualTo(pdqSorted));
|
|
198
|
+
Assert.That(input.OrderBy(x => x), Is.EqualTo(pdqSorted));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
[Test]
|
|
203
|
+
public void GrailSort()
|
|
204
|
+
{
|
|
205
|
+
for (int i = 0; i < NumTries; ++i)
|
|
206
|
+
{
|
|
207
|
+
int[] input = Enumerable
|
|
208
|
+
.Range(0, 100)
|
|
209
|
+
.Select(_ => PRNG.Instance.Next(int.MinValue, int.MaxValue))
|
|
210
|
+
.ToArray();
|
|
211
|
+
int[] conventionalSorted = input.ToArray();
|
|
212
|
+
Array.Sort(conventionalSorted);
|
|
213
|
+
|
|
214
|
+
int[] grailSorted = input.ToArray();
|
|
215
|
+
grailSorted.GrailSort(new IntComparer());
|
|
216
|
+
Assert.That(conventionalSorted, Is.EqualTo(grailSorted));
|
|
217
|
+
Assert.That(input.OrderBy(x => x), Is.EqualTo(grailSorted));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
[Test]
|
|
222
|
+
public void PowerSort()
|
|
223
|
+
{
|
|
224
|
+
for (int i = 0; i < NumTries; ++i)
|
|
225
|
+
{
|
|
226
|
+
int[] input = Enumerable
|
|
227
|
+
.Range(0, 100)
|
|
228
|
+
.Select(_ => PRNG.Instance.Next(int.MinValue, int.MaxValue))
|
|
229
|
+
.ToArray();
|
|
230
|
+
int[] conventionalSorted = input.ToArray();
|
|
231
|
+
Array.Sort(conventionalSorted);
|
|
232
|
+
|
|
233
|
+
int[] powerSorted = input.ToArray();
|
|
234
|
+
powerSorted.PowerSort(new IntComparer());
|
|
235
|
+
Assert.That(conventionalSorted, Is.EqualTo(powerSorted));
|
|
236
|
+
Assert.That(input.OrderBy(x => x), Is.EqualTo(powerSorted));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
[Test]
|
|
241
|
+
public void GrailSortIsStable()
|
|
242
|
+
{
|
|
243
|
+
ValueTuple<int, int>[] input = Enumerable
|
|
244
|
+
.Range(0, 100)
|
|
245
|
+
.Select(i => ValueTuple.Create(i / 5, i))
|
|
246
|
+
.ToArray();
|
|
247
|
+
ValueTuple<int, int>[] grailSorted = input.ToArray();
|
|
248
|
+
grailSorted.GrailSort(new StableTupleComparer());
|
|
249
|
+
|
|
250
|
+
for (int i = 1; i < grailSorted.Length; ++i)
|
|
251
|
+
{
|
|
252
|
+
if (grailSorted[i - 1].Item1 == grailSorted[i].Item1)
|
|
253
|
+
{
|
|
254
|
+
Assert.That(grailSorted[i - 1].Item2, Is.LessThan(grailSorted[i].Item2));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
[Test]
|
|
260
|
+
public void PowerSortIsStable()
|
|
261
|
+
{
|
|
262
|
+
ValueTuple<int, int>[] input = Enumerable
|
|
263
|
+
.Range(0, 100)
|
|
264
|
+
.Select(i => ValueTuple.Create(i / 4, i))
|
|
265
|
+
.ToArray();
|
|
266
|
+
ValueTuple<int, int>[] powerSorted = input.ToArray();
|
|
267
|
+
powerSorted.PowerSort(new StableTupleComparer());
|
|
268
|
+
|
|
269
|
+
for (int i = 1; i < powerSorted.Length; ++i)
|
|
270
|
+
{
|
|
271
|
+
if (powerSorted[i - 1].Item1 == powerSorted[i].Item1)
|
|
272
|
+
{
|
|
273
|
+
Assert.That(powerSorted[i - 1].Item2, Is.LessThan(powerSorted[i].Item2));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
156
278
|
[Test]
|
|
157
279
|
public void SortDefaultAlgorithm()
|
|
158
280
|
{
|
|
@@ -731,6 +853,70 @@ namespace WallstopStudios.UnityHelpers.Tests.Extensions
|
|
|
731
853
|
Assert.That(single, Is.EqualTo(new[] { 42 }));
|
|
732
854
|
}
|
|
733
855
|
|
|
856
|
+
[Test]
|
|
857
|
+
public void PatternDefeatingQuickSortEmptyList()
|
|
858
|
+
{
|
|
859
|
+
int[] empty = Array.Empty<int>();
|
|
860
|
+
empty.PatternDefeatingQuickSort(new IntComparer());
|
|
861
|
+
Assert.That(empty, Is.Empty);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
[Test]
|
|
865
|
+
public void PatternDefeatingQuickSortSingleElement()
|
|
866
|
+
{
|
|
867
|
+
int[] single = { 42 };
|
|
868
|
+
single.PatternDefeatingQuickSort(new IntComparer());
|
|
869
|
+
Assert.That(single, Is.EqualTo(new[] { 42 }));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
[Test]
|
|
873
|
+
public void GrailSortEmptyList()
|
|
874
|
+
{
|
|
875
|
+
int[] empty = Array.Empty<int>();
|
|
876
|
+
empty.GrailSort(new IntComparer());
|
|
877
|
+
Assert.That(empty, Is.Empty);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
[Test]
|
|
881
|
+
public void GrailSortSingleElement()
|
|
882
|
+
{
|
|
883
|
+
int[] single = { 42 };
|
|
884
|
+
single.GrailSort(new IntComparer());
|
|
885
|
+
Assert.That(single, Is.EqualTo(new[] { 42 }));
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
[Test]
|
|
889
|
+
public void PowerSortEmptyList()
|
|
890
|
+
{
|
|
891
|
+
int[] empty = Array.Empty<int>();
|
|
892
|
+
empty.PowerSort(new IntComparer());
|
|
893
|
+
Assert.That(empty, Is.Empty);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
[Test]
|
|
897
|
+
public void PowerSortSingleElement()
|
|
898
|
+
{
|
|
899
|
+
int[] single = { 42 };
|
|
900
|
+
single.PowerSort(new IntComparer());
|
|
901
|
+
Assert.That(single, Is.EqualTo(new[] { 42 }));
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
[Test]
|
|
905
|
+
public void MeteorSortEmptyList()
|
|
906
|
+
{
|
|
907
|
+
int[] empty = Array.Empty<int>();
|
|
908
|
+
empty.MeteorSort(new IntComparer());
|
|
909
|
+
Assert.That(empty, Is.Empty);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
[Test]
|
|
913
|
+
public void MeteorSortSingleElement()
|
|
914
|
+
{
|
|
915
|
+
int[] single = { 42 };
|
|
916
|
+
single.MeteorSort(new IntComparer());
|
|
917
|
+
Assert.That(single, Is.EqualTo(new[] { 42 }));
|
|
918
|
+
}
|
|
919
|
+
|
|
734
920
|
[Test]
|
|
735
921
|
public void CombinedOperationsShuffleThenSort()
|
|
736
922
|
{
|
|
@@ -343,8 +343,8 @@ namespace WallstopStudios.UnityHelpers.Tests.Helper
|
|
|
343
343
|
CustomEnumerable ascending = new(1, 2, 3);
|
|
344
344
|
CustomEnumerable descending = new(3, 2, 1);
|
|
345
345
|
|
|
346
|
-
int ascHash = Objects.EnumerableHashCode
|
|
347
|
-
int descHash = Objects.EnumerableHashCode
|
|
346
|
+
int ascHash = Objects.EnumerableHashCode(ascending);
|
|
347
|
+
int descHash = Objects.EnumerableHashCode(descending);
|
|
348
348
|
|
|
349
349
|
Assert.AreNotEqual(ascHash, descHash);
|
|
350
350
|
}
|
|
@@ -354,7 +354,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Helper
|
|
|
354
354
|
{
|
|
355
355
|
DisposableEnumerable enumerable = new(1, 2, 3);
|
|
356
356
|
|
|
357
|
-
int result = Objects.EnumerableHashCode
|
|
357
|
+
int result = Objects.EnumerableHashCode(enumerable);
|
|
358
358
|
|
|
359
359
|
Assert.AreNotEqual(0, result);
|
|
360
360
|
Assert.IsTrue(enumerable.LastEnumerator.WasDisposed);
|
|
@@ -321,7 +321,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Integrations.Reflex
|
|
|
321
321
|
GameObject root = Track(new GameObject("ReflexComponentPrefab"));
|
|
322
322
|
ReflexRelationalTester tester = root.AddComponent<ReflexRelationalTester>();
|
|
323
323
|
|
|
324
|
-
GameObject child = new
|
|
324
|
+
GameObject child = new("ReflexComponentPrefabChild");
|
|
325
325
|
child.AddComponent<CapsuleCollider>();
|
|
326
326
|
child.transform.SetParent(root.transform, false);
|
|
327
327
|
|
|
@@ -334,7 +334,7 @@ namespace WallstopStudios.UnityHelpers.Tests.Integrations.Reflex
|
|
|
334
334
|
GameObject root = Track(new GameObject("ReflexGameObjectPrefab"));
|
|
335
335
|
root.AddComponent<ReflexRelationalTester>();
|
|
336
336
|
|
|
337
|
-
GameObject child = new
|
|
337
|
+
GameObject child = new("ReflexGameObjectPrefabChild");
|
|
338
338
|
child.AddComponent<CapsuleCollider>();
|
|
339
339
|
child.transform.SetParent(root.transform, false);
|
|
340
340
|
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Tests.Performance
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Diagnostics;
|
|
6
|
+
using System.Globalization;
|
|
7
|
+
using System.Text;
|
|
8
|
+
using NUnit.Framework;
|
|
9
|
+
using UnityEngine;
|
|
10
|
+
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
11
|
+
using WallstopStudios.UnityHelpers.Core.Random;
|
|
12
|
+
|
|
13
|
+
public sealed class IListSortingPerformanceTests
|
|
14
|
+
{
|
|
15
|
+
private const string DocumentPath = "Docs/ILIST_SORTING_PERFORMANCE.md";
|
|
16
|
+
private const string SectionPrefix = "ILIST_SORT_";
|
|
17
|
+
private const int NearlySortedSwapPercentage = 50;
|
|
18
|
+
|
|
19
|
+
private static readonly DatasetSizeSpec[] DatasetSizeSpecs =
|
|
20
|
+
{
|
|
21
|
+
new DatasetSizeSpec("100", 100),
|
|
22
|
+
new DatasetSizeSpec("1,000", 1_000),
|
|
23
|
+
new DatasetSizeSpec("10,000", 10_000),
|
|
24
|
+
new DatasetSizeSpec("100,000", 100_000),
|
|
25
|
+
new DatasetSizeSpec("1,000,000", 1_000_000),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
private static readonly DatasetState[] DatasetStates =
|
|
29
|
+
{
|
|
30
|
+
new DatasetState("Sorted", BuildSortedData),
|
|
31
|
+
new DatasetState("Nearly Sorted (2% swaps)", BuildNearlySortedData),
|
|
32
|
+
new DatasetState("Shuffled (deterministic)", BuildShuffledData),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
private static readonly SortImplementation[] SortImplementations =
|
|
36
|
+
{
|
|
37
|
+
new SortImplementation("Ghost", SortAlgorithm.Ghost, false, int.MaxValue),
|
|
38
|
+
new SortImplementation("Meteor", SortAlgorithm.Meteor, false, int.MaxValue),
|
|
39
|
+
new SortImplementation(
|
|
40
|
+
"Pattern-Defeating QuickSort",
|
|
41
|
+
SortAlgorithm.PatternDefeatingQuickSort,
|
|
42
|
+
false,
|
|
43
|
+
int.MaxValue
|
|
44
|
+
),
|
|
45
|
+
new SortImplementation("Grail", SortAlgorithm.Grail, true, int.MaxValue),
|
|
46
|
+
new SortImplementation("Power", SortAlgorithm.Power, true, int.MaxValue),
|
|
47
|
+
new SortImplementation("Insertion", SortAlgorithm.Insertion, true, 10_000),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
[Test]
|
|
51
|
+
[Timeout(0)]
|
|
52
|
+
public void Benchmark()
|
|
53
|
+
{
|
|
54
|
+
string operatingSystemToken = GetOperatingSystemToken();
|
|
55
|
+
string sectionName = SectionPrefix + operatingSystemToken;
|
|
56
|
+
|
|
57
|
+
List<string> readmeLines = new List<string>
|
|
58
|
+
{
|
|
59
|
+
string.Format(
|
|
60
|
+
CultureInfo.InvariantCulture,
|
|
61
|
+
"_Last updated {0:yyyy-MM-dd HH:mm} UTC on {1}_",
|
|
62
|
+
DateTime.UtcNow,
|
|
63
|
+
SystemInfo.operatingSystem
|
|
64
|
+
),
|
|
65
|
+
string.Empty,
|
|
66
|
+
"Times are single-pass measurements in milliseconds (lower is better). `n/a` indicates the algorithm was skipped for the dataset size.",
|
|
67
|
+
string.Empty,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
IComparer<int> comparer = Comparer<int>.Default;
|
|
71
|
+
string headerLine = BuildHeaderLine();
|
|
72
|
+
string dividerLine = BuildDividerLine();
|
|
73
|
+
|
|
74
|
+
foreach (DatasetState datasetState in DatasetStates)
|
|
75
|
+
{
|
|
76
|
+
UnityEngine.Debug.Log($"IList Sorting Benchmarks - {datasetState.Label}");
|
|
77
|
+
UnityEngine.Debug.Log(headerLine);
|
|
78
|
+
UnityEngine.Debug.Log(dividerLine);
|
|
79
|
+
|
|
80
|
+
readmeLines.Add($"### {datasetState.Label}");
|
|
81
|
+
readmeLines.Add(headerLine);
|
|
82
|
+
readmeLines.Add(dividerLine);
|
|
83
|
+
|
|
84
|
+
foreach (DatasetSizeSpec sizeSpec in DatasetSizeSpecs)
|
|
85
|
+
{
|
|
86
|
+
int[] baseData = datasetState.CreateData(sizeSpec.Count);
|
|
87
|
+
string rowLine = BuildRowLine(sizeSpec.Label, baseData, comparer);
|
|
88
|
+
UnityEngine.Debug.Log(rowLine);
|
|
89
|
+
readmeLines.Add(rowLine);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
readmeLines.Add(string.Empty);
|
|
93
|
+
UnityEngine.Debug.Log(string.Empty);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
BenchmarkReadmeUpdater.UpdateSection(sectionName, readmeLines, DocumentPath);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private static string BuildHeaderLine()
|
|
100
|
+
{
|
|
101
|
+
StringBuilder headerBuilder = new StringBuilder();
|
|
102
|
+
headerBuilder.Append("| List Size |");
|
|
103
|
+
foreach (SortImplementation implementation in SortImplementations)
|
|
104
|
+
{
|
|
105
|
+
headerBuilder.Append(' ');
|
|
106
|
+
headerBuilder.Append(implementation.Label);
|
|
107
|
+
headerBuilder.Append(" |");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return headerBuilder.ToString();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static string BuildDividerLine()
|
|
114
|
+
{
|
|
115
|
+
StringBuilder dividerBuilder = new StringBuilder();
|
|
116
|
+
dividerBuilder.Append("| --- |");
|
|
117
|
+
foreach (SortImplementation implementation in SortImplementations)
|
|
118
|
+
{
|
|
119
|
+
dividerBuilder.Append(" --- |");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return dividerBuilder.ToString();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private static string BuildRowLine(
|
|
126
|
+
string sizeLabel,
|
|
127
|
+
int[] baseData,
|
|
128
|
+
IComparer<int> comparer
|
|
129
|
+
)
|
|
130
|
+
{
|
|
131
|
+
StringBuilder rowBuilder = new StringBuilder();
|
|
132
|
+
rowBuilder.Append("| ");
|
|
133
|
+
rowBuilder.Append(sizeLabel);
|
|
134
|
+
rowBuilder.Append(" |");
|
|
135
|
+
|
|
136
|
+
foreach (SortImplementation implementation in SortImplementations)
|
|
137
|
+
{
|
|
138
|
+
string result = BenchmarkImplementation(implementation, baseData, comparer);
|
|
139
|
+
rowBuilder.Append(' ');
|
|
140
|
+
rowBuilder.Append(result);
|
|
141
|
+
rowBuilder.Append(" |");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return rowBuilder.ToString();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private static string BenchmarkImplementation(
|
|
148
|
+
SortImplementation implementation,
|
|
149
|
+
int[] baseData,
|
|
150
|
+
IComparer<int> comparer
|
|
151
|
+
)
|
|
152
|
+
{
|
|
153
|
+
if (baseData.Length > implementation.MaxSupportedCount)
|
|
154
|
+
{
|
|
155
|
+
return "n/a";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
int[] workingData = new int[baseData.Length];
|
|
159
|
+
Array.Copy(baseData, workingData, baseData.Length);
|
|
160
|
+
|
|
161
|
+
IList<int> workingList = workingData;
|
|
162
|
+
Stopwatch stopwatch = Stopwatch.StartNew();
|
|
163
|
+
|
|
164
|
+
try
|
|
165
|
+
{
|
|
166
|
+
implementation.Execute(workingList, comparer);
|
|
167
|
+
}
|
|
168
|
+
catch (Exception exception)
|
|
169
|
+
{
|
|
170
|
+
UnityEngine.Debug.LogError(
|
|
171
|
+
$"Sorting benchmark failed for {implementation.Label}: {exception.Message}"
|
|
172
|
+
);
|
|
173
|
+
return "error";
|
|
174
|
+
}
|
|
175
|
+
finally
|
|
176
|
+
{
|
|
177
|
+
stopwatch.Stop();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
double milliseconds = stopwatch.Elapsed.TotalMilliseconds;
|
|
181
|
+
return FormatDuration(milliseconds);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private static string FormatDuration(double milliseconds)
|
|
185
|
+
{
|
|
186
|
+
if (milliseconds >= 1000d)
|
|
187
|
+
{
|
|
188
|
+
double seconds = milliseconds / 1000d;
|
|
189
|
+
return string.Format(CultureInfo.InvariantCulture, "{0:0.00} s", seconds);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (milliseconds >= 100d)
|
|
193
|
+
{
|
|
194
|
+
return string.Format(CultureInfo.InvariantCulture, "{0:0} ms", milliseconds);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (milliseconds >= 10d)
|
|
198
|
+
{
|
|
199
|
+
return string.Format(CultureInfo.InvariantCulture, "{0:0.0} ms", milliseconds);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (milliseconds >= 1d)
|
|
203
|
+
{
|
|
204
|
+
return string.Format(CultureInfo.InvariantCulture, "{0:0.00} ms", milliseconds);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return string.Format(CultureInfo.InvariantCulture, "{0:0.000} ms", milliseconds);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private static int[] BuildSortedData(int count)
|
|
211
|
+
{
|
|
212
|
+
int[] data = new int[count];
|
|
213
|
+
for (int i = 0; i < count; ++i)
|
|
214
|
+
{
|
|
215
|
+
data[i] = i;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return data;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private static int[] BuildNearlySortedData(int count)
|
|
222
|
+
{
|
|
223
|
+
int[] data = BuildSortedData(count);
|
|
224
|
+
if (count <= 1)
|
|
225
|
+
{
|
|
226
|
+
return data;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
int swaps = Math.Max(1, count / NearlySortedSwapPercentage);
|
|
230
|
+
IRandom random = new PcgRandom(unchecked(count * 37));
|
|
231
|
+
|
|
232
|
+
for (int i = 0; i < swaps; ++i)
|
|
233
|
+
{
|
|
234
|
+
int index = random.Next(0, count - 1);
|
|
235
|
+
int neighbor = Math.Min(count - 1, index + 1);
|
|
236
|
+
int temp = data[index];
|
|
237
|
+
data[index] = data[neighbor];
|
|
238
|
+
data[neighbor] = temp;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return data;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private static int[] BuildShuffledData(int count)
|
|
245
|
+
{
|
|
246
|
+
int[] data = BuildSortedData(count);
|
|
247
|
+
if (count <= 1)
|
|
248
|
+
{
|
|
249
|
+
return data;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
IRandom random = new PcgRandom(unchecked(count * 7919));
|
|
253
|
+
|
|
254
|
+
for (int i = count - 1; i > 0; --i)
|
|
255
|
+
{
|
|
256
|
+
int swapIndex = random.Next(0, i + 1);
|
|
257
|
+
int temp = data[i];
|
|
258
|
+
data[i] = data[swapIndex];
|
|
259
|
+
data[swapIndex] = temp;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return data;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private static string GetOperatingSystemToken()
|
|
266
|
+
{
|
|
267
|
+
RuntimePlatform platform = Application.platform;
|
|
268
|
+
switch (platform)
|
|
269
|
+
{
|
|
270
|
+
case RuntimePlatform.WindowsEditor:
|
|
271
|
+
case RuntimePlatform.WindowsPlayer:
|
|
272
|
+
case RuntimePlatform.WindowsServer:
|
|
273
|
+
return "WINDOWS";
|
|
274
|
+
case RuntimePlatform.OSXEditor:
|
|
275
|
+
case RuntimePlatform.OSXPlayer:
|
|
276
|
+
return "MACOS";
|
|
277
|
+
case RuntimePlatform.LinuxEditor:
|
|
278
|
+
case RuntimePlatform.LinuxPlayer:
|
|
279
|
+
case RuntimePlatform.LinuxServer:
|
|
280
|
+
return "LINUX";
|
|
281
|
+
default:
|
|
282
|
+
return "OTHER";
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private readonly struct DatasetSizeSpec
|
|
287
|
+
{
|
|
288
|
+
public DatasetSizeSpec(string label, int count)
|
|
289
|
+
{
|
|
290
|
+
Label = label;
|
|
291
|
+
Count = count;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public string Label { get; }
|
|
295
|
+
|
|
296
|
+
public int Count { get; }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private readonly struct DatasetState
|
|
300
|
+
{
|
|
301
|
+
private readonly Func<int, int[]> generator;
|
|
302
|
+
|
|
303
|
+
public DatasetState(string label, Func<int, int[]> generator)
|
|
304
|
+
{
|
|
305
|
+
Label = label;
|
|
306
|
+
this.generator = generator;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
public string Label { get; }
|
|
310
|
+
|
|
311
|
+
public int[] CreateData(int count)
|
|
312
|
+
{
|
|
313
|
+
return generator(count);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private readonly struct SortImplementation
|
|
318
|
+
{
|
|
319
|
+
public SortImplementation(
|
|
320
|
+
string label,
|
|
321
|
+
SortAlgorithm algorithm,
|
|
322
|
+
bool isStable,
|
|
323
|
+
int maxSupportedCount
|
|
324
|
+
)
|
|
325
|
+
{
|
|
326
|
+
Label = label;
|
|
327
|
+
Algorithm = algorithm;
|
|
328
|
+
IsStable = isStable;
|
|
329
|
+
MaxSupportedCount = maxSupportedCount;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
public string Label { get; }
|
|
333
|
+
|
|
334
|
+
public SortAlgorithm Algorithm { get; }
|
|
335
|
+
|
|
336
|
+
public bool IsStable { get; }
|
|
337
|
+
|
|
338
|
+
public int MaxSupportedCount { get; }
|
|
339
|
+
|
|
340
|
+
public void Execute(IList<int> list, IComparer<int> comparer)
|
|
341
|
+
{
|
|
342
|
+
list.Sort(comparer, Algorithm);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -31,12 +31,15 @@ namespace WallstopStudios.UnityHelpers.Tests.Performance
|
|
|
31
31
|
LogRow(RunTest(new PcgRandom(), timeout));
|
|
32
32
|
LogRow(RunTest(new RomuDuo(), timeout));
|
|
33
33
|
LogRow(RunTest(new SplitMix64(), timeout));
|
|
34
|
+
LogRow(RunTest(new FlurryBurstRandom(), timeout));
|
|
34
35
|
LogRow(RunTest(new SquirrelRandom(), timeout));
|
|
35
36
|
LogRow(RunTest(new SystemRandom(), timeout));
|
|
36
37
|
LogRow(RunTest(new UnityRandom(), timeout));
|
|
37
38
|
LogRow(RunTest(new WyRandom(), timeout));
|
|
38
39
|
LogRow(RunTest(new XorShiftRandom(), timeout));
|
|
39
40
|
LogRow(RunTest(new XoroShiroRandom(), timeout));
|
|
41
|
+
LogRow(RunTest(new PhotonSpinRandom(), timeout));
|
|
42
|
+
LogRow(RunTest(new StormDropRandom(), timeout));
|
|
40
43
|
|
|
41
44
|
BenchmarkReadmeUpdater.UpdateSection(
|
|
42
45
|
"RANDOM_BENCHMARKS",
|