com.wallstop-studios.unity-helpers 2.0.0-rc06 → 2.0.0-rc08

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 (28) hide show
  1. package/Runtime/Core/Extension/DirectionExtensions.cs +5 -2
  2. package/Runtime/Core/Extension/IEnumerableExtensions.cs +19 -6
  3. package/Runtime/Core/Extension/RandomExtensions.cs +9 -87
  4. package/Runtime/Core/Extension/UnityExtensions.cs +2 -2
  5. package/Runtime/Core/Helper/Helpers.cs +1 -556
  6. package/Runtime/Core/Helper/Partials/LogHelpers.cs +13 -0
  7. package/Runtime/Core/Helper/Partials/LogHelpers.cs.meta +3 -0
  8. package/Runtime/Core/Helper/Partials/MathHelpers.cs +30 -0
  9. package/Runtime/Core/Helper/Partials/MathHelpers.cs.meta +3 -0
  10. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +388 -0
  11. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs.meta +3 -0
  12. package/Runtime/Core/Helper/Partials/TransformHelpers.cs +167 -0
  13. package/Runtime/Core/Helper/Partials/TransformHelpers.cs.meta +3 -0
  14. package/Runtime/Core/Helper/Partials.meta +3 -0
  15. package/Runtime/Core/Random/AbstractRandom.cs +140 -154
  16. package/Runtime/Core/Random/IRandom.cs +26 -7
  17. package/Runtime/Core/Random/PerlinNoise.cs +369 -0
  18. package/Runtime/Core/Random/PerlinNoise.cs.meta +3 -0
  19. package/Runtime/Core/Random/SquirrelRandom.cs +9 -10
  20. package/Runtime/Core/Random/SystemRandom.cs +78 -41
  21. package/Tests/Runtime/Extensions/RandomExtensionTests.cs +27 -0
  22. package/Tests/Runtime/Extensions/RandomExtensionTests.cs.meta +3 -0
  23. package/Tests/Runtime/Helper/ObjectHelperTests.cs +402 -0
  24. package/Tests/Runtime/Helper/ObjectHelperTests.cs.meta +3 -0
  25. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +58 -3
  26. package/Tests/Runtime/Random/RandomTestBase.cs +557 -6
  27. package/Tests/Runtime/Random/SquirrelRandomTests.cs +5 -0
  28. package/package.json +1 -1
@@ -5,7 +5,6 @@
5
5
  using System.Collections.Generic;
6
6
  using System.Linq;
7
7
  using System.Runtime.Serialization;
8
- using System.Text.Json.Serialization;
9
8
  using DataStructure.Adapters;
10
9
  using UnityEngine;
11
10
 
@@ -16,24 +15,16 @@
16
15
  private static readonly ConcurrentDictionary<Type, Array> EnumTypeCache = new();
17
16
 
18
17
  protected const uint HalfwayUint = uint.MaxValue / 2;
19
- protected const double MagicDouble = 4.6566128752458E-10;
20
18
  protected const float MagicFloat = 5.960465E-008F;
21
19
 
22
20
  protected double? _cachedGaussian;
23
21
 
24
- protected AbstractRandom() { }
25
-
26
22
  public abstract RandomState InternalState { get; }
27
23
 
28
- public int Next()
24
+ public virtual int Next()
29
25
  {
30
- int result;
31
- do
32
- {
33
- result = unchecked((int)NextUint());
34
- } while (result < 0);
35
-
36
- return result;
26
+ // Mask out the MSB to ensure the value is within [0, int.MaxValue]
27
+ return unchecked((int)NextUint() & 0x7FFFFFFF);
37
28
  }
38
29
 
39
30
  public int Next(int max)
@@ -55,8 +46,13 @@
55
46
  );
56
47
  }
57
48
 
58
- uint range = unchecked((uint)(max - min));
59
- return unchecked((int)NextUint(range)) + min;
49
+ uint range = (uint)(max - min);
50
+ if (range == 0)
51
+ {
52
+ return unchecked((int)NextUint());
53
+ }
54
+
55
+ return unchecked((int)(min + NextUint(range)));
60
56
  }
