// MIT License - Copyright (c) 2023 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Core.Random
{
using System;
///
/// A lightweight PCG-based struct RNG intended for low-overhead use (e.g., Burst/Jobs-friendly contexts).
///
///
///
/// Provides a minimal, non-allocating random number source patterned after PCG. Because it is a struct,
/// it is copied by value; take care to pass by ref where you want a single advancing sequence. Not wired into
/// ; use this when allocations or interface dispatch must be avoided.
///
/// Pros:
///
/// - No allocations, tiny footprint; suitable for tight inner loops.
/// - Deterministic with good general-purpose quality.
///
/// Cons:
///
/// - Value-type semantics—accidental copying can lead to diverging sequences.
/// - Not cryptographically secure; limited convenience API.
///
/// When to use:
///
/// - Performance-critical contexts (Burst/Jobs) where you control by-ref semantics.
///
/// When not to use:
///
/// - General gameplay—prefer with the richer API.
///
///
///
///
/// var rng = new NativePcgRandom(seed: 123);
/// uint u = rng.NextUint();
/// bool b = rng.NextBool();
/// float t = rng.NextFloat();
///
///
public struct NativePcgRandom
{
private const uint HalfwayUint = uint.MaxValue / 2;
private const double MagicDouble = 4.6566128752458E-10;
private const float MagicFloat = 5.960465E-008F;
private readonly ulong _increment;
private ulong _state;
public NativePcgRandom(Guid seed)
{
(ulong a, ulong b) = RandomUtilities.GuidToUInt64Pair(seed);
_state = a;
_increment = b;
}
public NativePcgRandom(int seed)
{
_increment = 6554638469UL;
_state = unchecked((ulong)seed);
_increment = NextUlong();
}
public uint NextUint()
{
unchecked
{
ulong oldState = _state;
_state = oldState * 6364136223846793005UL + _increment;
uint xorShifted = (uint)(((oldState >> 18) ^ oldState) >> 27);
int rot = (int)(oldState >> 59);
return (xorShifted >> rot) | (xorShifted << (-rot & 31));
}
}
public int Next(int max)
{
if (max <= 0)
{
throw new ArgumentException($"Max {max} cannot be less-than or equal-to 0");
}
return unchecked((int)NextUint(unchecked((uint)max)));
}
public uint NextUint(uint max)
{
/*
https://github.com/libevent/libevent/blob/3807a30b03ab42f2f503f2db62b1ef5876e2be80/arc4random.c#L531
https://cs.stackexchange.com/questions/570/generating-uniformly-distributed-random-numbers-using-a-coin
Generates a uniform random number within the bound, avoiding modulo bias
*/
uint threshold = unchecked((uint)((0x100000000UL - max) % max));
int attempts = 0;
while (true)
{
uint randomValue = NextUint();
if (threshold <= randomValue)
{
return randomValue % max;
}
if (++attempts > 1 << 16)
{
// Prevent infinite loop: return modulo (introduces tiny bias) rather than hang
return randomValue % max;
}
}
}
public long NextLong()
{
uint upper = NextUint();
uint lower = NextUint();
// Mix things up a little
if (NextBool())
{
return unchecked((long)((ulong)upper << 32) | lower);
}
return unchecked((long)((ulong)lower << 32) | upper);
}
public ulong NextUlong()
{
return unchecked((ulong)NextLong());
}
public bool NextBool()
{
return NextUint() < HalfwayUint;
}
public float NextFloat()
{
uint floatAsInt = NextUint();
return (floatAsInt >> 8) * MagicFloat;
}
}
}