com.wallstop-studios.unity-helpers 2.1.0 → 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.
Files changed (52) hide show
  1. package/Docs/ILIST_SORTING_PERFORMANCE.md +92 -0
  2. package/Docs/ILIST_SORTING_PERFORMANCE.md.meta +7 -0
  3. package/Docs/INDEX.md +10 -1
  4. package/Docs/Images/random_generators.svg +7 -7
  5. package/Docs/RANDOM_PERFORMANCE.md +17 -14
  6. package/Docs/SPATIAL_TREE_2D_PERFORMANCE.md +64 -64
  7. package/Docs/SPATIAL_TREE_3D_PERFORMANCE.md +64 -64
  8. package/Editor/Core/Helper/AnimationEventHelpers.cs +1 -1
  9. package/README.md +25 -15
  10. package/Runtime/Core/Extension/IListExtensions.cs +720 -12
  11. package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +2 -3
  12. package/Runtime/Core/Random/AbstractRandom.cs +52 -5
  13. package/Runtime/Core/Random/DotNetRandom.cs +3 -3
  14. package/Runtime/Core/Random/FlurryBurstRandom.cs +285 -0
  15. package/Runtime/Core/Random/FlurryBurstRandom.cs.meta +3 -0
  16. package/Runtime/Core/Random/IllusionFlow.cs +3 -3
  17. package/Runtime/Core/Random/LinearCongruentialGenerator.cs +3 -3
  18. package/Runtime/Core/Random/PcgRandom.cs +6 -6
  19. package/Runtime/Core/Random/PhotonSpinRandom.cs +387 -0
  20. package/Runtime/Core/Random/PhotonSpinRandom.cs.meta +3 -0
  21. package/Runtime/Core/Random/RomuDuo.cs +3 -3
  22. package/Runtime/Core/Random/SplitMix64.cs +3 -3
  23. package/Runtime/Core/Random/SquirrelRandom.cs +6 -4
  24. package/Runtime/Core/Random/StormDropRandom.cs +271 -0
  25. package/Runtime/Core/Random/StormDropRandom.cs.meta +3 -0
  26. package/Runtime/Core/Random/UnityRandom.cs +3 -3
  27. package/Runtime/Core/Random/WyRandom.cs +6 -4
  28. package/Runtime/Core/Random/XorShiftRandom.cs +3 -3
  29. package/Runtime/Core/Random/XoroShiroRandom.cs +3 -3
  30. package/Runtime/Tags/AttributeMetadataCache.cs +312 -3
  31. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs +192 -0
  32. package/Tests/Editor/Tags/AttributeMetadataCacheTests.cs.meta +11 -0
  33. package/Tests/Editor/Tags.meta +8 -0
  34. package/Tests/Runtime/Extensions/IListExtensionTests.cs +187 -1
  35. package/Tests/Runtime/Helper/ObjectsTests.cs +3 -3
  36. package/Tests/Runtime/Integrations/Reflex/RelationalComponentsReflexTests.cs +2 -2
  37. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs +346 -0
  38. package/Tests/Runtime/Performance/IListSortingPerformanceTests.cs.meta +11 -0
  39. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
  40. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs +12 -0
  41. package/Tests/Runtime/Random/FlurryBurstRandomTests.cs.meta +3 -0
  42. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs +12 -0
  43. package/Tests/Runtime/Random/PhotonSpinRandomTests.cs.meta +3 -0
  44. package/Tests/Runtime/Random/RandomProtoSerializationTests.cs +14 -0
  45. package/Tests/Runtime/Random/RandomTestBase.cs +39 -4
  46. package/Tests/Runtime/Random/StormDropRandomTests.cs +12 -0
  47. package/Tests/Runtime/Random/StormDropRandomTests.cs.meta +3 -0
  48. package/Tests/Runtime/Serialization/ProtoInterfaceResolutionEdgeTests.cs +2 -2
  49. package/Tests/Runtime/Serialization/ProtoRootRegistrationTests.cs +1 -1
  50. package/Tests/Runtime/Serialization/ProtoSerializeBehaviorTests.cs +1 -1
  51. package/Tests/Runtime/Tags/PeriodicEffectDefinitionSerializationTests.cs +2 -2
  52. package/package.json +1 -1