61
57
 
62
58
  // Internal sampler
@@ -64,21 +60,12 @@
64
60
 
65
61
  public uint NextUint(uint max)
66
62
  {
67
- /*
68
- https://github.com/libevent/libevent/blob/3807a30b03ab42f2f503f2db62b1ef5876e2be80/arc4random.c#L531
69
-
70
- http://cs.stackexchange.com/questions/570/generating-uniformly-distributed-random-numbers-using-a-coin
71
- Generates a uniform random number within the bound, avoiding modulo bias
72
- */
73
- uint threshold = unchecked((uint)((0x100000000UL - max) % max));
74
- while (true)
63
+ if (max == 0)
75
64
  {
76
- uint randomValue = NextUint();
77
- if (threshold <= randomValue)
78
- {
79
- return randomValue % max;
80
- }
65
+ throw new ArgumentException("Max cannot be zero");
81
66
  }
67
+
68
+ return (uint)(NextDouble() * max);
82
69
  }
83
70
 
84
71
  public uint NextUint(uint min, uint max)
@@ -127,12 +114,10 @@
127
114
  {
128
115
  uint upper = NextUint();
129
116
  uint lower = NextUint();
130
- // Mix things up a little
131
- if (NextBool())
117
+ unchecked
132
118
  {
133
- return unchecked((long)((ulong)upper << 32) | lower);
119
+ return (long)((((ulong)upper << 32) | lower) & 0x7FFFFFFFFFFFFFFF);
134
120
  }
135
- return unchecked((long)((ulong)lower << 32) | upper);
136
121
  }
137
122
 
138
123
  public long NextLong(long max)
@@ -142,17 +127,7 @@
142
127
  throw new ArgumentException($"Max {max} cannot be less-than or equal-to 0");
143
128
  }
144
129
 
145
- if (max < int.MaxValue)
146
- {
147
- return Next(unchecked((int)max));
148
- }
149
-
150
- long withinRange;
151
- do
152
- {
153
- withinRange = NextLong();
154
- } while (withinRange < 0 || max <= withinRange);
155
- return withinRange;
130
+ return (long)(NextDouble() * max);
156
131
  }
157
132
 
158
133
  public long NextLong(long min, long max)
@@ -164,17 +139,25 @@
164
139
  );
165
140
  }
166
141
 
167
- return min + NextLong(max - min);
142
+ ulong range = (ulong)(max - min);
143
+ if (range == 0)
144
+ {
145
+ return unchecked((long)NextUlong());
146
+ }
147
+
148
+ return unchecked((long)(NextDouble() * range + min));
168
149
  }
169
150
 
170
151
  public ulong NextUlong()
171
152
  {
172
- return unchecked((ulong)NextLong());
153
+ uint upper = NextUint();
154
+ uint lower = NextUint();
155
+ return ((ulong)upper << 32) | lower;
173
156
  }
174
157
 
175
158
  public ulong NextUlong(ulong max)
176
159
  {
177
- return unchecked((ulong)NextLong(unchecked((long)max)));
160
+ return (ulong)(NextDouble() * max);
178
161
  }
179
162
 
180
163
  public ulong NextUlong(ulong min, ulong max)
@@ -186,44 +169,46 @@
186
169
  );
187
170
  }
188
171
 
189
- return unchecked((ulong)NextLong(unchecked((long)min), unchecked((long)max)));
172
+ return NextUlong(max - min) + min;
190
173
  }
191
174
 
192
- public bool NextBool()
175
+ public virtual bool NextBool()
193
176
  {
194
177
  return NextUint() < HalfwayUint;
195
178
  }
196
179
 
197
180
  public void NextBytes(byte[] buffer)
