From bcf6faad0c008158b539a6a9c6c79821cf0c28e1 Mon Sep 17 00:00:00 2001 From: staphen Date: Thu, 23 Nov 2023 08:54:53 -0500 Subject: [PATCH 1/5] Introduce xoshiro RNG to generate dungeon seeds --- Source/engine/random.cpp | 19 ++++++++-- Source/engine/random.hpp | 79 +++++++++++++++++++++++++++++++++++++++- Source/loadsave.cpp | 2 +- Source/msg.cpp | 2 +- Source/multi.cpp | 15 ++++++-- Source/multi.h | 2 +- test/random_test.cpp | 8 ++-- 7 files changed, 112 insertions(+), 15 deletions(-) diff --git a/Source/engine/random.cpp b/Source/engine/random.cpp index 0b0f9754d4e..2832017e854 100644 --- a/Source/engine/random.cpp +++ b/Source/engine/random.cpp @@ -13,6 +13,19 @@ uint32_t sglGameSeed; /** Borland C/C++ psuedo-random number generator needed for vanilla compatibility */ std::linear_congruential_engine diabloGenerator; +/** Xoshiro pseudo-random number generator to provide less predictable seeds */ +xoshiro128plusplus seedGenerator; + +void ResetSeedGenerator(uint64_t seed) +{ + seedGenerator.seed(seed); +} + +uint32_t GenerateSeed() +{ + return seedGenerator.next(); +} + void SetRndSeed(uint32_t seed) { diabloGenerator.seed(seed); @@ -27,12 +40,12 @@ uint32_t GetLCGEngineState() void DiscardRandomValues(unsigned count) { while (count != 0) { - GenerateSeed(); + GenerateRandomNumber(); count--; } } -uint32_t GenerateSeed() +uint32_t GenerateRandomNumber() { sglGameSeed = diabloGenerator(); return sglGameSeed; @@ -40,7 +53,7 @@ uint32_t GenerateSeed() int32_t AdvanceRndSeed() { - const int32_t seed = static_cast(GenerateSeed()); + const int32_t seed = static_cast(GenerateRandomNumber()); // since abs(INT_MIN) is undefined behavior, handle this value specially return seed == std::numeric_limits::min() ? std::numeric_limits::min() : std::abs(seed); } diff --git a/Source/engine/random.hpp b/Source/engine/random.hpp index 2dda68c7c10..abd7a1d36a4 100644 --- a/Source/engine/random.hpp +++ b/Source/engine/random.hpp @@ -136,6 +136,83 @@ class DiabloGenerator { } }; +/** Adapted from https://prng.di.unimi.it/xoshiro128plusplus.c */ +class xoshiro128plusplus { +private: + static uint32_t rotl(const uint32_t x, int k) + { + return (x << k) | (x >> (32 - k)); + } + + uint32_t s[4]; + +public: + void seed(uint64_t value) + { + uint64_t rand1 = next_split(value); + uint64_t rand2 = next_split(rand1); + s[0] = static_cast(rand1); + s[1] = static_cast(rand2); + s[2] = static_cast(rand1 >> 32); + s[3] = static_cast(rand2 >> 32); + } + + void seed(uint32_t value) + { + s[0] = next_split(value); + s[1] = next_split(s[0]); + s[2] = next_split(s[1]); + s[3] = next_split(s[2]); + } + + uint32_t next() + { + const uint32_t result = rotl(s[0] + s[3], 7) + s[0]; + + const uint32_t t = s[1] << 9; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + + s[3] = rotl(s[3], 11); + + return result; + } + +private: + /** Adapted from https://prng.di.unimi.it/splitmix64.c */ + static uint64_t next_split(uint64_t x) + { + uint64_t z = (x += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + + /** Adapted from https://prng.di.unimi.it/splitmix64.c using fmix32 from MurmurHash3 */ + static uint32_t next_split(uint32_t x) + { + uint32_t z = (x += 0x9e3779b9); + z = (z ^ (z >> 16)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae35; + return z ^ (z >> 16); + } +}; + +/** + * @brief Advances the global seed generator state and returns the new value + */ +void ResetSeedGenerator(uint64_t seed); + +/** + * @brief Advances the global seed generator state and returns the new value + */ +uint32_t GenerateSeed(); + /** * @brief Set the state of the RandomNumberEngine used by the base game to the specific seed * @param seed New engine state @@ -163,7 +240,7 @@ void DiscardRandomValues(unsigned count); /** * @brief Advances the global RandomNumberEngine state and returns the new value */ -uint32_t GenerateSeed(); +uint32_t GenerateRandomNumber(); /** * @brief Generates a random non-negative integer (most of the time) using the vanilla RNG diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index b940160db93..71c7165b9c0 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -1821,7 +1821,7 @@ void SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData) DoUnVision(myPlayer.position.tile, myPlayer._pLightRad); // fix for vision staying on the level if (leveltype == DTYPE_TOWN) - DungeonSeeds[0] = AdvanceRndSeed(); + DungeonSeeds[0] = GenerateSeed(); char szName[MaxMpqPathSize]; GetTempLevelNames(szName); diff --git a/Source/msg.cpp b/Source/msg.cpp index f3d90d0095a..de5f79491bd 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -750,7 +750,7 @@ void DeltaLeaveSync(uint8_t bLevel) if (!gbIsMultiplayer) return; if (leveltype == DTYPE_TOWN) { - DungeonSeeds[0] = AdvanceRndSeed(); + DungeonSeeds[0] = GenerateSeed(); return; } diff --git a/Source/multi.cpp b/Source/multi.cpp index fb99a31b8ed..9403a88b085 100644 --- a/Source/multi.cpp +++ b/Source/multi.cpp @@ -4,9 +4,9 @@ * Implementation of functions for keeping multiplaye games in sync. */ +#include #include #include -#include #include #include @@ -481,12 +481,19 @@ bool InitMulti(GameData *gameData) return true; } +uint64_t GenerateGameSeed() +{ + auto now = std::chrono::high_resolution_clock::now(); + auto duration = now.time_since_epoch(); + return static_cast(duration.count()); +} + } // namespace void InitGameInfo() { sgGameInitInfo.size = sizeof(sgGameInitInfo); - sgGameInitInfo.dwSeed = static_cast(time(nullptr)); + sgGameInitInfo.gameSeed = GenerateGameSeed(); sgGameInitInfo.programid = GAME_ID; sgGameInitInfo.versionMajor = PROJECT_VERSION_MAJOR; sgGameInitInfo.versionMinor = PROJECT_VERSION_MINOR; @@ -787,11 +794,11 @@ bool NetInit(bool bSinglePlayer) NetClose(); gbSelectProvider = false; } - SetRndSeed(sgGameInitInfo.dwSeed); + ResetSeedGenerator(sgGameInitInfo.gameSeed); gnTickDelay = 1000 / sgGameInitInfo.nTickRate; for (int i = 0; i < NUMLEVELS; i++) { - DungeonSeeds[i] = AdvanceRndSeed(); + DungeonSeeds[i] = GenerateSeed(); LevelSeeds[i] = std::nullopt; } PublicGame = DvlNet_IsPublicGame(); diff --git a/Source/multi.h b/Source/multi.h index b71c1aef914..9d24a918e75 100644 --- a/Source/multi.h +++ b/Source/multi.h @@ -23,7 +23,7 @@ struct Player; struct GameData { int32_t size; /** Used to initialise the seed table for dungeon levels so players in multiplayer games generate the same layout */ - uint32_t dwSeed; + uint64_t gameSeed; uint32_t programid; uint8_t versionMajor; uint8_t versionMinor; diff --git a/test/random_test.cpp b/test/random_test.cpp index ed8a8bdc6b5..0da0a97bd21 100644 --- a/test/random_test.cpp +++ b/test/random_test.cpp @@ -17,10 +17,10 @@ TEST(RandomTest, RandomEngineParams) SetRndSeed(0); // Starting from a seed of 0 means the multiplicand is dropped and the state advances by increment only - ASSERT_EQ(GenerateSeed(), increment) << "Increment factor is incorrect"; + ASSERT_EQ(GenerateRandomNumber(), increment) << "Increment factor is incorrect"; // LCGs use a formula of mult * seed + inc. Using a long form in the code to document the expected factors. - ASSERT_EQ(GenerateSeed(), (multiplicand * 1) + increment) << "Multiplicand factor is incorrect"; + ASSERT_EQ(GenerateRandomNumber(), (multiplicand * 1) + increment) << "Multiplicand factor is incorrect"; // C++11 defines the default seed for a LCG engine as 1. The ten thousandth value is commonly used for sanity checking // a sequence, so as we've had one round since state 1 we need to discard another 9998 values to get to the 10000th state. @@ -28,9 +28,9 @@ TEST(RandomTest, RandomEngineParams) DiscardRandomValues(9997); uint32_t expectedState = 3495122800U; - EXPECT_EQ(GenerateSeed(), expectedState) << "Wrong engine state after 9999 invocations"; + EXPECT_EQ(GenerateRandomNumber(), expectedState) << "Wrong engine state after 9999 invocations"; expectedState = 3007658545U; - ASSERT_EQ(GenerateSeed(), expectedState) << "Wrong engine state after 10000 invocations"; + ASSERT_EQ(GenerateRandomNumber(), expectedState) << "Wrong engine state after 10000 invocations"; } TEST(RandomTest, AbsDistribution) From 3ba95e0be4de9de6d6419d7bdef79757c71be9ca Mon Sep 17 00:00:00 2001 From: staphen Date: Sun, 17 Mar 2024 16:37:31 -0400 Subject: [PATCH 2/5] Apply changes based on code review --- Source/engine/random.hpp | 122 +++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 37 deletions(-) diff --git a/Source/engine/random.hpp b/Source/engine/random.hpp index abd7a1d36a4..0b029c9a343 100644 --- a/Source/engine/random.hpp +++ b/Source/engine/random.hpp @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -136,38 +137,103 @@ class DiabloGenerator { } }; -/** Adapted from https://prng.di.unimi.it/xoshiro128plusplus.c */ -class xoshiro128plusplus { -private: - static uint32_t rotl(const uint32_t x, int k) +// Based on fmix32 implementation from MurmurHash3 created by Austin Appleby in 2008 +// https://github.com/aappleby/smhasher/blob/61a0530f28277f2e850bfc39600ce61d02b518de/src/MurmurHash3.cpp#L68 +// and adapted from https://prng.di.unimi.it/splitmix64.c written in 2015 by Sebastiano Vigna +// +// See also: +// Guy L. Steele, Doug Lea, and Christine H. Flood. 2014. +// Fast splittable pseudorandom number generators. SIGPLAN Not. 49, 10 (October 2014), 453–472. +// https://doi.org/10.1145/2714064.2660195 +class SplitMix32 { + uint32_t state; + +public: + SplitMix32(uint32_t state) + : state(state) + { + } + + uint32_t next() + { + uint32_t z = (state += 0x9e3779b9); + z = (z ^ (z >> 16)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae35; + return z ^ (z >> 16); + } + + void generate(uint32_t *begin, const uint32_t *end) + { + while (begin != end) { + *begin = next(); + ++begin; + } + } +}; + +// Adapted from https://prng.di.unimi.it/splitmix64.c written in 2015 by Sebastiano Vigna +// +// See also: +// Guy L. Steele, Doug Lea, and Christine H. Flood. 2014. +// Fast splittable pseudorandom number generators. SIGPLAN Not. 49, 10 (October 2014), 453–472. +// https://doi.org/10.1145/2714064.2660195 +class SplitMix64 { + uint64_t state; + +public: + SplitMix64(uint64_t state) + : state(state) { - return (x << k) | (x >> (32 - k)); } + uint64_t next() + { + uint64_t z = (state += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + + void generate(uint64_t *begin, const uint64_t *end) + { + while (begin != end) { + *begin = next(); + ++begin; + } + } +}; + +/** Adapted from https://prng.di.unimi.it/xoshiro128plusplus.c written in 2019 by David Blackman and Sebastiano Vigna */ +class xoshiro128plusplus { +private: uint32_t s[4]; public: + xoshiro128plusplus() { seed(static_cast(1)); } + xoshiro128plusplus(uint32_t initialSeed) { seed(initialSeed); } + xoshiro128plusplus(uint64_t initialSeed) { seed(initialSeed); } + void seed(uint64_t value) { - uint64_t rand1 = next_split(value); - uint64_t rand2 = next_split(rand1); - s[0] = static_cast(rand1); - s[1] = static_cast(rand2); - s[2] = static_cast(rand1 >> 32); - s[3] = static_cast(rand2 >> 32); + uint64_t seeds[2]; + SplitMix64 seedSequence { value }; + seedSequence.generate(seeds, seeds + 2); + + s[0] = static_cast(seeds[0] >> 32); + s[1] = static_cast(seeds[0]); + s[2] = static_cast(seeds[1] >> 32); + s[3] = static_cast(seeds[1]); } void seed(uint32_t value) { - s[0] = next_split(value); - s[1] = next_split(s[0]); - s[2] = next_split(s[1]); - s[3] = next_split(s[2]); + SplitMix32 seedSequence { value }; + seedSequence.generate(s, s + 4); } uint32_t next() { - const uint32_t result = rotl(s[0] + s[3], 7) + s[0]; + const uint32_t result = std::rotl(s[0] + s[3], 7) + s[0]; const uint32_t t = s[1] << 9; @@ -178,33 +244,15 @@ class xoshiro128plusplus { s[2] ^= t; - s[3] = rotl(s[3], 11); + s[3] = std::rotl(s[3], 11); return result; } - -private: - /** Adapted from https://prng.di.unimi.it/splitmix64.c */ - static uint64_t next_split(uint64_t x) - { - uint64_t z = (x += 0x9e3779b97f4a7c15); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); - } - - /** Adapted from https://prng.di.unimi.it/splitmix64.c using fmix32 from MurmurHash3 */ - static uint32_t next_split(uint32_t x) - { - uint32_t z = (x += 0x9e3779b9); - z = (z ^ (z >> 16)) * 0x85ebca6b; - z = (z ^ (z >> 13)) * 0xc2b2ae35; - return z ^ (z >> 16); - } }; /** - * @brief Advances the global seed generator state and returns the new value + * @brief Overwrite the state of the global seed generator with state derived from the given seed + * @param seed Seed from which to derive the new state */ void ResetSeedGenerator(uint64_t seed); From 6cd230ea3a20ca4db924b88bd9405cf1681887bf Mon Sep 17 00:00:00 2001 From: staphen Date: Mon, 18 Mar 2024 22:03:00 -0400 Subject: [PATCH 3/5] Additional changes based on code review --- Source/engine/random.cpp | 28 +++++++++++- Source/engine/random.hpp | 98 ++++++++++++++++++++++++++++++---------- Source/multi.cpp | 16 ++----- Source/multi.h | 2 +- Source/stores.cpp | 2 - 5 files changed, 106 insertions(+), 40 deletions(-) diff --git a/Source/engine/random.cpp b/Source/engine/random.cpp index 2832017e854..31a8329c97a 100644 --- a/Source/engine/random.cpp +++ b/Source/engine/random.cpp @@ -1,7 +1,10 @@ #include "engine/random.hpp" +#include +#include #include #include +#include #include #include @@ -16,9 +19,30 @@ std::linear_congruential_engine diabloGenerator; /** Xoshiro pseudo-random number generator to provide less predictable seeds */ xoshiro128plusplus seedGenerator; -void ResetSeedGenerator(uint64_t seed) +uint32_t xoshiro128plusplus::rotl(const uint32_t x, int k) { - seedGenerator.seed(seed); + return std::rotl(x, k); +} + +uint64_t xoshiro128plusplus::timeSeed() +{ + auto now = std::chrono::system_clock::now(); + auto nano = std::chrono::nanoseconds(now.time_since_epoch()); + long long time = nano.count(); + SplitMix64 seedSequence { static_cast(time) }; + return seedSequence.next(); +} + +void xoshiro128plusplus::copy(state &dst, const state &src) +{ + memcpy(dst, src, sizeof(dst)); +} + +xoshiro128plusplus ReserveSeedSequence() +{ + xoshiro128plusplus reserved = seedGenerator; + seedGenerator.jump(); + return reserved; } uint32_t GenerateSeed() diff --git a/Source/engine/random.hpp b/Source/engine/random.hpp index 0b029c9a343..4802c751c80 100644 --- a/Source/engine/random.hpp +++ b/Source/engine/random.hpp @@ -8,7 +8,6 @@ #pragma once #include -#include #include #include #include @@ -205,13 +204,67 @@ class SplitMix64 { /** Adapted from https://prng.di.unimi.it/xoshiro128plusplus.c written in 2019 by David Blackman and Sebastiano Vigna */ class xoshiro128plusplus { -private: - uint32_t s[4]; - public: - xoshiro128plusplus() { seed(static_cast(1)); } - xoshiro128plusplus(uint32_t initialSeed) { seed(initialSeed); } + typedef uint32_t state[4]; + + xoshiro128plusplus() { seed(); } + xoshiro128plusplus(const state &s) { copy(this->s, s); } xoshiro128plusplus(uint64_t initialSeed) { seed(initialSeed); } + xoshiro128plusplus(uint32_t initialSeed) { seed(initialSeed); } + + uint32_t next() + { + const uint32_t result = rotl(s[0] + s[3], 7) + s[0]; + + const uint32_t t = s[1] << 9; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + + s[3] = rotl(s[3], 11); + + return result; + } + + /* This is the jump function for the generator. It is equivalent + to 2^64 calls to next(); it can be used to generate 2^64 + non-overlapping subsequences for parallel computations. */ + void jump() + { + static const uint32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; + + uint32_t s0 = 0; + uint32_t s1 = 0; + uint32_t s2 = 0; + uint32_t s3 = 0; + for (int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) + for (int b = 0; b < 32; b++) { + if (JUMP[i] & UINT32_C(1) << b) { + s0 ^= s[0]; + s1 ^= s[1]; + s2 ^= s[2]; + s3 ^= s[3]; + } + next(); + } + + s[0] = s0; + s[1] = s1; + s[2] = s2; + s[3] = s3; + } + + void save(state &s) const + { + copy(s, this->s); + } + +private: + state s; void seed(uint64_t value) { @@ -231,30 +284,27 @@ class xoshiro128plusplus { seedSequence.generate(s, s + 4); } - uint32_t next() + void seed() { - const uint32_t result = std::rotl(s[0] + s[3], 7) + s[0]; - - const uint32_t t = s[1] << 9; - - s[2] ^= s[0]; - s[3] ^= s[1]; - s[1] ^= s[2]; - s[0] ^= s[3]; - - s[2] ^= t; - - s[3] = std::rotl(s[3], 11); - - return result; + uint64_t ts = timeSeed(); + s[0] = static_cast(ts >> 32); + s[1] = static_cast(ts); + + static std::random_device rd; + std::uniform_int_distribution dist; + s[2] = dist(rd); + s[3] = dist(rd); } + + static uint32_t rotl(const uint32_t x, int k); + static uint64_t timeSeed(); + static void copy(state &dst, const state &src); }; /** - * @brief Overwrite the state of the global seed generator with state derived from the given seed - * @param seed Seed from which to derive the new state + * @brief Returns a copy of the global seed generator and fast-forwards the global seed generator to avoid collisions */ -void ResetSeedGenerator(uint64_t seed); +xoshiro128plusplus ReserveSeedSequence(); /** * @brief Advances the global seed generator state and returns the new value diff --git a/Source/multi.cpp b/Source/multi.cpp index 9403a88b085..8de5fac4141 100644 --- a/Source/multi.cpp +++ b/Source/multi.cpp @@ -4,7 +4,6 @@ * Implementation of functions for keeping multiplaye games in sync. */ -#include #include #include #include @@ -481,19 +480,14 @@ bool InitMulti(GameData *gameData) return true; } -uint64_t GenerateGameSeed() -{ - auto now = std::chrono::high_resolution_clock::now(); - auto duration = now.time_since_epoch(); - return static_cast(duration.count()); -} - } // namespace void InitGameInfo() { + xoshiro128plusplus gameGenerator = ReserveSeedSequence(); + gameGenerator.save(sgGameInitInfo.gameSeed); + sgGameInitInfo.size = sizeof(sgGameInitInfo); - sgGameInitInfo.gameSeed = GenerateGameSeed(); sgGameInitInfo.programid = GAME_ID; sgGameInitInfo.versionMajor = PROJECT_VERSION_MAJOR; sgGameInitInfo.versionMinor = PROJECT_VERSION_MINOR; @@ -794,11 +788,11 @@ bool NetInit(bool bSinglePlayer) NetClose(); gbSelectProvider = false; } - ResetSeedGenerator(sgGameInitInfo.gameSeed); + xoshiro128plusplus gameGenerator(sgGameInitInfo.gameSeed); gnTickDelay = 1000 / sgGameInitInfo.nTickRate; for (int i = 0; i < NUMLEVELS; i++) { - DungeonSeeds[i] = GenerateSeed(); + DungeonSeeds[i] = gameGenerator.next(); LevelSeeds[i] = std::nullopt; } PublicGame = DvlNet_IsPublicGame(); diff --git a/Source/multi.h b/Source/multi.h index 9d24a918e75..723d7db4f17 100644 --- a/Source/multi.h +++ b/Source/multi.h @@ -23,7 +23,7 @@ struct Player; struct GameData { int32_t size; /** Used to initialise the seed table for dungeon levels so players in multiplayer games generate the same layout */ - uint64_t gameSeed; + uint32_t gameSeed[4]; uint32_t programid; uint8_t versionMajor; uint8_t versionMinor; diff --git a/Source/stores.cpp b/Source/stores.cpp index baa6afb9a09..319ba350565 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -2134,8 +2134,6 @@ void SetupTownStores() if (myPlayer._pLvlVisited[i]) l = i; } - } else { - SetRndSeed(DungeonSeeds[currlevel] * SDL_GetTicks()); } l = std::clamp(l + 2, 6, 16); From 14a9261fe8fedc1aabcd68832b72615df0ff5208 Mon Sep 17 00:00:00 2001 From: staphen Date: Tue, 19 Mar 2024 09:23:12 -0400 Subject: [PATCH 4/5] More changes from code review --- Source/engine/random.cpp | 21 ++++++++++++++++----- Source/engine/random.hpp | 33 +++++++-------------------------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/Source/engine/random.cpp b/Source/engine/random.cpp index 31a8329c97a..4c36129cb29 100644 --- a/Source/engine/random.cpp +++ b/Source/engine/random.cpp @@ -19,18 +19,29 @@ std::linear_congruential_engine diabloGenerator; /** Xoshiro pseudo-random number generator to provide less predictable seeds */ xoshiro128plusplus seedGenerator; -uint32_t xoshiro128plusplus::rotl(const uint32_t x, int k) +uint32_t xoshiro128plusplus::next() { - return std::rotl(x, k); + const uint32_t result = std::rotl(s[0] + s[3], 7) + s[0]; + + const uint32_t t = s[1] << 9; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + + s[3] = std::rotl(s[3], 11); + + return result; } uint64_t xoshiro128plusplus::timeSeed() { auto now = std::chrono::system_clock::now(); auto nano = std::chrono::nanoseconds(now.time_since_epoch()); - long long time = nano.count(); - SplitMix64 seedSequence { static_cast(time) }; - return seedSequence.next(); + return static_cast(nano.count()); } void xoshiro128plusplus::copy(state &dst, const state &src) diff --git a/Source/engine/random.hpp b/Source/engine/random.hpp index 4802c751c80..e77745fea81 100644 --- a/Source/engine/random.hpp +++ b/Source/engine/random.hpp @@ -212,38 +212,22 @@ class xoshiro128plusplus { xoshiro128plusplus(uint64_t initialSeed) { seed(initialSeed); } xoshiro128plusplus(uint32_t initialSeed) { seed(initialSeed); } - uint32_t next() - { - const uint32_t result = rotl(s[0] + s[3], 7) + s[0]; - - const uint32_t t = s[1] << 9; - - s[2] ^= s[0]; - s[3] ^= s[1]; - s[1] ^= s[2]; - s[0] ^= s[3]; - - s[2] ^= t; - - s[3] = rotl(s[3], 11); - - return result; - } + uint32_t next(); /* This is the jump function for the generator. It is equivalent to 2^64 calls to next(); it can be used to generate 2^64 non-overlapping subsequences for parallel computations. */ void jump() { - static const uint32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; + static constexpr uint32_t JUMP[] = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b }; uint32_t s0 = 0; uint32_t s1 = 0; uint32_t s2 = 0; uint32_t s3 = 0; - for (int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) + for (const uint32_t entry : JUMP) for (int b = 0; b < 32; b++) { - if (JUMP[i] & UINT32_C(1) << b) { + if (entry & UINT32_C(1) << b) { s0 ^= s[0]; s1 ^= s[1]; s2 ^= s[2]; @@ -286,17 +270,14 @@ class xoshiro128plusplus { void seed() { - uint64_t ts = timeSeed(); - s[0] = static_cast(ts >> 32); - s[1] = static_cast(ts); + seed(timeSeed()); static std::random_device rd; std::uniform_int_distribution dist; - s[2] = dist(rd); - s[3] = dist(rd); + for (uint32_t &cell : s) + cell ^= dist(rd); } - static uint32_t rotl(const uint32_t x, int k); static uint64_t timeSeed(); static void copy(state &dst, const state &src); }; From 7d29e8363ca8cf0dd38ec449653c1a3009db9a1c Mon Sep 17 00:00:00 2001 From: staphen Date: Fri, 22 Mar 2024 18:36:26 -0400 Subject: [PATCH 5/5] Rearrange GameData struct to remain compatible with Discord bot --- Source/multi.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/multi.h b/Source/multi.h index 723d7db4f17..2f44dbed560 100644 --- a/Source/multi.h +++ b/Source/multi.h @@ -22,8 +22,7 @@ struct Player; struct GameData { int32_t size; - /** Used to initialise the seed table for dungeon levels so players in multiplayer games generate the same layout */ - uint32_t gameSeed[4]; + uint8_t reserved[4]; uint32_t programid; uint8_t versionMajor; uint8_t versionMinor; @@ -35,6 +34,8 @@ struct GameData { uint8_t bCowQuest; uint8_t bFriendlyFire; uint8_t fullQuests; + /** Used to initialise the seed table for dungeon levels so players in multiplayer games generate the same layout */ + uint32_t gameSeed[4]; }; /* @brief Contains info of running public game (for game list browsing) */