// MIT License - Copyright (c) 2024 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.Random { using System; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text.Json.Serialization; using Extension; using Helper; using ProtoBuf; /// /// A fast 128-bit state Xoroshiro-based PRNG with good quality and tiny footprint. /// /// /// /// Xoroshiro family generators (here in a 64/64 configuration) offer an excellent balance between speed and quality /// for real-time applications. This implementation maintains two 64-bit state variables and returns 32-bit outputs. /// /// Pros: /// /// Very fast; suitable for gameplay and procedural generation. /// Good statistical properties for non-crypto use; long period (~2^128−1). /// Deterministic and reproducible across platforms. /// /// Cons: /// /// Not cryptographically secure. /// Low bits may show weaker properties in some variants; use full width for mixing. /// /// When to use: /// /// General-purpose game randomness, procedural placement, shuffles, noise seeding. /// /// When not to use: /// /// Security or adversarial contexts. /// /// /// Threading: Prefer ThreadLocalRandom<XoroShiroRandom>.Instance to avoid sharing state across threads. /// /// /// /// /// using WallstopStudios.UnityHelpers.Core.Random; /// /// IRandom rng = new XoroShiroRandom(Guid.NewGuid()); /// float t = rng.NextFloat(); /// Color c = rng.NextColor(); // via RandomExtensions /// /// // Save/restore for deterministic replays /// var state = rng.InternalState; /// var replay = new XoroShiroRandom(state); /// /// [RandomGeneratorMetadata( RandomQuality.VeryGood, "xoshiro128** variant; authors recommend for general-purpose use and report clean BigCrush performance with jump functions.", "Blackman & Vigna 2019", "https://prng.di.unimi.it/xoshiro128starstar.c" )] [Serializable] [DataContract] [ProtoContract] public sealed class XoroShiroRandom : AbstractRandom, IEquatable, IComparable, IComparable { public static XoroShiroRandom Instance => ThreadLocalRandom.Instance; public override RandomState InternalState => BuildState(_s0, _s1); [ProtoMember(6)] internal ulong _s0; [ProtoMember(7)] internal ulong _s1; private void EnsureNonZeroState() { if ((_s0 | _s1) == 0) { _s0 = 0x9E3779B97F4A7C15UL; _s1 = 0xD1B54A32D192ED03UL; } } public XoroShiroRandom() : this(Guid.NewGuid()) { } public XoroShiroRandom(Guid guid) { (ulong a, ulong b) = RandomUtilities.GuidToUInt64Pair(guid); _s0 = a; _s1 = b; EnsureNonZeroState(); } public XoroShiroRandom(ulong seed1, ulong seed2) { _s0 = seed1; _s1 = seed2; EnsureNonZeroState(); } [JsonConstructor] public XoroShiroRandom(RandomState internalState) { _s0 = internalState.State1; _s1 = internalState.State2; RestoreCommonState(internalState); EnsureNonZeroState(); } public override uint NextUint() { unchecked { EnsureNonZeroState(); ulong s0 = _s0; ulong s1 = _s1; ulong result = s0 + s1; s1 ^= s0; _s0 = Rotl(s0, 24) ^ s1 ^ (s1 << 16); _s1 = Rotl(s1, 37); return (uint)result; } } public override IRandom Copy() { return new XoroShiroRandom(InternalState); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong Rotl(ulong x, int k) { return (x << k) | (x >> (64 - k)); } public override bool Equals(object obj) { return Equals(obj as XoroShiroRandom); } public bool Equals(XoroShiroRandom other) { if (other == null) { return false; } return _s0 == other._s0 && _s1 == other._s1; } public override int GetHashCode() { return Objects.HashCode(_s0, _s1); } public override string ToString() { return this.ToJson(); } public int CompareTo(object obj) { return CompareTo(obj as XoroShiroRandom); } public int CompareTo(XoroShiroRandom other) { if (other == null) { return -1; } int comparison = _s0.CompareTo(other._s0); if (comparison != 0) { return comparison; } return _s1.CompareTo(other._s1); } } }