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

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.
@@ -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,30 +46,122 @@
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
- // Internal sampler
63
- public abstract uint NextUint();
58
+ public T Next<T>(IEnumerable<T> enumerable)
59
+ {
60
+ if (enumerable is ICollection<T> collection)
61
+ {
62
+ return Next(collection);
63
+ }
64
64
 
65
- public uint NextUint(uint max)
65
+ return Next((IReadOnlyList<T>)enumerable.ToList());
66
+ }
67
+
68
+ public T Next<T>(ICollection<T> collection)
66
69
  {
67
- /*
68
- https://github.com/libevent/libevent/blob/3807a30b03ab42f2f503f2db62b1ef5876e2be80/arc4random.c#L531
70
+ int count = collection.Count;
71
+ if (count <= 0)
72
+ {
73
+ throw new ArgumentException("Collection size cannot be less-than or equal-to 0");
74
+ }
69
75
 
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)
76
+ switch (collection)
75
77
  {
76
- uint randomValue = NextUint();
77
- if (threshold <= randomValue)
78
+ case IList<T> list:
79
+ return Next(list);
80
+ case IReadOnlyList<T> readOnlyList:
81
+ return Next(readOnlyList);
82
+ }
83
+
84
+ int index = Next(count);
85
+ int i = 0;
86
+ foreach (T element in collection)
87
+ {
88
+ if (i++ == index)
78
89
  {
79
- return randomValue % max;
90
+ return element;
80
91
  }
81
92
  }
93
+
94
+ // Should never happen
95
+ return default;
96
+ }
97
+
98
+ public T Next<T>(IList<T> list)
99
+ {
100
+ if (ReferenceEquals(list, null))
101
+ {
102
+ throw new ArgumentNullException(nameof(list));
103
+ }
104
+
105
+ /*
106
+ For small lists, it's much more efficient to simply return one of their elements
107
+ instead of trying to generate a random number within bounds (which is implemented as a while(true) loop)
108
+ */
109
+ switch (list.Count)
110
+ {
111
+ case 1:
112
+ return list[0];
113
+ case 2:
114
+ return NextBool() ? list[0] : list[1];
115
+ default:
116
+ return list[Next(list.Count)];
117
+ }
118
+ }
119
+
120
+ private T Next<T>(IReadOnlyList<T> list)
121
+ {
122
+ /*
123
+ For small lists, it's much more efficient to simply return one of their elements
124
+ instead of trying to generate a random number within bounds (which is implemented as a while(true) loop)
125
+ */
126
+ switch (list.Count)
127
+ {
128
+ case 1:
129
+ return list[0];
130
+ case 2:
131
+ return NextBool() ? list[0] : list[1];
132
+ default:
133
+ return list[Next(list.Count)];
134
+ }
135
+ }
136
+
137
+ public T Next<T>()
138
+ where T : struct, Enum
139
+ {
140
+ Type enumType = typeof(T);
141
+ T[] enumValues;
142
+ if (EnumTypeCache.TryGetValue(enumType, out Array enumArray))
143
+ {
144
+ enumValues = (T[])enumArray;
145
+ }
146
+ else
147
+ {
148
+ enumValues = (T[])Enum.GetValues(enumType);
149
+ }
150
+
151
+ return RandomOf(enumValues);
152
+ }
153
+
154
+ // Internal sampler
155
+ public abstract uint NextUint();
156
+
157
+ public uint NextUint(uint max)
158
+ {
159
+ if (max == 0)
160
+ {
161
+ throw new ArgumentException("Max cannot be zero");
162
+ }
163
+
164
+ return (uint)(NextDouble() * max);
82
165
  }
83
166
 
84
167
  public uint NextUint(uint min, uint max)
@@ -127,12 +210,10 @@
127
210
  {
128
211
  uint upper = NextUint();
129
212
  uint lower = NextUint();
130
- // Mix things up a little
131
- if (NextBool())
213
+ unchecked
132
214
  {
133
- return unchecked((long)((ulong)upper << 32) | lower);
215
+ return (long)((((ulong)upper << 32) | lower) & (0x1UL << 63));
134
216
  }
135
- return unchecked((long)((ulong)lower << 32) | upper);
136
217
  }
