diff --git a/Source/engine/random.cpp b/Source/engine/random.cpp index 2832017e854..625192a1d3b 100644 --- a/Source/engine/random.cpp +++ b/Source/engine/random.cpp @@ -1,5 +1,7 @@ #include "engine/random.hpp" +#include +#include #include #include #include @@ -16,9 +18,25 @@ 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(); +} + +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..940e7460cf7 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) { memcpy(this->s, s, sizeof(this->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 + { + memcpy(s, this->s, sizeof(s)); + } + +private: + state s; void seed(uint64_t value) { @@ -231,30 +284,26 @@ 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(); }; /** - * @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..ec23e9676a8 100644 --- a/Source/multi.cpp +++ b/Source/multi.cpp @@ -481,19 +481,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 +789,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 0cdd99e44bb..65c24d11f1c 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);