From b24d154c93fd0f4ac5ff89996b0a79f940b548cb Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Thu, 22 Aug 2024 05:57:03 +1000 Subject: [PATCH] broader randomrange extensions --- Sources/Voxelotl/Game.swift | 4 +- Sources/Voxelotl/Random/Arc4Random.swift | 35 +++++++++-- Sources/Voxelotl/Random/RandomRange.swift | 77 +++++++++++++++++++++-- 3 files changed, 106 insertions(+), 10 deletions(-) diff --git a/Sources/Voxelotl/Game.swift b/Sources/Voxelotl/Game.swift index 027d2c6..911de9d 100644 --- a/Sources/Voxelotl/Game.swift +++ b/Sources/Voxelotl/Game.swift @@ -33,7 +33,9 @@ class Game: GameDelegate { } private func generateWorld() { - var random = DarwinRandom(seed: Arc4Random.instance.next(in: DarwinRandom.max)) + let newSeed = Arc4Random.instance.next(in: DarwinRandom.max) + printErr(newSeed) + var random = DarwinRandom(seed: newSeed) 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/Arc4Random.swift b/Sources/Voxelotl/Random/Arc4Random.swift index 2d3a75b..33a0c9d 100644 --- a/Sources/Voxelotl/Random/Arc4Random.swift +++ b/Sources/Voxelotl/Random/Arc4Random.swift @@ -17,12 +17,37 @@ public class Arc4Random: RandomProvider { arc4random() } - public func next(in bound: Int) -> Int { - assert(bound <= Self.max) - return Int(arc4random_uniform(UInt32(bound))) + func next(in bound: UInt32) -> UInt32 { + return arc4random_uniform(bound) } - public func next(in range: Range) -> Int { - return range.lowerBound + next(in: range.upperBound - range.lowerBound) + func next(in bound: Int) -> Int { + assert(bound <= UInt32.max, "Maximum raw random provider output is smaller than requested bound") + return Int(arc4random_uniform(UInt32(bound))) + } +} + +public extension Arc4Random { + func next(in range: Range) -> UInt32 { + assert(!range.isEmpty, "Ranged next called with empty range") + return range.lowerBound + next(in: range.upperBound - range.lowerBound) + } + + func next(in range: ClosedRange) -> UInt32 { + if range == 0...UInt32.max { + next() + } else { + next(in: range.upperBound - range.lowerBound + 1) + } + } + + func next(in range: Range) -> Int { + assert(!range.isEmpty, "Ranged next called with empty range") + return range.lowerBound + next(in: range.upperBound - range.lowerBound) + } + + func next(in range: ClosedRange) -> Int { + assert(range.upperBound - range.lowerBound < Int.max, "Closed range exceeds Int.max") + return range.lowerBound + next(in: range.upperBound - range.lowerBound + 1) } } diff --git a/Sources/Voxelotl/Random/RandomRange.swift b/Sources/Voxelotl/Random/RandomRange.swift index baf5b61..1352213 100644 --- a/Sources/Voxelotl/Random/RandomRange.swift +++ b/Sources/Voxelotl/Random/RandomRange.swift @@ -1,15 +1,17 @@ public extension RandomProvider where Output: BinaryInteger { mutating func next(in range: Range) -> Int { - range.lowerBound + self.next(in: range.upperBound - range.lowerBound) + assert(!range.isEmpty, "Ranged next called with empty range") + return range.lowerBound + self.next(in: range.upperBound - range.lowerBound) } mutating func next(in range: ClosedRange) -> Int { - range.lowerBound + self.next(in: range.upperBound - range.lowerBound + 1) + assert(range.upperBound - range.lowerBound < Int.max, "Closed range exceeds Int.max") + return range.lowerBound + self.next(in: range.upperBound - range.lowerBound + 1) } mutating func next(in bound: Int) -> Int { - assert(Self.min == 0) - assert(Self.max >= bound) + assert(Self.min == 0, "Range operations are unsupported on random providers with a non-zero minimum") + assert(Self.max >= bound, "Maximum raw random provider output is smaller than requested bound") let threshold = Int(Self.max % Output(bound)) var result: Int repeat { @@ -18,3 +20,70 @@ public extension RandomProvider where Output: BinaryInteger { return result % bound } } + +public extension RandomProvider where Output: UnsignedInteger { + mutating func next(in range: Range) -> Output { + assert(!range.isEmpty, "Ranged next called with empty range") + return range.lowerBound + self.next(in: range.upperBound - range.lowerBound) + } + + mutating func next(in range: ClosedRange) -> Output { + if range == 0...Output.max { + next() + } else { + range.lowerBound + self.next(in: range.upperBound - range.lowerBound + 1) + } + } + + mutating func next(in bound: Output) -> Output { + assert(Self.min == 0, "Range operations are unsupported on random providers with a non-zero minimum") + assert(Self.max >= bound, "Maximum raw random provider output is smaller than requested bound") + let threshold = (Self.max &- bound &+ 1) % bound + var result: Output + repeat { + result = self.next() + } while result < threshold + return result % bound + } +} + +//MARK: - Experimental + +// Uniform bounded random without modulos, WILL produce different results from the standard bounded next +public extension RandomProvider where Output: UnsignedInteger { + mutating func nextModless(in range: Range) -> Output { + assert(!range.isEmpty, "Ranged next called with empty range") + return range.lowerBound + self.nextModless(in: range.upperBound - range.lowerBound) + } + + mutating func nextModless(in range: ClosedRange) -> Output { + if range == 0...Output.max { + self.next() + } else { + range.lowerBound + self.nextModless(in: range.upperBound - range.lowerBound + 1) + } + } + + mutating func nextModless(in bound: Output) -> Output { + func pow2MaskFrom(range num: Output) -> Output { + if num & (num - 1) == 0 { + return num - 1 + } + var result: Output = 1 + for _ in 0..= num { + return result - 1 + } + result <<= 1 + } + return .max + } + + let mask = pow2MaskFrom(range: bound) + var result: Output + repeat { + result = next() & mask + } while result >= bound + return result + } +}