diff --git a/src/openrct2/core/Meta.hpp b/src/openrct2/core/Meta.hpp new file mode 100644 index 0000000000..b63d140f17 --- /dev/null +++ b/src/openrct2/core/Meta.hpp @@ -0,0 +1,30 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include + +namespace Meta +{ + /** + * Meta function for checking that all Conditions are true types. + */ + template struct all : std::true_type + { + }; + + template + struct all<_TCondition, _TConditions...> : std::conditional<_TCondition::value, all<_TConditions...>, std::false_type>::type + { + }; + + template using all_convertible = all...>; + +} // namespace Meta diff --git a/src/openrct2/core/Random.hpp b/src/openrct2/core/Random.hpp new file mode 100644 index 0000000000..cdca9daa73 --- /dev/null +++ b/src/openrct2/core/Random.hpp @@ -0,0 +1,195 @@ +/***************************************************************************** + * Copyright (c) 2014-2019 OpenRCT2 developers + * + * For a complete list of all authors, please refer to contributors.md + * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is licensed under the GNU General Public License version 3. + *****************************************************************************/ + +#pragma once + +#include "Meta.hpp" +#include "Numerics.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace Random +{ + using namespace Numerics; + + /** + * FixedSeedSequence adheres to the _Named Requirement_ `SeedSequence`. + */ + template class FixedSeedSequence + { + public: + using result_type = uint32_t; + + static constexpr size_t N = _TNum; + static constexpr result_type default_seed = 0x1234567F; + + explicit FixedSeedSequence() + { + std::fill(v.begin(), v.end(), default_seed); + } + + template< + typename... _TTypes, typename std::enable_if::type = 0, + typename std::enable_if::value, int>::type = 0> + explicit FixedSeedSequence(_TTypes... s) + : v{ static_cast(s)... } + { + } + + template(), ++std::declval<_TIt&>(), void())> + explicit FixedSeedSequence(_TIt begin, _TIt end) + { + std::copy(begin, end, v.begin()); + } + + template + explicit FixedSeedSequence(std::initializer_list<_TType> il) + : FixedSeedSequence(il.begin(), il.end()) + { + } + + template void generate(_TIt begin, _TIt end) const + { + std::copy_n(v.begin(), std::min((size_t)(end - begin), N), begin); + } + + constexpr size_t size() const + { + return N; + } + + template constexpr void param(_TIt ob) const + { + std::copy(v.begin(), v.end(), ob); + } + + protected: + std::array v; + }; + + template struct RotateEngineState + { + using value_type = _TUIntType; + + value_type s0; + value_type s1; + }; + + /** + * RotateEngine adheres to the _Named Requirement_ `RandomNumberEngine` + * https://en.cppreference.com/w/cpp/named_req/RandomNumberEngine + */ + template + class RotateEngine : protected RotateEngineState<_TUIntType> + { + static_assert(std::is_unsigned<_TUIntType>::value, "Type must be unsigned integral."); + + using RotateEngineState<_TUIntType>::s0; + using RotateEngineState<_TUIntType>::s1; + + public: + using result_type = _TUIntType; + using state_type = RotateEngineState<_TUIntType>; + + static constexpr result_type x = _TX; + static constexpr size_t r1 = _TR1; + static constexpr size_t r2 = _TR2; + static constexpr result_type default_seed = 1; + + static constexpr result_type min() + { + return std::numeric_limits::min(); + } + + static constexpr result_type max() + { + return std::numeric_limits::max(); + } + + explicit RotateEngine(result_type seed_value = default_seed) + { + seed(seed_value); + } + + RotateEngine(RotateEngine& r) + { + s0 = r.s0; + s1 = r.s1; + } + + template::value>::type> + explicit RotateEngine(_TSseq& seed_seq) + { + seed(seed_seq); + } + + void seed(result_type s = default_seed) + { + s0 = s; + s1 = s; + } + + template typename std::enable_if::value, void>::type seed(_TSseq& seed_seq) + { + std::array s; + seed_seq.generate(s.begin(), s.end()); + s0 = s[0]; + s1 = s[1]; + } + + void discard(size_t n) + { + for (; n > 0; n--) + (*this)(); + } + result_type operator()() + { + auto s0z = s0; + s0 += ror(s1 ^ x, r1); + s1 = ror(s0z, r2); + return s1; + } + + const state_type& state() const + { + return *this; + } + + friend bool operator==(const RotateEngine& lhs, const RotateEngine& rhs) + { + return lhs.s0 == rhs.s0 && lhs.s1 == rhs.s1; + } + + friend std::ostream& operator<<(std::ostream& os, const RotateEngine& e) + { + os << e.s0 << ' ' << e.s1; + return os; + } + + friend std::istream& operator>>(std::istream& is, RotateEngine& e) + { + is >> e.s0; + is >> e.s1; + return is; + } + }; + + namespace Rct2 + { + using Engine = RotateEngine; + using Seed = FixedSeedSequence<2>; + using State = Engine::state_type; + } // namespace Rct2 +} // namespace Random diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index bee3d003a8..b7ba3c2840 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -960,7 +960,7 @@ bool Network::CheckSRAND(uint32_t tick, uint32_t srand0) void Network::CheckDesynchronizaton() { // Check synchronisation - if (GetMode() == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, gScenarioSrand0)) + if (GetMode() == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, scenario_rand_state().s0)) { _desynchronised = true; @@ -1596,7 +1596,7 @@ void Network::Server_Send_TICK() last_tick_sent_time = ticks; std::unique_ptr packet(NetworkPacket::Allocate()); - *packet << (uint32_t)NETWORK_COMMAND_TICK << gCurrentTicks << gScenarioSrand0; + *packet << (uint32_t)NETWORK_COMMAND_TICK << gCurrentTicks << scenario_rand_state().s0; uint32_t flags = 0; // Simple counter which limits how often a sprite checksum gets sent. // This can get somewhat expensive, so we don't want to push it every tick in release, diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index f45e07ef1f..470f3e9922 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -2451,8 +2451,7 @@ private: { // Date and srand gScenarioTicks = _s4.ticks; - gScenarioSrand0 = _s4.random_a; - gScenarioSrand1 = _s4.random_b; + scenario_rand_seed(_s4.random_a, _s4.random_b); gDateMonthsElapsed = _s4.month; gDateMonthTicks = _s4.day; diff --git a/src/openrct2/rct2/S6Exporter.cpp b/src/openrct2/rct2/S6Exporter.cpp index 25fa6e70be..a44f43f279 100644 --- a/src/openrct2/rct2/S6Exporter.cpp +++ b/src/openrct2/rct2/S6Exporter.cpp @@ -188,8 +188,10 @@ void S6Exporter::Export() _s6.elapsed_months = gDateMonthsElapsed; _s6.current_day = gDateMonthTicks; _s6.scenario_ticks = gScenarioTicks; - _s6.scenario_srand_0 = gScenarioSrand0; - _s6.scenario_srand_1 = gScenarioSrand1; + + auto state = scenario_rand_state(); + _s6.scenario_srand_0 = state.s0; + _s6.scenario_srand_1 = state.s1; std::memcpy(_s6.tile_elements, gTileElements, sizeof(_s6.tile_elements)); diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index 39dcef6c6c..86bb9ebe8f 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -17,6 +17,7 @@ #include "../core/FileStream.hpp" #include "../core/IStream.hpp" #include "../core/Path.hpp" +#include "../core/Random.hpp" #include "../core/String.hpp" #include "../interface/Viewport.h" #include "../localisation/Date.h" @@ -207,8 +208,8 @@ public: gDateMonthsElapsed = _s6.elapsed_months; gDateMonthTicks = _s6.current_day; gScenarioTicks = _s6.scenario_ticks; - gScenarioSrand0 = _s6.scenario_srand_0; - gScenarioSrand1 = _s6.scenario_srand_1; + + scenario_rand_seed(_s6.scenario_srand_0, _s6.scenario_srand_1); ImportTileElements(); diff --git a/src/openrct2/scenario/Scenario.cpp b/src/openrct2/scenario/Scenario.cpp index eaa6bf5862..77fcdbbac7 100644 --- a/src/openrct2/scenario/Scenario.cpp +++ b/src/openrct2/scenario/Scenario.cpp @@ -18,6 +18,7 @@ #include "../ParkImporter.h" #include "../audio/audio.h" #include "../config/Config.h" +#include "../core/Random.hpp" #include "../interface/Viewport.h" #include "../localisation/Date.h" #include "../localisation/Localisation.h" @@ -66,8 +67,7 @@ uint16_t gSavedAge; uint32_t gLastAutoSaveUpdate = 0; uint32_t gScenarioTicks; -uint32_t gScenarioSrand0; -uint32_t gScenarioSrand1; +random_engine_t gScenarioRand; uint8_t gScenarioObjectiveType; uint8_t gScenarioObjectiveYear; @@ -90,8 +90,8 @@ void scenario_begin() game_load_init(); // Set the scenario pseudo-random seeds - gScenarioSrand0 ^= platform_get_ticks(); - gScenarioSrand1 ^= platform_get_ticks(); + Random::Rct2::Seed s{ 0x1234567F ^ platform_get_ticks(), 0x789FABCD ^ platform_get_ticks() }; + gScenarioRand.seed(s); gParkFlags &= ~PARK_FLAGS_NO_MONEY; if (gParkFlags & PARK_FLAGS_NO_MONEY_SCENARIO) @@ -476,6 +476,17 @@ static int32_t scenario_create_ducks() return 1; } +const random_engine_t::state_type& scenario_rand_state() +{ + return gScenarioRand.state(); +}; + +void scenario_rand_seed(random_engine_t::result_type s0, random_engine_t::result_type s1) +{ + Random::Rct2::Seed s{ s0, s1 }; + gScenarioRand.seed(s); +} + /** * * rct2: 0x006E37D2 @@ -483,7 +494,7 @@ static int32_t scenario_create_ducks() * @return eax */ #ifndef DEBUG_DESYNC -uint32_t scenario_rand() +random_engine_t::result_type scenario_rand() #else static FILE* fp = nullptr; static const char* realm = "LC"; @@ -491,10 +502,6 @@ static const char* realm = "LC"; uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data) #endif { - uint32_t originalSrand0 = gScenarioSrand0; - gScenarioSrand0 += ror32(gScenarioSrand1 ^ 0x1234567F, 7); - gScenarioSrand1 = ror32(originalSrand0, 3); - #ifdef DEBUG_DESYNC if (fp == nullptr) { @@ -527,7 +534,7 @@ uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_ } #endif - return gScenarioSrand1; + return gScenarioRand(); } #ifdef DEBUG_DESYNC diff --git a/src/openrct2/scenario/Scenario.h b/src/openrct2/scenario/Scenario.h index 0e5966794e..3b2a68188a 100644 --- a/src/openrct2/scenario/Scenario.h +++ b/src/openrct2/scenario/Scenario.h @@ -11,6 +11,7 @@ #define _SCENARIO_H_ #include "../common.h" +#include "../core/Random.hpp" #include "../management/Finance.h" #include "../management/Research.h" #include "../object/Object.h" @@ -23,6 +24,8 @@ #include "../world/MapAnimation.h" #include "../world/Sprite.h" +using random_engine_t = Random::Rct2::Engine; + struct ParkLoadResult; #pragma pack(push, 1) @@ -366,8 +369,7 @@ enum extern const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT]; extern uint32_t gScenarioTicks; -extern uint32_t gScenarioSrand0; -extern uint32_t gScenarioSrand1; +extern random_engine_t gScenarioRand; extern uint8_t gScenarioObjectiveType; extern uint8_t gScenarioObjectiveYear; @@ -394,13 +396,15 @@ void load_from_sc6(const char* path); void scenario_begin(); void scenario_update(); +const random_engine_t::state_type& scenario_rand_state(); +void scenario_rand_seed(random_engine_t::result_type s0, random_engine_t::result_type s1); #ifdef DEBUG_DESYNC uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data); # define scenario_rand() dbg_scenario_rand(__FILE__, __FUNCTION__, __LINE__, NULL) # define scenario_rand_data(data) dbg_scenario_rand(__FILE__, __FUNCTION__, __LINE__, data) void dbg_report_desync(uint32_t tick, uint32_t srand0, uint32_t server_srand0, const char* clientHash, const char* serverHash); #else -uint32_t scenario_rand(); +random_engine_t::result_type scenario_rand(); #endif uint32_t scenario_rand_max(uint32_t max); diff --git a/test/tests/Pathfinding.cpp b/test/tests/Pathfinding.cpp index 43a99654df..2bcca46322 100644 --- a/test/tests/Pathfinding.cpp +++ b/test/tests/Pathfinding.cpp @@ -41,8 +41,7 @@ public: void SetUp() override { // Use a consistent random seed in every test - gScenarioSrand0 = 0x12345678; - gScenarioSrand1 = 0x87654321; + scenario_rand_seed(0x12345678, 0x87654321); } static void TearDownTestCase()