198
181
  {
199
- if (ReferenceEquals(buffer, null))
182
+ if (buffer == null)
200
183
  {
201
184
  throw new ArgumentException(nameof(buffer));
202
185
  }
203
186
 
204
- const byte sizeOfInt = 4; // May differ on some platforms
187
+ const int sizeOfInt = 4; // May differ on some platforms
205
188
 
206
189
  // See how many ints we can slap into it.
207
190
  int chunks = buffer.Length / sizeOfInt;
208
- byte spare = unchecked((byte)(buffer.Length - (chunks * sizeOfInt)));
191
+ int spare = buffer.Length - chunks * sizeOfInt;
209
192
  for (int i = 0; i < chunks; ++i)
210
193
  {
211
- int offset = i * chunks;
212
- int random = Next();
213
- buffer[offset] = unchecked((byte)(random & 0xFF000000));
214
- buffer[offset + 1] = unchecked((byte)(random & 0x00FF0000));
215
- buffer[offset + 2] = unchecked((byte)(random & 0x0000FF00));
216
- buffer[offset + 3] = unchecked((byte)(random & 0x000000FF));
194
+ int offset = i * sizeOfInt;
195
+ uint random = NextUint();
196
+ for (int j = 0; j < sizeOfInt; ++j)
197
+ {
198
+ buffer[offset + j] = unchecked(
199
+ (byte)((random >> (j * sizeOfInt)) & 0x000000FF)
200
+ );
201
+ }
217
202
  }
218
203
 
204
+ if (0 < spare)
219
205
  {
220
- /*
221
- This could be implemented more optimally by generating a single int and
222
- bit shifting along the position, but that is too much for me right now.
223
- */
224
- for (byte i = 0; i < spare; ++i)
206
+ uint spareRandom = NextUint();
207
+ for (int i = 0; i < spare; ++i)
225
208
  {
226
- buffer[buffer.Length - 1 - i] = unchecked((byte)Next());
209
+ buffer[buffer.Length - 1 - i] = unchecked(
210
+ (byte)((spareRandom >> (i * sizeOfInt)) & 0x000000FF)
211
+ );
227
212
  }
228
213
  }
229
214
  }
@@ -233,8 +218,8 @@
233
218
  double value;
234
219
  do
235
220
  {
236
- value = NextUint() * MagicDouble;
237
- } while (value < 0 || 1 <= value);
221
+ value = NextUint() * (1.0 / uint.MaxValue);
222
+ } while (1.0 <= value);
238
223
 
239
224
  return value;
240
225
  }
@@ -259,7 +244,51 @@
259
244
  }
260
245
 
261
246
  double range = max - min;
262
- return min + NextDouble(range);
247
+ if (double.IsInfinity(range))
248
+ {
249
+ return NextDoubleWithInfiniteRange(min, max);
250
+ }
251
+
252
+ return min + NextDouble() * range;
253
+ }
254
+
255
+ protected double NextDoubleWithInfiniteRange(double min, double max)
256
+ {
257
+ double random;
258
+ do
259
+ {
260
+ random = NextDoubleFullRange();
261
+ } while (random < min || max <= random);
262
+
263
+ return random;
264
+ }
265
+
266
+ protected double NextDoubleFullRange()
267
+ {
268
+ double value = double.NaN;
269
+ do
270
+ {
271
+ ulong randomBits = NextUlong();
272
+
273
+ // Extract exponent (bits 52-62)
274
+ const ulong exponentMask = 0x7FF0000000000000;
275
+
276
+ ulong exponent = (randomBits & exponentMask) >> 52;
277
+
278
+ // Ensure exponent is not all 1's to avoid Inf and NaN
279
+ if (exponent == 0x7FF)
280
+ {
281
+ continue; // Regenerate
282
+ }
283
+
284
+ /*
285
+ For uniform distribution over all finite doubles, no further masking is necessary,
286
+ reassemble the bits
287
+ */
288
+ value = BitConverter.Int64BitsToDouble(unchecked((long)randomBits));
289
+ } while (double.IsInfinity(value) || double.IsNaN(value));
290
+
291
+ return value;
263
292
  }
264
293
 