137
218
 
138
219
  public long NextLong(long max)
@@ -142,17 +223,7 @@
142
223
  throw new ArgumentException($"Max {max} cannot be less-than or equal-to 0");
143
224
  }
144
225
 
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;
226
+ return (long)(NextDouble() * max);
156
227
  }
157
228
 
158
229
  public long NextLong(long min, long max)
@@ -164,17 +235,25 @@
164
235
  );
165
236
  }
166
237
 
167
- return min + NextLong(max - min);
238
+ ulong range = (ulong)(max - min);
239
+ if (range == 0)
240
+ {
241
+ return unchecked((long)NextUlong());
242
+ }
243
+
244
+ return unchecked((long)(NextDouble() * range + min));
168
245
  }
169
246
 
170
247
  public ulong NextUlong()
171
248
  {
172
- return unchecked((ulong)NextLong());
249
+ uint upper = NextUint();
250
+ uint lower = NextUint();
251
+ return ((ulong)upper << 32) | lower;
173
252
  }
174
253
 
175
254
  public ulong NextUlong(ulong max)
176
255
  {
177
- return unchecked((ulong)NextLong(unchecked((long)max)));
256
+ return (ulong)(NextDouble() * max);
178
257
  }
179
258
 
180
259
  public ulong NextUlong(ulong min, ulong max)
@@ -186,10 +265,10 @@
186
265
  );
187
266
  }
188
267
 
189
- return unchecked((ulong)NextLong(unchecked((long)min), unchecked((long)max)));
268
+ return NextUlong(max - min) + min;
190
269
  }
191
270
 
192
- public bool NextBool()
271
+ public virtual bool NextBool()
193
272
  {
194
273
  return NextUint() < HalfwayUint;
195
274
  }
@@ -201,7 +280,7 @@
201
280
  throw new ArgumentException(nameof(buffer));
202
281
  }
203
282
 
204
- const byte sizeOfInt = 4; // May differ on some platforms
283
+ const byte sizeOfInt = sizeof(int); // May differ on some platforms
205
284
 
206
285
  // See how many ints we can slap into it.
207
286
  int chunks = buffer.Length / sizeOfInt;
@@ -233,8 +312,8 @@
233
312
  double value;
234
313
  do
235
314
  {
236
- value = NextUint() * MagicDouble;
237
- } while (value < 0 || 1 <= value);
315
+ value = NextUint() * (1.0 / uint.MaxValue);
316
+ } while (1.0 <= value);
238
317
 
239
318
  return value;
240
319
  }
@@ -259,7 +338,51 @@
259
338
  }
260
339
 
261
340
  double range = max - min;
262
- return min + NextDouble(range);
341
+ if (double.IsInfinity(range))
342
+ {
343
+ return NextDoubleWithInfiniteRange(min, max);
344
+ }
345
+
346
+ return min + (NextDouble() * range);
347
+ }
348
+
349
+ protected double NextDoubleWithInfiniteRange(double min, double max)
350
+ {
351
+ double random;
352
+ do
353
+ {
354
+ random = NextDoubleFullRange();
355
+ } while (random < min || max <= random);
356
+
357
+ return random;
358
+ }
359
+
360
+ protected double NextDoubleFullRange()
361
+ {
362
+ double value = double.NaN;
363
+ do
364
+ {
365
+ ulong randomBits = NextUlong();
366
+
367
+ // Extract exponent (bits 52-62)
368
+ const ulong exponentMask = 0x7FF0000000000000;
369
+
370
+ ulong exponent = (randomBits & exponentMask) >> 52;
371
+
372
+ // Ensure exponent is not all 1's to avoid Inf and NaN
373
+ if (exponent == 0x7FF)
374
+ {
375
+ continue; // Regenerate
376
+ }
377
+
378
+ /*
379
+ For uniform distribution over all finite doubles, no further masking is necessary,
380
+ reassemble the bits
381
+ */
382
+ value = BitConverter.Int64BitsToDouble(unchecked((long)randomBits));
383
+ } while (double.IsInfinity(value) || double.IsNaN(value));
384
+
385
+ return value;
263
386
  }