@@ -105,8 +105,7 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
105
105
  /// </summary>
106
106
  public System.Threading.Tasks.Task RunAsync(Action action)
107
107
  {
108
- TaskCompletionSource<bool> tcs =
109
- new System.Threading.Tasks.TaskCompletionSource<bool>();
108
+ TaskCompletionSource<bool> tcs = new();
110
109
  RunOnMainThread(() =>
111
110
  {
112
111
  try
@@ -127,7 +126,7 @@ namespace WallstopStudios.UnityHelpers.Core.Helper
127
126
  /// </summary>
128
127
  public System.Threading.Tasks.Task<T> Post<T>(Func<T> func)
129
128
  {
130
- TaskCompletionSource<T> tcs = new System.Threading.Tasks.TaskCompletionSource<T>();
129
+ TaskCompletionSource<T> tcs = new();
131
130
  RunOnMainThread(() =>
132
131
  {
133
132
  try
@@ -15,9 +15,6 @@ namespace WallstopStudios.UnityHelpers.Core.Random
15
15
  using Extension;
16
16
  #endif
17
17
 
18
- [Serializable]
19
- [DataContract]
20
- [ProtoContract]
21
18
  /// <summary>
22
19
  /// Common abstract base for all <see cref="IRandom"/> implementations with protobuf support.
23
20
  /// </summary>
@@ -58,6 +55,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
58
55
  /// then use Serializer.ProtoDeserialize&lt;IRandom&gt;.</description></item>
59
56
  /// </list>
60
57
  /// </remarks>
58
+ [Serializable]
59
+ [DataContract]
60
+ [ProtoContract]
61
61
  [ProtoInclude(100, typeof(DotNetRandom))]
62
62
  [ProtoInclude(101, typeof(PcgRandom))]
63
63
  [ProtoInclude(102, typeof(XorShiftRandom))]
@@ -70,6 +70,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
70
70
  [ProtoInclude(109, typeof(RomuDuo))]
71
71
  [ProtoInclude(110, typeof(SplitMix64))]
72
72
  [ProtoInclude(111, typeof(IllusionFlow))]
73
+ [ProtoInclude(112, typeof(FlurryBurstRandom))]
74
+ [ProtoInclude(113, typeof(PhotonSpinRandom))]
75
+ [ProtoInclude(114, typeof(StormDropRandom))]
73
76
  public abstract class AbstractRandom : IRandom
74
77
  {
75
78
  #if SINGLE_THREADED
@@ -107,13 +110,37 @@ namespace WallstopStudios.UnityHelpers.Core.Random
107
110
  protected int _byteCount;
108
111
 
109
112
  [MethodImpl(MethodImplOptions.AggressiveInlining)]
110
- protected RandomState BuildState(ulong state1, ulong state2 = 0, byte[] payload = null)
113
+ protected RandomState BuildState(
114
+ ulong state1,
115
+ ulong state2 = 0,
116
+ IReadOnlyList<byte> payload = null
117
+ )
111
118
  {
119
+ byte[] payloadCopy = null;
120
+ if (payload != null)
121
+ {
122
+ if (payload is byte[] payloadArray)
123
+ {
124
+ int length = payloadArray.Length;
125
+ payloadCopy = new byte[length];
126
+ Buffer.BlockCopy(payloadArray, 0, payloadCopy, 0, length);
127
+ }
128
+ else
129
+ {
130
+ int count = payload.Count;
131
+ payloadCopy = new byte[count];
132
+ for (int i = 0; i < count; ++i)
133
+ {
134
+ payloadCopy[i] = payload[i];
135
+ }
136
+ }
137
+ }
138
+
112
139
  return new RandomState(
113
140
  state1,
114
141
  state2,
115
142
  _cachedGaussian,
116
- payload: payload,
143
+ payload: payloadCopy,
117
144
  bitBuffer: _bitBuffer,
118
145
  bitCount: _bitCount,
119
146
  byteBuffer: _byteBuffer,
@@ -1314,5 +1341,25 @@ namespace WallstopStudios.UnityHelpers.Core.Random
1314
1341
  }
1315
1342
 
1316
1343
  public abstract IRandom Copy();
1344
+
1345
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
1346
+ protected internal static uint RotateLeft(uint value, int count)
1347
+ {
1348
+ return (value << count) | (value >> (32 - count));
1349
+ }
1350
+
1351
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
1352
+ protected internal static uint Mix32(ref ulong state)
1353
+ {
1354
+ unchecked
1355
+ {
1356
+ state += 0x9E3779B97F4A7C15UL;
1357
+ ulong z = state;
1358
+ z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL;
1359
+ z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL;
1360
+ z ^= z >> 31;
1361
+ return (uint)z;
1362
+ }
1363
+ }
1317
1364
  }
1318
1365
  }
@@ -5,9 +5,6 @@ namespace WallstopStudios.UnityHelpers.Core.Random
5
5
  using System.Text.Json.Serialization;
6
6
  using ProtoBuf;
7
7
 
8
- [Serializable]
9
- [DataContract]
10
- [ProtoContract(SkipConstructor = true)]
11
8
  /// <summary>
12
9
  /// A thin wrapper around <c>System.Random</c> that exposes the <see cref="IRandom"/> API and supports state capture.
13
10
  /// </summary>
@@ -46,6 +43,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
46
43
  /// float f = compat.NextFloat();
47
44
  /// </code>
48
45
  /// </example>
46
+ [Serializable]
47
+ [DataContract]
48
+ [ProtoContract(SkipConstructor = true)]
49
49
  public sealed class DotNetRandom : AbstractRandom
50
50
  {
51
51
  public static DotNetRandom Instance => ThreadLocalRandom<DotNetRandom>.Instance;
@@ -0,0 +1,285 @@
1
+ namespace WallstopStudios.UnityHelpers.Core.Random
2
+ {
3
+ using System;
4
+ using System.Buffers.Binary;
5
+ using System.Runtime.CompilerServices;
6
+ using System.Runtime.Serialization;
7
+ using System.Text.Json.Serialization;
8
+ using Extension;
9
+ using Helper;
10
+ using ProtoBuf;
11
+ using WallstopStudios.UnityHelpers.Utils;
12
+
13
+ /// <summary>
14
+ /// FlurryBurst32: a six-word ARX-style generator offering high quality and excellent parallel sequencing.
15
+ /// </summary>
16
+ /// <remarks>
17
+ /// <para>
18
+ /// https://github.com/wileylooper/flurryburst
19
+ /// Based on <c>wileylooper/flurryburst</c>, this implementation captures the 32-bit variant that balances
20
+ /// speed, period (~2<sup>128</sup>) and state size for gameplay workloads. It is suitable as a drop-in
21
+ /// alternative to Xoshiro128** and similar families, while retaining deterministic serialization support.
22
+ /// </para>
23
+ /// <para>Pros:</para>
24
+ /// <list type="bullet">
25
+ /// <item><description>Small state (6×32-bit) with excellent statistical behaviour.</description></item>
26
+ /// <item><description>Deterministic snapshots via <see cref="RandomState"/> and protobuf/JSON.</description></item>
27
+ /// <item><description>Easy to create parallel streams by varying the <c>d</c> word.</description></item>
28
+ /// </list>
29
+ /// <para>Cons:</para>
30
+ /// <list type="bullet">
31
+ /// <item><description>Not cryptographically secure.</description></item>
32
+ /// <item><description>Requires a short warm-up (performed automatically) to avoid transient bias.</description></item>
33
+ /// </list>
34
+ /// <para>When to use:</para>
35
+ /// <list type="bullet">
36
+ /// <item><description>Deterministic gameplay, procedural content, Monte-Carlo style sampling.</description></item>
37
+ /// </list>
38
+ /// <para>When not to use:</para>
39
+ /// <list type="bullet">
40
+ /// <item><description>Security/adversarial contexts.</description></item>
41
+ /// </list>
42
+ /// </remarks>
43
+ /// <example>
44
+ /// <code>
45
+ /// using WallstopStudios.UnityHelpers.Core.Random;
46
+ ///
47
+ /// IRandom rng = new FlurryBurstRandom(seed: 123u);
48
+ /// int value = rng.Next(0, 100);
49
+ /// float weight = rng.NextFloat();
50
+ /// </code>
51
+ /// </example>
52
+ [Serializable]
53
+ [DataContract]
54
+ [ProtoContract]
55
+ public sealed class FlurryBurstRandom
56
+ : AbstractRandom,
57
+ IEquatable<FlurryBurstRandom>,
58
+ IComparable,
59
+ IComparable<FlurryBurstRandom>
60
+ {
61
+ private const uint Increment = 1_111_111_111U;
62
+ private const int PayloadByteCount = sizeof(uint) * 2;
63
+
64
+ public static FlurryBurstRandom Instance => ThreadLocalRandom<FlurryBurstRandom>.Instance;
65
+
66
+ public override RandomState InternalState
67
+ {
68
+ get
69
+ {
70
+ ulong state1 = ((ulong)_a << 32) | _b;
71
+ ulong state2 = ((ulong)_c << 32) | _d;
72
+ using PooledResource<byte[]> payloadLease = WallstopArrayPool<byte>.Get(
73
+ PayloadByteCount,
74
+ out byte[] buffer
75
+ );
76
+ Span<byte> payload = buffer.AsSpan(0, PayloadByteCount);
77
+ BinaryPrimitives.WriteUInt32LittleEndian(payload.Slice(0, sizeof(uint)), _e);
78
+ BinaryPrimitives.WriteUInt32LittleEndian(
79
+ payload.Slice(sizeof(uint), sizeof(uint)),
80
+ _f
81
+ );
82
+
83
+ return BuildState(
84
+ state1,
85
+ state2,
86
+ new ArraySegment<byte>(buffer, 0, PayloadByteCount)
87
+ );
88
+ }
89
+ }
90
+
91
+ [ProtoMember(6)]
92
+ private uint _a;
93
+
94
+ [ProtoMember(7)]
95
+ private uint _b;
96
+
97
+ [ProtoMember(8)]
98
+ private uint _c;
99
+
100
+ [ProtoMember(9)]
101
+ private uint _d;
102
+
103
+ [ProtoMember(10)]
104
+ private uint _e;
105
+
106
+ [ProtoMember(11)]
107
+ private uint _f;
108
+
109
+ public FlurryBurstRandom()
110
+ : this(Guid.NewGuid()) { }
111
+
112
+ public FlurryBurstRandom(Guid guid)
113
+ {
114
+ InitializeFromGuid(guid);
115
+ }
116
+
117
+ [JsonConstructor]
118
+ public FlurryBurstRandom(RandomState internalState)
119
+ {
120
+ ulong state1 = internalState.State1;
121
+ ulong state2 = internalState.State2;
122
+ _a = (uint)(state1 >> 32);
123
+ _b = (uint)state1;
124
+ _c = (uint)(state2 >> 32);
125
+ _d = (uint)state2;
126
+
127
+ byte[] payload = internalState._payload;
128
+ if (payload != null && payload.Length >= sizeof(uint) * 2)
129
+ {
130
+ _e = BinaryPrimitives.ReadUInt32LittleEndian(payload.AsSpan(0, sizeof(uint)));
131
+ _f = BinaryPrimitives.ReadUInt32LittleEndian(
132
+ payload.AsSpan(sizeof(uint), sizeof(uint))
133
+ );
134
+ }
135
+ else
136
+ {
137
+ _e = 0;
138
+ _f = 0;
139
+ }
140
+
141
+ RestoreCommonState(internalState);
142
+ }
143
+
144
+ public override uint NextUint()
145
+ {
146
+ unchecked
147
+ {
148
+ uint mix = RotateLeft(_a, 13);
149
+
150
+ _a = _b;
151
+ _b = _e;
152
+ _c += _d;
153
+ _d += _b;
154
+ _e = _f + Increment;
155
+ _f = _c ^ mix;
156
+
157
+ return (_e >> 1) ^ _f;
158
+ }
159
+ }
160
+
161
+ public override IRandom Copy()
162
+ {
163
+ return new FlurryBurstRandom(InternalState);
164
+ }
165
+
166
+ public override bool Equals(object obj)
167
+ {
168
+ return Equals(obj as FlurryBurstRandom);
169
+ }
170
+
171
+ public bool Equals(FlurryBurstRandom other)
172
+ {
173
+ if (other == null)
174
+ {
175
+ return false;
176
+ }
177
+
178
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
179
+ return _a == other._a
180
+ && _b == other._b
181
+ && _c == other._c
182
+ && _d == other._d
183
+ && _e == other._e
184
+ && _f == other._f
185
+ && _cachedGaussian == other._cachedGaussian;
186
+ }
187
+
188
+ public override int GetHashCode()
189
+ {
190
+ return Objects.HashCode(_a, _b, _c, _d, _e, _f);
191
+ }
192
+
193
+ public override string ToString()
194
+ {
195
+ return this.ToJson();
196
+ }
197
+
198
+ public int CompareTo(object obj)
199
+ {
200
+ return CompareTo(obj as FlurryBurstRandom);
201
+ }
202
+
203
+ public int CompareTo(FlurryBurstRandom other)
204
+ {
205
+ if (other == null)
206
+ {
207
+ return -1;
208
+ }
209
+
210
+ int comparison = _a.CompareTo(other._a);
211
+ if (comparison != 0)
212
+ {
213
+ return comparison;
214
+ }
215
+
216
+ comparison = _b.CompareTo(other._b);
217
+ if (comparison != 0)
218
+ {
219
+ return comparison;
220
+ }
221
+
222
+ comparison = _c.CompareTo(other._c);
223
+ if (comparison != 0)
224
+ {
225
+ return comparison;
226
+ }
227
+
228
+ comparison = _d.CompareTo(other._d);
229
+ if (comparison != 0)
230
+ {
231
+ return comparison;
232
+ }
233
+
234
+ comparison = _e.CompareTo(other._e);
235
+ if (comparison != 0)
236
+ {
237
+ return comparison;
238
+ }
239
+
240
+ return _f.CompareTo(other._f);
241
+ }
242
+
243
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
244
+ private static uint RotateLeft(uint value, int count)
245
+ {
246
+ return (value << count) | (value >> (32 - count));
247
+ }
248
+
249
+ private void InitializeFromGuid(Guid guid)
250
+ {
251
+ (ulong seed0, ulong seed1) = RandomUtilities.GuidToUInt64Pair(guid);
252
+ InitializeFromUlongs(seed0, seed1);
253
+ }
254
+
255
+ private void InitializeFromUlongs(ulong seed0, ulong seed1)
256
+ {
257
+ ulong mixer = seed0 ^ (seed1 << 1) ^ 0x9E3779B97F4A7C15UL;
258
+
259
+ _a = Mix(ref mixer);
260
+ _b = Mix(ref mixer);
261
+ _c = Mix(ref mixer);
262
+ _d = Mix(ref mixer);
263
+ if (_d == 0)
264
+ {
265
+ _d = 1;
266
+ }
267
+ _e = Mix(ref mixer);
268
+ _f = Mix(ref mixer);
269
+ }
270
+
271
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
272
+ private static uint Mix(ref ulong state)
273
+ {
274
+ unchecked
275
+ {
276
+ state += 0x9E3779B97F4A7C15UL;
277
+ ulong z = state;
278
+ z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL;
279
+ z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL;
280
+ z ^= z >> 31;
281
+ return (uint)z;
282
+ }
283
+ }
284
+ }
285
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 83db2e5bc6be4c03966c71bbe032a519
3
+ timeCreated: 1761435962
@@ -13,9 +13,6 @@ namespace WallstopStudios.UnityHelpers.Core.Random
13
13
  using System.Text.Json.Serialization;
14
14
  using ProtoBuf;
15
15
 
16
- [Serializable]
17
- [DataContract]
18
- [ProtoContract]
19
16
  /// <summary>
20
17
  /// IllusionFlow: a modern, high-performance PRNG building on Xoroshiro concepts with additional state and mixing.
21
18
  /// </summary>
@@ -60,6 +57,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
60
57
  /// var replay = new IllusionFlow(state);
61
58
  /// </code>
62
59
  /// </example>
60
+ [Serializable]
61
+ [DataContract]
62
+ [ProtoContract]
63
63
  public sealed class IllusionFlow : AbstractRandom
64
64
  {
65
65
  private const int UintByteCount = sizeof(uint) * 8;
@@ -5,9 +5,6 @@ namespace WallstopStudios.UnityHelpers.Core.Random
5
5
  using System.Text.Json.Serialization;
6
6
  using ProtoBuf;
7
7
 
8
- [Serializable]
9
- [DataContract]
10
- [ProtoContract]
11
8
  /// <summary>
12
9
  /// A simple Linear Congruential Generator (LCG): extremely fast with low-quality randomness.
13
10
  /// </summary>
@@ -43,6 +40,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
43
40
  /// // Prefer PCG or IllusionFlow for production gameplay.
44
41
  /// </code>
45
42
  /// </example>
43
+ [Serializable]
44
+ [DataContract]
45
+ [ProtoContract]
46
46
  public sealed class LinearCongruentialGenerator : AbstractRandom
47
47
  {
48
48
  public static LinearCongruentialGenerator Instance =>
@@ -5,17 +5,14 @@ namespace WallstopStudios.UnityHelpers.Core.Random
5
5
  using System.Text.Json.Serialization;
6
6
  using ProtoBuf;
7
7
 
8
- /// <summary>
9
- /// Implementation based off of the reference PCG Random, found here: https://www.pcg-random.org/index.html
10
- /// </summary>
11
- [Serializable]
12
- [DataContract]
13
- [ProtoContract]
14
8
  /// <summary>
15
9
  /// A high-quality, small-state pseudo-random number generator based on the PCG family.
16
10
  /// </summary>
17
11
  /// <remarks>
18
12
  /// <para>
13
+ /// Implementation based off of the reference PCG Random, found here: https://www.pcg-random.org/index.html
14
+ /// </para>
15
+ /// <para>
19
16
  /// PCG (Permuted Congruential Generator) offers excellent statistical quality with very small state
20
17
  /// and extremely fast generation. This implementation uses a 64-bit state with 32-bit outputs and
21
18
  /// an increment (stream selector) to avoid overlapping sequences when constructing multiple instances.
@@ -96,6 +93,9 @@ namespace WallstopStudios.UnityHelpers.Core.Random
96
93
  /// // var index = rng.NextWeightedIndex(new float[] { 0.1f, 0.3f, 0.6f });
97
94
  /// </code>
98
95
  /// </example>
96
+ [Serializable]
97
+ [DataContract]
98
+ [ProtoContract]
99
99
  public sealed class PcgRandom
100
100
  : AbstractRandom,
101
101
  IEquatable<PcgRandom>,