From 83fc86d2a5b7722dffc3bd0c48e0092411b787da Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Thu, 22 Aug 2024 14:19:45 +1000 Subject: [PATCH] replace darwin prng with higher quality prngs --- Sources/Voxelotl/CMakeLists.txt | 3 +- Sources/Voxelotl/Game.swift | 10 +- Sources/Voxelotl/Random/DarwinRandom.swift | 33 ------ Sources/Voxelotl/Random/PCG32Random.swift | 47 ++++++++ Sources/Voxelotl/Random/Xoroshiro128.swift | 126 +++++++++++++++++++++ 5 files changed, 183 insertions(+), 36 deletions(-) delete mode 100644 Sources/Voxelotl/Random/DarwinRandom.swift create mode 100644 Sources/Voxelotl/Random/PCG32Random.swift create mode 100644 Sources/Voxelotl/Random/Xoroshiro128.swift diff --git a/Sources/Voxelotl/CMakeLists.txt b/Sources/Voxelotl/CMakeLists.txt index 435ac7c..1dd7bca 100644 --- a/Sources/Voxelotl/CMakeLists.txt +++ b/Sources/Voxelotl/CMakeLists.txt @@ -18,7 +18,8 @@ add_executable(Voxelotl MACOSX_BUNDLE Random/RandomProvider.swift Random/RandomRange.swift Random/Arc4Random.swift - Random/DarwinRandom.swift + Random/PCG32Random.swift + Random/Xoroshiro128.swift # Resource classes NSImageLoader.swift diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 911de9d..dfd16c1 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -33,9 +33,15 @@ class Game: GameDelegate { } private func generateWorld() { - let newSeed = Arc4Random.instance.next(in: DarwinRandom.max) +#if true + let newSeed = UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32 printErr(newSeed) - var random = DarwinRandom(seed: newSeed) + var random = Xoroshiro128PlusPlus(seed: newSeed) +#else + var random = PCG32Random( + seed: UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32, + sequence: UInt64(Arc4Random.instance.next()) | UInt64(Arc4Random.instance.next()) << 32) +#endif self.chunk.fill(allBy: { if (random.next() & 0x1) == 0x1 { .solid(.init(rgb888: UInt32(random.next(in: 0..<0xFFFFFF+1))).linear) diff --git a/Sources/Voxelotl/Random/DarwinRandom.swift b/Sources/Voxelotl/Random/DarwinRandom.swift deleted file mode 100644 index daf30a3..0000000 --- a/Sources/Voxelotl/Random/DarwinRandom.swift +++ /dev/null @@ -1,33 +0,0 @@ -public struct DarwinRandom: RandomProvider { - public typealias Output = Int - - public static var min: Int { 0x00000000 } - public static var max: Int { 0x7FFFFFFF } - - private var state: Int - - init() { - self.state = 0 - } - - public init(seed: Int) { - self.state = seed - } - - mutating public func seed(with seed: Int) { - self.state = seed - } - - mutating public func next() -> Int { - if self.state == 0 { - self.state = 123459876 - } - let hi = self.state / 127773 - let lo = self.state - hi * 127773 - self.state = 16807 * lo - 2836 * hi - if self.state < 0 { - self.state += Self.max - } - return self.state % (Self.max + 1) - } -} diff --git a/Sources/Voxelotl/Random/PCG32Random.swift b/Sources/Voxelotl/Random/PCG32Random.swift new file mode 100644 index 0000000..3462663 --- /dev/null +++ b/Sources/Voxelotl/Random/PCG32Random.swift @@ -0,0 +1,47 @@ +public struct PCG32Random: RandomProvider { + public typealias Output = UInt32 + + public static var min: UInt32 { .min } + public static var max: UInt32 { .max } + + private var _state: UInt64, _inc: UInt64 + + public var state: (UInt64, UInt64) { + get { (self._state, self._inc) } + set { + self._state = newValue.0 + self._inc = newValue.1 + } + } + + init() { + self._state = 0x853C49E6748FEA9B + self._inc = 0xDA3E39CB94B95BDB + } + + public init(seed: UInt64, sequence: UInt64) { + self.init() + self.seed(state: _state, sequence: sequence) + } + + public mutating func seed(state: UInt64, sequence: UInt64) { + self._state = 0 + self._inc = sequence << 1 | 0x1 + _ = next() + self._state &+= state + _ = next() + } + + public mutating func next() -> UInt32 { + let prevState = self._state + + // LCG component + self._state &*= 6364136223846793005 + self._state &+= self._inc + + // Permutation (XorShift + RotRight) + let xorShifted = UInt32(truncatingIfNeeded: (prevState &>> 18 ^ prevState) &>> 27) + let rot59 = UInt32(truncatingIfNeeded: prevState &>> 59) + return xorShifted &>> rot59 | xorShifted &<< UInt32(bitPattern: -Int32(bitPattern: rot59) & 0x1F) + } +} diff --git a/Sources/Voxelotl/Random/Xoroshiro128.swift b/Sources/Voxelotl/Random/Xoroshiro128.swift new file mode 100644 index 0000000..e6eb016 --- /dev/null +++ b/Sources/Voxelotl/Random/Xoroshiro128.swift @@ -0,0 +1,126 @@ +struct Xoroshiro128Plus: RandomProvider { + public typealias Output = UInt64 + + static public var min: UInt64 { .min } + static public var max: UInt64 { .max } + + public var state: (UInt64, UInt64) + + public init() { + self.state = (0, 0) + } + + public init(state: (UInt64, UInt64)) { + self.state = (state.0, state.1) + } + + public init(seed: UInt64) { + let s0 = splitMix64(seed: seed) + self.init(state: (s0, splitMix64(seed: s0))) + } + + public mutating func seed(_ seed: UInt64) { + let s0 = splitMix64(seed: seed) + self.state = (s0, splitMix64(seed: s0)) + } + + public mutating func next() -> UInt64 { + let result = self.state.0 &+ self.state.1 + + let xor = state.1 ^ self.state.0 + self.state = ( + self.state.0.rotate(left: 24) ^ xor ^ xor &<< 16, + xor.rotate(left: 37)) + + return result + } +} + +struct Xoroshiro128PlusPlus: RandomProvider { + public typealias Output = UInt64 + + static public var min: UInt64 { .min } + static public var max: UInt64 { .max } + + public var state: (UInt64, UInt64) + + public init() { + self.state = (0, 0) + } + + public init(state: (UInt64, UInt64)) { + self.state = (state.0, state.1) + } + + public init(seed: UInt64) { + let s0 = splitMix64(seed: seed) + self.init(state: (s0, splitMix64(seed: s0))) + } + + public mutating func seed(_ seed: UInt64) { + let s0 = splitMix64(seed: seed) + self.state = (s0, splitMix64(seed: s0)) + } + + public mutating func next() -> UInt64 { + let result = (self.state.0 &+ self.state.1).rotate(left: 17) &+ self.state.0 + + let xor = state.1 ^ self.state.0 + self.state = ( + self.state.0.rotate(left: 49) ^ xor ^ xor << 21, + xor.rotate(left: 28)) + + return result + } +} + +struct Xoroshiro128StarStar: RandomProvider { + public typealias Output = UInt64 + + static public var min: UInt64 { .min } + static public var max: UInt64 { .max } + + public var state: (UInt64, UInt64) + + public init() { + self.state = (0, 0) + } + + public init(state: (UInt64, UInt64)) { + self.state = (state.0, state.1) + } + + public init(seed: UInt64) { + let s0 = splitMix64(seed: seed) + self.init(state: (s0, splitMix64(seed: s0))) + } + + public mutating func seed(_ seed: UInt64) { + let s0 = splitMix64(seed: seed) + self.state = (s0, splitMix64(seed: s0)) + } + + public mutating func next() -> UInt64 { + let result = (self.state.0 &* 5).rotate(left: 7) &* 9 + + let xor = self.state.1 ^ self.state.0 + self.state = ( + self.state.0.rotate(left: 24) ^ xor ^ xor << 16, + xor.rotate(left: 37)) + + return result + } +} + +fileprivate extension UInt64 { + func rotate(left count: Int) -> Self { + self &<< count | self &>> (Self.bitWidth &- count) + } +} + +fileprivate func splitMix64(seed: UInt64) -> UInt64 { + var x = seed &+ 0x9E3779B97F4A7C15 + x = (x ^ x &>> 30) &* 0xBF58476D1CE4E5B9 + x = (x ^ x &>> 27) &* 0x94D049BB133111EB + return x ^ x &>> 31 +}