265
294
  public double NextGaussian(double mean = 0, double stdDev = 1)
@@ -285,7 +314,7 @@
285
314
  x = 2 * NextDouble() - 1;
286
315
  y = 2 * NextDouble() - 1;
287
316
  square = x * x + y * y;
288
- } while (square > 1 || square == 0);
317
+ } while (square is 0 or > 1);
289
318
 
290
319
  double fac = Math.Sqrt(-2 * Math.Log(square) / square);
291
320
  _cachedGaussian = x * fac;
@@ -297,9 +326,8 @@
297
326
  float value;
298
327
  do
299
328
  {
300
- uint floatAsInt = NextUint();
301
- value = (floatAsInt >> 8) * MagicFloat;
302
- } while (value < 0 || 1 <= value);
329
+ value = NextUint() / (1f * uint.MaxValue);
330
+ } while (1f <= value);
303
331
 
304
332
  return value;
305
333
  }
@@ -323,52 +351,45 @@
323
351
  );
324
352
  }
325
353
 
326
- return min + NextFloat(max - min);
327
- }
328
-
329
- public T Next<T>(IEnumerable<T> enumerable)
330
- {
331
- if (enumerable is ICollection<T> collection)
354
+ float range = max - min;
355
+ if (float.IsInfinity(range))
332
356
  {
333
- return Next(collection);
357
+ return (float)NextDouble(min, max);
334
358
  }
335
359
 
336
- return Next((IReadOnlyList<T>)enumerable.ToList());
360
+ return min + NextFloat(range);
337
361
  }
338
362
 
339
- public T Next<T>(ICollection<T> collection)
363
+ public T NextOf<T>(IEnumerable<T> enumerable)
340
364
  {
341
- int count = collection.Count;
342
- if (count <= 0)
365
+ return enumerable switch
343
366
  {
344
- throw new ArgumentException("Collection size cannot be less-than or equal-to 0");
345
- }
367
+ IReadOnlyList<T> list => NextOf(list),
368
+ IReadOnlyCollection<T> collection => NextOf(collection),
369
+ null => throw new ArgumentNullException(nameof(enumerable)),
370
+ _ => NextOf(enumerable.ToArray()),
371
+ };
372
+ }
346
373
 
347
- switch (collection)
374
+ public T NextOf<T>(IReadOnlyCollection<T> collection)
375
+ {
376
+ if (collection is not { Count: > 0 })
348
377
  {
349
- case IList<T> list:
350
- return Next(list);
351
- case IReadOnlyList<T> readOnlyList:
352
- return Next(readOnlyList);
378
+ throw new ArgumentException("Collection cannot be empty");
353
379
  }
354
380
 
355
- int index = Next(count);
356
- int i = 0;
357
- foreach (T element in collection)
381
+ if (collection is IReadOnlyList<T> list)
358
382
  {
359
- if (i++ == index)
360
- {
361
- return element;
362
- }
383
+ return NextOf(list);
363
384
  }
364
385
 
365
- // Should never happen
366
- return default;
386
+ int index = Next(collection.Count);
387
+ return collection.ElementAt(index);
367
388
  }
368
389
 