264
387
 
265
388
  public double NextGaussian(double mean = 0, double stdDev = 1)
@@ -285,7 +408,7 @@
285
408
  x = 2 * NextDouble() - 1;
286
409
  y = 2 * NextDouble() - 1;
287
410
  square = x * x + y * y;
288
- } while (square > 1 || square == 0);
411
+ } while (square == 0 || 1 < square);
289
412
 
290
413
  double fac = Math.Sqrt(-2 * Math.Log(square) / square);
291
414
  _cachedGaussian = x * fac;
@@ -297,9 +420,8 @@
297
420
  float value;
298
421
  do
299
422
  {
300
- uint floatAsInt = NextUint();
301
- value = (floatAsInt >> 8) * MagicFloat;
302
- } while (value < 0 || 1 <= value);
423
+ value = NextUint() / (1f * uint.MaxValue);
424
+ } while (1f <= value);
303
425
 
304
426
  return value;
305
427
  }
@@ -323,103 +445,13 @@
323
445
  );
324
446
  }
325
447
 
326
- return min + NextFloat(max - min);
327
- }
328
-
329
- public T Next<T>(IEnumerable<T> enumerable)
330
- {
331
- if (enumerable is ICollection<T> collection)
448
+ float range = max - min;
449
+ if (float.IsInfinity(range))
332
450
  {
333
- return Next(collection);
334
- }
335
-
336
- return Next((IReadOnlyList<T>)enumerable.ToList());
337
- }
338
-
339
- public T Next<T>(ICollection<T> collection)
340
- {
341
- int count = collection.Count;
342
- if (count <= 0)
343
- {
344
- throw new ArgumentException("Collection size cannot be less-than or equal-to 0");
345
- }
346
-
347
- switch (collection)
348
- {
349
- case IList<T> list:
350
- return Next(list);
351
- case IReadOnlyList<T> readOnlyList:
352
- return Next(readOnlyList);
353
- }
354
-
355
- int index = Next(count);
356
- int i = 0;
357
- foreach (T element in collection)
358
- {
359
- if (i++ == index)
360
- {
361
- return element;
362
- }
451
+ return (float)NextDouble(min, max);
363
452
  }
364
453
 
365
- // Should never happen
366
- return default;
367
- }
368
-
369
- public T Next<T>(IList<T> list)
370
- {
371
- if (ReferenceEquals(list, null))
372
- {
373
- throw new ArgumentNullException(nameof(list));
374
- }
375
-
376
- /*
377
- For small lists, it's much more efficient to simply return one of their elements
378
- instead of trying to generate a random number within bounds (which is implemented as a while(true) loop)
379
- */
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
418
- {
419
- enumValues = (T[])Enum.GetValues(enumType);
420
- }
421
-
422
- return RandomOf(enumValues);
454
+ return min + NextFloat(range);
423
455
  }
424
456
 
425
457
  public T NextCachedEnum<T>()
@@ -447,7 +479,7 @@
447
479
 
448
480
  // Advances the RNG
449
481
  // https://code2d.wordpress.com/2020/07/21/perlin-noise/
