// 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.Runtime.Serialization; using System.Text.Json.Serialization; using ProtoBuf; using WallstopStudios.UnityHelpers.Core.Extension; using WallstopStudios.UnityHelpers.Core.Helper; using WallstopStudios.UnityHelpers.Utils; /// /// StormDrop32: a large-state ARX generator inspired by SHISHUA-style buffer mixing, emphasizing long periods and diffusion. /// /// /// /// Reference: Will Stafford Parsons (wileylooper/stormdrop, repository offline). /// Ported from wileylooper/stormdrop. The 32-bit variant maintains a 1024-element ring buffer and two 32-bit /// accumulators. Each step mixes the current index with the accumulators, rotates, and feeds the buffer to provide /// high-quality sequences suitable for heavy simulation workloads. /// /// Pros: /// /// Large period and strong diffusion thanks to the 4 KB buffer. /// Deterministic snapshots via . /// Thread-local access available via . /// /// Cons: /// /// Higher per-instance memory compared to smaller generators. /// Not cryptographically secure. /// /// When to use: /// /// Procedural workloads needing long non-overlapping streams or large batches. /// /// When not to use: /// /// Memory-constrained contexts; prefer smaller-state generators like FlurryBurst. /// Security/adversarial scenarios. /// /// /// /// /// using WallstopStudios.UnityHelpers.Core.Random; /// /// StormDropRandom rng = new StormDropRandom(seed: 42u); /// float noise = rng.NextFloat(); /// Vector3 point = rng.NextVector3InSphere(10f); // via RandomExtensions /// /// [RandomGeneratorMetadata( RandomQuality.Excellent, "20-word ARX generator derived from SHISHUA; author reports excellent PractRand performance and long periods.", "Will Stafford Parsons", "" // Original repository wileylooper/stormdrop is offline )] [Serializable] [DataContract] [ProtoContract(SkipConstructor = true)] public sealed class StormDropRandom : AbstractRandom, IEquatable, IComparable, IComparable { private const uint Increment = 1_111_111_111U; private const int ElementCount = 1024; private const int ElementMask = ElementCount - 1; private const int ElementByteSize = ElementCount * sizeof(uint); private const int WarmupRounds = 128; public static StormDropRandom Instance => ThreadLocalRandom.Instance; public override RandomState InternalState { get { using PooledArray payloadLease = WallstopArrayPool.Get( ElementByteSize, out byte[] buffer ); Buffer.BlockCopy(_elements, 0, buffer, 0, ElementByteSize); ulong state1 = ((ulong)_a << 32) | _b; return BuildState( state1, payload: new ArraySegment(buffer, 0, ElementByteSize) ); } } [ProtoMember(6)] private uint[] _elements = new uint[ElementCount]; [ProtoMember(7)] private uint _a; [ProtoMember(8)] private uint _b; public StormDropRandom() : this(Guid.NewGuid()) { } public StormDropRandom(Guid guid) { InitializeFromGuid(guid); } public StormDropRandom(uint seed) { uint seedB = seed ^ 0x9E3779B9U; if (seedB == 0) { seedB = 1U; } InitializeFromScalars(seed, seedB); } public StormDropRandom(uint seedA, uint seedB) { InitializeFromScalars(seedA, seedB == 0 ? 1U : seedB); } [JsonConstructor] public StormDropRandom(RandomState internalState) { _a = (uint)(internalState.State1 >> 32); _b = (uint)internalState.State1; LoadSerializedElements(internalState._payload); RestoreCommonState(internalState); } public override uint NextUint() { unchecked { uint index = _b & ElementMask; uint mix = (_elements[index] ^ _a) + _b; _a = RotateLeft(_a, 17) ^ _b; _b += Increment; _elements[_b & ElementMask] += RotateLeft(mix, 13); return mix; } } public override IRandom Copy() { return new StormDropRandom(InternalState); } public bool Equals(StormDropRandom other) { if (other == null) { return false; } if (_a != other._a || _b != other._b) { return false; } if (!_elements.AsSpan().SequenceEqual(other._elements)) { return false; } return _cachedGaussian == other._cachedGaussian; } public override bool Equals(object obj) { return Equals(obj as StormDropRandom); } public int CompareTo(object obj) { return CompareTo(obj as StormDropRandom); } public int CompareTo(StormDropRandom 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; } for (int i = 0; i < ElementCount; ++i) { comparison = _elements[i].CompareTo(other._elements[i]); if (comparison != 0) { return comparison; } } return 0; } public override int GetHashCode() { return Objects.HashCode(_a, _b); } public override string ToString() { return this.ToJson(); } private void InitializeFromGuid(Guid guid) { (ulong seed0, ulong seed1) = RandomUtilities.GuidToUInt64Pair(guid); ulong mixer = seed0 ^ (seed1 << 1) ^ 0x9E3779B97F4A7C15UL; InitializeFromMixer(ref mixer); } private void InitializeFromScalars(uint seedA, uint seedB) { ulong mixer = ((ulong)seedA << 32) | seedB; mixer ^= 0xD2B74407B1CE6E93UL; InitializeFromMixer(ref mixer); } private void InitializeFromMixer(ref ulong mixer) { if (_elements == null || _elements.Length != ElementCount) { _elements = new uint[ElementCount]; } for (int i = 0; i < ElementCount; ++i) { _elements[i] = Mix32(ref mixer); } _a = Mix32(ref mixer); _b = Mix32(ref mixer) | 1U; for (int i = 0; i < WarmupRounds; ++i) { _ = NextUint(); } } private void LoadSerializedElements(byte[] payload) { if (_elements == null || _elements.Length != ElementCount) { _elements = new uint[ElementCount]; } if (payload != null && payload.Length >= ElementByteSize) { Buffer.BlockCopy(payload, 0, _elements, 0, ElementByteSize); return; } Array.Clear(_elements, 0, _elements.Length); } } }