369
- public T Next<T>(IList<T> list)
390
+ public T NextOf<T>(IReadOnlyList<T> list)
370
391
  {
371
- if (ReferenceEquals(list, null))
392
+ if (list is not { Count: > 0 })
372
393
  {
373
394
  throw new ArgumentNullException(nameof(list));
374
395
  }
@@ -377,52 +398,15 @@
377
398
  For small lists, it's much more efficient to simply return one of their elements
378
399
  instead of trying to generate a random number within bounds (which is implemented as a while(true) loop)
379
400
  */
380
- switch (list.Count)
381
- {
382
- case 1:
383
- return list[0];
384
- case 2:
385
- return NextBool() ? list[0] : list[1];
386
- default:
387
- return list[Next(list.Count)];
388
- }
389
- }
390
-
391
- private T Next<T>(IReadOnlyList<T> list)
392
- {
393
- /*
394
- For small lists, it's much more efficient to simply return one of their elements
395
- instead of trying to generate a random number within bounds (which is implemented as a while(true) loop)
396
- */
397
- switch (list.Count)
398
- {
399
- case 1:
400
- return list[0];
401
- case 2:
402
- return NextBool() ? list[0] : list[1];
403
- default:
404
- return list[Next(list.Count)];
405
- }
406
- }
407
-
408
- public T Next<T>()
409
- where T : struct, Enum
410
- {
411
- Type enumType = typeof(T);
412
- T[] enumValues;
413
- if (EnumTypeCache.TryGetValue(enumType, out Array enumArray))
414
- {
415
- enumValues = (T[])enumArray;
416
- }
417
- else
401
+ return list.Count switch
418
402
  {
419
- enumValues = (T[])Enum.GetValues(enumType);
420
- }
421
-
422
- return RandomOf(enumValues);
403
+ 1 => list[0],
404
+ 2 => NextBool() ? list[0] : list[1],
405
+ _ => list[Next(list.Count)],
406
+ };
423
407
  }
424
408
 
425
- public T NextCachedEnum<T>()
409
+ public T NextEnum<T>()
426
410
  where T : struct, Enum
427
411
  {
428
412
  Type enumType = typeof(T);
@@ -447,7 +431,13 @@
447
431
 
448
432
  // Advances the RNG
449
433
  // https://code2d.wordpress.com/2020/07/21/perlin-noise/
450
- public float[,] NextNoiseMap(int width, int height, float scale, int octaves)
434
+ public float[,] NextNoiseMap(
435
+ int width,
436
+ int height,
437
+ PerlinNoise noise = null,
438
+ float scale = 2.5f,
439
+ int octaves = 8
440
+ )
451
441
  {
452
442
  if (width <= 0)
453
443
  {
@@ -469,6 +459,7 @@
469
459
  throw new ArgumentException(nameof(octaves));
470
460
  }
471
461
 
462
+ noise ??= PerlinNoise.Instance;
472
463
  float[,] noiseMap = new float[width, height];
473
464
 
474
465
  Vector2[] octaveOffsets = new Vector2[octaves];
@@ -497,8 +488,7 @@
497
488
  float sampleX = (x - halfWidth) / scale * frequency + octaveOffsets[i].x;
498
489
  float sampleY = (y - halfHeight) / scale * frequency + octaveOffsets[i].y;
499
490
 
500
- // Use unity's implementation of perlin noise
501
- float perlinValue = Mathf.PerlinNoise(sampleX, sampleY) * 2 - 1;
491
+ float perlinValue = noise.Noise(sampleX, sampleY) * 2 - 1;
502
492
  noiseHeight += perlinValue * amplitude;
503
493
  }
504
494
 
@@ -533,17 +523,13 @@
533
523
 
534
524
  protected T RandomOf<T>(T[] values)
535
525
  {
536
- switch (values.Length)
526
+ return values.Length switch
537
527
  {
538
- case 0:
539
- return default;
540
- case 1:
541
- return values[0];
542
- case 2:
543
- return NextBool() ? values[0] : values[1];
544
- default:
545
- return values[Next(values.Length)];
546
- }
528
+ 0 => default,
529
+ 1 => values[0],
530
+ 2 => NextBool() ? values[0] : values[1],
531
+ _ => values[Next(values.Length)],
532
+ };
547
533
  }
548
534
 
549
535
  public abstract IRandom Copy();
@@ -27,10 +27,12 @@
27
27
  /// </summary>
28
28
  /// <returns>A number within the range [0, uint.MaxValue].</returns>
29
29
  uint NextUint();
30
+
30
31
  /// <summary>
31
32
  /// </summary>
32
33
  /// <returns>A number within the range [0, max).</returns>
33
34
  uint NextUint(uint max);
35
+
34
36
  /// <summary>
35
37
  /// </summary>
