// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.Random { using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text.Json.Serialization; using Extension; using Helper; using ProtoBuf; using WallstopStudios.UnityHelpers.Utils; /// /// FlurryBurst32: a six-word ARX-style generator offering high quality and excellent parallel sequencing. /// /// /// /// Reference: Will Stafford Parsons (wileylooper/flurryburst, repository offline). /// Based on wileylooper/flurryburst, this implementation captures the 32-bit variant that balances /// speed, period (~2128) and state size for gameplay workloads. It is suitable as a drop-in /// alternative to Xoshiro128** and similar families, while retaining deterministic serialization support. /// /// Pros: /// /// Small state (6×32-bit) with excellent statistical behaviour. /// Deterministic snapshots via and protobuf/JSON. /// Easy to create parallel streams by varying the d word. /// /// Cons: /// /// Not cryptographically secure. /// Requires a short warm-up (performed automatically) to avoid transient bias. /// /// When to use: /// /// Deterministic gameplay, procedural content, Monte-Carlo style sampling. /// /// When not to use: /// /// Security/adversarial contexts. /// /// /// /// /// using WallstopStudios.UnityHelpers.Core.Random; /// /// IRandom rng = new FlurryBurstRandom(seed: 123u); /// int value = rng.Next(0, 100); /// float weight = rng.NextFloat(); /// /// [RandomGeneratorMetadata( RandomQuality.Excellent, "Six-word ARX-style generator tuned for all-around use; passes TestU01 BigCrush per upstream reference implementation.", "Will Stafford Parsons (wileylooper)", "https://github.com/wileylooper/flurryburst" )] [Serializable] [DataContract] [ProtoContract] public sealed class FlurryBurstRandom : AbstractRandom, IEquatable, IComparable, IComparable { private const uint Increment = 1_111_111_111U; private const int PayloadByteCount = sizeof(uint) * 2; public static FlurryBurstRandom Instance => ThreadLocalRandom.Instance; public override RandomState InternalState { get { ulong state1 = ((ulong)_a << 32) | _b; ulong state2 = ((ulong)_c << 32) | _d; using PooledArray payloadLease = WallstopArrayPool.Get( PayloadByteCount, out byte[] buffer ); Span payload = buffer.AsSpan(0, PayloadByteCount); BinaryPrimitives.WriteUInt32LittleEndian(payload.Slice(0, sizeof(uint)), _e); BinaryPrimitives.WriteUInt32LittleEndian( payload.Slice(sizeof(uint), sizeof(uint)), _f ); return BuildState( state1, state2, new ArraySegment(buffer, 0, PayloadByteCount) ); } } [ProtoMember(6)] private uint _a; [ProtoMember(7)] private uint _b; [ProtoMember(8)] private uint _c; [ProtoMember(9)] private uint _d; [ProtoMember(10)] private uint _e; [ProtoMember(11)] private uint _f; public FlurryBurstRandom() : this(Guid.NewGuid()) { } public FlurryBurstRandom(Guid guid) { InitializeFromGuid(guid); } [JsonConstructor] public FlurryBurstRandom(RandomState internalState) { ulong state1 = internalState.State1; ulong state2 = internalState.State2; _a = (uint)(state1 >> 32); _b = (uint)state1; _c = (uint)(state2 >> 32); _d = (uint)state2; byte[] payload = internalState._payload; if (payload != null && payload.Length >= sizeof(uint) * 2) { _e = BinaryPrimitives.ReadUInt32LittleEndian(payload.AsSpan(0, sizeof(uint))); _f = BinaryPrimitives.ReadUInt32LittleEndian( payload.AsSpan(sizeof(uint), sizeof(uint)) ); } else { _e = 0; _f = 0; } RestoreCommonState(internalState); } public override uint NextUint() { unchecked { uint mix = RotateLeft(_a, 13); _a = _b; _b = _e; _c += _d; _d += _b; _e = _f + Increment; _f = _c ^ mix; return (_e >> 1) ^ _f; } } public override IRandom Copy() { return new FlurryBurstRandom(InternalState); } public override bool Equals(object obj) { return Equals(obj as FlurryBurstRandom); } public bool Equals(FlurryBurstRandom other) { if (other == null) { return false; } // ReSharper disable once CompareOfFloatsByEqualityOperator return _a == other._a && _b == other._b && _c == other._c && _d == other._d && _e == other._e && _f == other._f && _cachedGaussian == other._cachedGaussian; } public override int GetHashCode() { return Objects.HashCode(_a, _b, _c, _d, _e, _f); } public override string ToString() { return this.ToJson(); } public int CompareTo(object obj) { return CompareTo(obj as FlurryBurstRandom); } public int CompareTo(FlurryBurstRandom other) { if (other == null) { return -1; } int comparison = _a.CompareTo(other._a); if (comparison != 0) { return comparison; } comparison = _b.CompareTo(other._b); if (comparison != 0) { return comparison; } comparison = _c.CompareTo(other._c); if (comparison != 0) { return comparison; } comparison = _d.CompareTo(other._d); if (comparison != 0) { return comparison; } comparison = _e.CompareTo(other._e); if (comparison != 0) { return comparison; } return _f.CompareTo(other._f); } private void InitializeFromGuid(Guid guid) { (ulong seed0, ulong seed1) = RandomUtilities.GuidToUInt64Pair(guid); InitializeFromUlongs(seed0, seed1); } private void InitializeFromUlongs(ulong seed0, ulong seed1) { ulong mixer = seed0 ^ (seed1 << 1) ^ 0x9E3779B97F4A7C15UL; _a = Mix(ref mixer); _b = Mix(ref mixer); _c = Mix(ref mixer); _d = Mix(ref mixer); if (_d == 0) { _d = 1; } _e = Mix(ref mixer); _f = Mix(ref mixer); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Mix(ref ulong state) { unchecked { state += 0x9E3779B97F4A7C15UL; ulong z = state; z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL; z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL; z ^= z >> 31; return (uint)z; } } } }