diff --git a/application/application.cpp b/application/application.cpp index e5d0b6ba..a6b88ae8 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -31,6 +31,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/dictionary.h" #include "version_info.h" #include +#include #include "utilities/translation.h" #if WITH_DISCORD_RPC @@ -320,9 +321,13 @@ int eu07_application::init(int Argc, char *Argv[]) return result; } + // the run's seed, settled once and kept in Globals: the engines come out of it. + // narrowed on purpose - mt19937 and seed_of() both work on 32 bits, and that is + // the seed somebody can write down and pass on if (!Global.random_seed) - Global.random_seed = std::random_device{}(); + Global.random_seed = static_cast(true_random_seed()); Global.random_engine.seed(Global.random_seed); + Global.local_random_engine.seed(Global.random_seed); // configure the OS console according to Globals.ShowSystemConsole. // must run AFTER init_settings (so the ini-loaded value is honoured) and @@ -1140,10 +1145,21 @@ int eu07_application::init_settings(int Argc, char *Argv[]) Global.local_start_vehicle = ToLower(Argv[++i]); } } + else if (token == "-seed") + { + // a number is the seed; anything else is a word that stands for one. said in the + // log as it was given, because that is what gets passed on to somebody else + if (i + 1 < Argc) + { + Global.random_seed = seed_of(Argv[++i]); + WriteLog(std::format("seed given on the command line: {} -> {}", Argv[i], Global.random_seed)); + } + } else { std::cout << "usage: " << std::string(Argv[0]) << " [-s sceneryfilepath]" - << " [-v vehiclename]" << std::endl; + << " [-v vehiclename]" + << " [-seed number|word]" << std::endl; return -1; } } @@ -1421,7 +1437,7 @@ int eu07_application::init_data() int eu07_application::init_modes() { - Global.local_random_engine.seed(std::random_device{}()); + Global.local_random_engine.seed(static_cast(true_random_seed())); if ((!Global.network_servers.empty() || Global.network_client) && Global.SceneryFile.empty()) { diff --git a/application/editoruipanels.cpp b/application/editoruipanels.cpp index f15f791e..cbfe0ae7 100644 --- a/application/editoruipanels.cpp +++ b/application/editoruipanels.cpp @@ -403,10 +403,9 @@ std::string *brush_object_list::GetRandomObject() if (Objects.empty()) return ∅ - static std::mt19937 rng{std::random_device{}()}; std::uniform_int_distribution dist(0, Objects.size() - 1); - return &Objects[dist(rng)]; + return &Objects[dist(Global.local_random_engine)]; } void brush_object_list::render() diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 0ab064b4..f45b6b3e 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -726,7 +726,9 @@ bool global_settings::ConfigParseSimulation(cParser& Parser, const std::string& if (token == "randomseed") { - ParseOne(Parser, Global.random_seed, 1); + std::string text; + ParseOne(Parser, text); + Global.random_seed = seed_of(text); return true; } diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index 156d16cc..c1d0b73d 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -15,6 +15,7 @@ Copyright (C) 2007-2014 Maciej Cierniak // //#include //#include +#include #include //#ifndef WIN32 //#include @@ -28,9 +29,9 @@ Copyright (C) 2007-2014 Maciej Cierniak #include "utilities/Globals.h" #include "utilities/parser.h" #include "utilities/U8.h" +#include "utilities/Logs.h" -//#include "utilities/Logs.h" // TODO: This shouldn't be in Globals? bool DebugModeFlag = false; @@ -137,15 +138,51 @@ int Random(int min, int max) return dist(Global.random_engine); } +std::uint64_t true_random_seed() +{ + try + { + std::random_device rd; + if (rd.entropy() > 0.0) + { + // NOTE: both halves have to be 64 bit wide before the shift - shifting a std::uint32_t + // by 32 is undefined behaviour, and drops the high half of the seed + std::uint64_t const high{rd()}; + std::uint64_t const low{rd()}; + return (high << 32) | low; + } + } + catch (std::exception const &Error) + { + // std::random_device throws when the machine has no usable hardware entropy source; + // the clock below stands in for it, which is worth saying out loud since it is a + // weaker seed than the caller asked for + WriteLog(std::format("random: no hardware entropy source ({}), seeding from the clock instead", Error.what())); + } + return static_cast(std::chrono::high_resolution_clock::now().time_since_epoch().count()); +} + +std::uint32_t seed_of(std::string const &Text) +{ + if (std::uint32_t number{}; std::from_chars(Text.data(), Text.data() + Text.size(), number).ec == std::errc{}) + { + return number; + } + std::uint32_t hash{}; + for (auto const character : Text) + { + hash = hash * 31u + static_cast(static_cast(character)); + } + return hash; +} + std::string generate_uuid_v4() { - std::random_device rd; - std::mt19937 gen(rd()); std::uniform_int_distribution dist(0, 255); std::array bytes; for (auto &b : bytes) - b = static_cast(dist(gen)); + b = static_cast(dist(Global.random_engine)); // UUID v4 (RFC 4122) bytes[6] = bytes[6] & 0x0F | 0x40; diff --git a/utilities/utilities.h b/utilities/utilities.h index bc38b125..70cf1c07 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -62,6 +62,18 @@ inline long Round(double const f) double Random(double a, double b); int Random(int min, int max); + +/// @author lxvia +/// @brief Returns a random seed from the system's entropy source, if available. Otherwise, falls back to a clock-based seed. +/// @return A random seed as 64 bit unsigned integer +std::uint64_t true_random_seed(); + +// The seed a word stands for. A number is taken as it is written; anything else is +// hashed the way Java's String.hashCode does it, so the same word gives the same +// world on every platform and every build - which is the whole point of telling +// somebody a seed. +std::uint32_t seed_of(std::string const &Text); + std::string generate_uuid_v4(); double LocalRandom(double a, double b); diff --git a/utilities/uuid.hpp b/utilities/uuid.hpp index d94fd0cc..5eeb89f4 100644 --- a/utilities/uuid.hpp +++ b/utilities/uuid.hpp @@ -6,6 +6,8 @@ #include #include #include +#include "utilities/utilities.h" +#include "utilities/Globals.h" class UID { public: @@ -13,10 +15,9 @@ public: static UID random() { - static thread_local std::mt19937_64 gen(std::random_device{}()); UID u; - uint64_t a = gen(); - uint64_t b = gen(); + uint64_t a = Global.random_engine(); + uint64_t b = Global.random_engine(); for (int i = 0; i < 8; ++i) u.bytes[i] = uint8_t(a >> (i * 8) & 0xFF); for (int i = 0; i < 8; ++i) u.bytes[8 + i] = uint8_t(b >> (i * 8) & 0xFF);