36
38
  /// <returns>A number within the range [min, max).</returns>
@@ -45,6 +47,7 @@
45
47
  /// </summary>
46
48
  /// <returns>A number within the range [0, max).</returns>
47
49
  short NextShort(short max);
50
+
48
51
  /// <summary>
49
52
  /// </summary>
50
53
  /// <returns>A number within the range [min, max).</returns>
@@ -54,10 +57,12 @@
54
57
  /// </summary>
55
58
  /// <returns>A number within the range [0, byte.MaxValue).</returns>
56
59
  byte NextByte();
60
+
57
61
  /// <summary>
58
62
  /// </summary>
59
63
  /// <returns>A number within the range [0, max).</returns>
60
64
  byte NextByte(byte max);
65
+
61
66
  /// <summary>
62
67
  /// </summary>
63
68
  /// <returns>A number within the range [min, max).</returns>
@@ -67,10 +72,12 @@
67
72
  /// </summary>
68
73
  /// <returns>A number within the range [0, long.MaxValue).</returns>
69
74
  long NextLong();
75
+
70
76
  /// <summary>
71
77
  /// </summary>
72
78
  /// <returns>A number within the range [0, max).</returns>
73
79
  long NextLong(long max);
80
+
74
81
  /// <summary>
75
82
  /// </summary>
76
83
  /// <returns>A number within the range [min, max).</returns>
@@ -80,10 +87,12 @@
80
87
  /// </summary>
81
88
  /// <returns>A number within the range [0, ulong.MaxValue).</returns>
82
89
  ulong NextUlong();
90
+
83
91
  /// <summary>
84
92
  /// </summary>
85
93
  /// <returns>A number within the range [0, max).</returns>
86
94
  ulong NextUlong(ulong max);
95
+
87
96
  /// <summary>
88
97
  /// </summary>
89
98
  /// <returns>A number within the range [min, max).</returns>
@@ -100,10 +109,12 @@
100
109
  /// </summary>
101
110
  /// <returns>A number within the range [0, 1).</returns>
102
111
  float NextFloat();
112
+
103
113
  /// <summary>
104
114
  /// </summary>
105
115
  /// <returns>A number within the range [0, max).</returns>
106
116
  float NextFloat(float max);
117
+
107
118
  /// <summary>
108
119
  /// </summary>
109
120
  /// <returns>A number within the range min, max).</returns>
@@ -113,10 +124,12 @@
113
124
  /// </summary>
114
125
  /// <returns>A number within the range [0, 1).</returns>
115
126
  double NextDouble();
127
+
116
128
  /// <summary>
117
129
  /// </summary>
118
130
  /// <returns>A number within the range [0, max).</returns>
119
131
  double NextDouble(double max);
132
+
120
133
  /// <summary>
121
134
  /// </summary>
122
135
  /// <returns>A number within the range min, max).</returns>
@@ -127,15 +140,21 @@
127
140
  Guid NextGuid();
128
141
  KGuid NextKGuid();
129
142
 
130
- T Next<T>(IEnumerable<T> enumerable);
131
- T Next<T>(ICollection<T> collection);
132
- T Next<T>(IList<T> list);
143
+ T NextOf<T>(IEnumerable<T> enumerable);
144
+ T NextOf<T>(IReadOnlyCollection<T> collection);
145
+ T NextOf<T>(IReadOnlyList<T> list);
133
146
 
134
- T Next<T>() where T : struct, Enum;
135
- T NextCachedEnum<T>() where T : struct, Enum;
147
+ T NextEnum<T>()
148
+ where T : struct, Enum;
136
149
 
137
- float[,] NextNoiseMap(int width, int height, float scale = 2.5f, int octaves = 8);
150
+ float[,] NextNoiseMap(
151
+ int width,
152
+ int height,
153
+ PerlinNoise noise = null,
154
+ float scale = 2.5f,
155
+ int octaves = 8
156
+ );
138
157
 
139
158
  IRandom Copy();
140
159
  }
141
- }
160
+ }