450
- public float[,] NextNoiseMap(int width, int height, float scale, int octaves)
482
+ public float[,] NextNoiseMap(int width, int height, float scale = 2.5f, int octaves = 8)
451
483
  {
452
484
  if (width <= 0)
453
485
  {
@@ -37,7 +37,7 @@
37
37
 
38
38
  public override uint NextUint()
39
39
  {
40
- return _position = NextUintInternal(_position);
40
+ return NextUintInternal(ref _position);
41
41
  }
42
42
 
43
43
  // Does not advance the RNG
@@ -51,16 +51,15 @@
51
51
  return new SquirrelRandom(InternalState);
52
52
  }
53
53
 
54
- private static uint NextUintInternal(uint seed)
54
+ private static uint NextUintInternal(ref uint seed)
55
55
  {
56
- uint result = seed;
57
- result *= BitNoise1;
58
- result ^= (result >> 8);
59
- result += BitNoise2;
60
- result ^= (result << 8);
61
- result *= BitNoise3;
62
- result ^= (result >> 8);
63
- return result;
56
+ seed *= BitNoise1;
57
+ seed ^= (seed >> 8);
58
+ seed += BitNoise2;
59
+ seed ^= (seed << 8);
60
+ seed *= BitNoise3;
61
+ seed ^= (seed >> 8);
62
+ return seed;
64
63
  }
65
64
 
66
65
  // https://youtu.be/LWFzPP8ZbdU?t=2906
@@ -12,14 +12,18 @@
12
12
  [DataContract]
13
13
  public sealed class SystemRandom : AbstractRandom
14
14
  {
15
+ private const int HalfwayInt = int.MaxValue / 2;
16
+ private const int SeedArraySize = 56;
17
+ private const int LastSeedIndex = SeedArraySize - 1;
18
+
15
19
  public static IRandom Instance => ThreadLocalRandom<SystemRandom>.Instance;
16
20
 
17
21
  public override RandomState InternalState =>
18
22
  new(
19
- unchecked((ulong)inext),
20
- unchecked((ulong)inextp),
23
+ unchecked((ulong)_inext),
24
+ unchecked((ulong)_inextp),
21
25
  _cachedGaussian,
22
- ArrayConverter.IntArrayToByteArrayBlockCopy(SeedArray)
26
+ ArrayConverter.IntArrayToByteArrayBlockCopy(_seedArray)
23
27
  );
24
28
 
25
29
  /*
@@ -27,9 +31,9 @@
27
31
  same across platforms, a fact which defeats the purpose of these serializable
28
32
  randoms.
29
33
  */
30
- private int inext;
31
- private int inextp;
32
- private readonly int[] SeedArray = new int[56];
34
+ private int _inext;
35
+ private int _inextp;
36
+ private readonly int[] _seedArray = new int[SeedArraySize];
33
37
 
34
38
  public SystemRandom()
35
39
  : this(Guid.NewGuid().GetHashCode()) { }
@@ -37,28 +41,34 @@
37
41
  public SystemRandom(int seed)
38
42
  {
39
43
  int num1 = 161803398 - (seed == int.MinValue ? int.MaxValue : Math.Abs(seed));
40
- this.SeedArray[55] = num1;
44
+ _seedArray[LastSeedIndex] = num1;
41
45
  int num2 = 1;
42
- for (int index1 = 1; index1 < 55; ++index1)
46
+ for (int index1 = 1; index1 < LastSeedIndex; ++index1)
43
47
  {
44
- int index2 = 21 * index1 % 55;
45
- this.SeedArray[index2] = num2;
48
+ int index2 = 21 * index1 % LastSeedIndex;
49
+ _seedArray[index2] = num2;
46
50
  num2 = num1 - num2;
47
51
  if (num2 < 0)
52
+ {
48
53
  num2 += int.MaxValue;
49
- num1 = this.SeedArray[index2];
54
+ }
55
+
56
+ num1 = _seedArray[index2];
50
57
  }
51
58
  for (int index3 = 1; index3 < 5; ++index3)
52
59
  {
53
- for (int index4 = 1; index4 < 56; ++index4)
60
+ for (int index4 = 1; index4 < SeedArraySize; ++index4)
54
61
  {
55
- this.SeedArray[index4] -= this.SeedArray[1 + (index4 + 30) % 55];
56
- if (this.SeedArray[index4] < 0)
57
- this.SeedArray[index4] += int.MaxValue;
62
+ int value = _seedArray[index4] -= _seedArray[1 + (index4 + 30) % LastSeedIndex];
63
+ if (value < 0)
64
+ {
65
+ _seedArray[index4] += int.MaxValue;
66
+ }
58
67
  }
59
68
  }
60
- this.inext = 0;
61
- this.inextp = 21;
69
+
70
+ _inext = 0;
71
+ _inextp = 21;
62
72
  }
63
73
 
64
74
  [JsonConstructor]
@@ -66,59 +76,86 @@
66
76
  {
67
77
  unchecked
68
78
  {
69
- inext = (int)internalState.State1;
70
- inextp = (int)internalState.State2;
79
+ _inext = (int)internalState.State1;
80
+ _inextp = (int)internalState.State2;
71
81
  }
72
82
  _cachedGaussian = internalState.Gaussian;
73
- SeedArray = ArrayConverter.ByteArrayToIntArrayBlockCopy(internalState.Payload);
83
+ _seedArray = ArrayConverter.ByteArrayToIntArrayBlockCopy(internalState.Payload);
74
84
  }
75
85
 
76
- public override uint NextUint()
86
+ public override int Next()
77
87
  {
78
- int inext = this.inext;
79
- int inextp = this.inextp;
88
+ int localINext = _inext;
89
+ int localINextP = _inextp;
80
90
  int index1;
81
- if ((index1 = inext + 1) >= 56)
91
+ if ((index1 = localINext + 1) >= SeedArraySize)
92
+ {
82
93
  index1 = 1;
94
+ }
95
+
83
96
  int index2;
84
- if ((index2 = inextp + 1) >= 56)
97
+ if ((index2 = localINextP + 1) >= SeedArraySize)
98
+ {
85
99
  index2 = 1;
86
- int num = this.SeedArray[index1] - this.SeedArray[index2];
100
+ }
101
+
102
+ int num = _seedArray[index1] - _seedArray[index2];
87
103
  if (num == int.MaxValue)
104
+ {
88
105
  --num;
106
+ }
107
+
89
108
  if (num < 0)
109
+ {
90
110
  num += int.MaxValue;
91
- this.SeedArray[index1] = num;
92
- this.inext = index1;
93
- this.inextp = index2;
94
- return unchecked((uint)num);
111
+ }
112
+
113
+ _seedArray[index1] = num;
114
+ _inext = index1;
115
+ _inextp = index2;
116
+ return num;
117
+ }
118
+
119
+ public override uint NextUint()
120
+ {
121
+ if (NextBool())
122
+ {
123
+ return unchecked((uint)(Next() ^ 0x80000000));
124
+ }
125
+ return unchecked((uint)Next());
126
+ }
127
+
128
+ public override bool NextBool()
129
+ {
130
+ return Next() < HalfwayInt;
95
131
  }
96
132
 
97
133
  public override double NextDouble()
98
134
  {
99
- double generated;
135
+ double random;
100
136
  do
101
137
  {
102
- generated = unchecked((int)NextUint()) * 4.6566128752458E-10;
103
- } while (generated < 0 || 1 <= generated);
138
+ random = Next() / (1.0 * int.MaxValue);
139
+ } while (1.0 <= random);
104
140
 
105
- return generated;
141
+ return random;
106
142
  }
107
143
 
108
144
  public override float NextFloat()
109
145
  {
110
- return (float)NextDouble();
146
+ float random;
147
+ do
148
+ {
149
+ random = Next() * (1f / int.MaxValue);
150
+ } while (1f <= random);
151
+
152
+ return random;
111
153
  }
112
154
 
113
155
  public override IRandom Copy()
114
156
  {
115
157
  SystemRandom copy = new(InternalState);
116
-
117
- for (int i = 0; i < SeedArray.Length; ++i)
118
- {
119
- copy.SeedArray[i] = SeedArray[i];
120
- }
121
-
158
+ Array.Copy(_seedArray, copy._seedArray, _seedArray.Length);
122
159
  return copy;
123
160
  }
124
161
  }