1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-23 15:52:55 +01:00

Refactor random engine

Introduce RotateEngine and Rct2Engine, FixedSeedSequence and Rct2Seed.
Adhere respectively to requirements `RandomNumberEngine` and `SeedSequence`.
Can be used with C++11 adaptors and distributions in <random>.
This commit is contained in:
Tom Lankhorst
2019-02-01 13:49:46 +01:00
parent 0233795add
commit 6a42a95495
8 changed files with 208 additions and 21 deletions

View File

@@ -0,0 +1,175 @@
/*****************************************************************************
* 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 "Numerics.hpp"
#include <array>
#include <cstdint>
#include <iostream>
#include <limits>
#include <random>
#include <type_traits>
namespace Random
{
using namespace Numerics;
/**
* FixedSeedSequence adheres to the _Named Requirement_ `SeedSequence`.
*/
template<size_t _N = 0> class FixedSeedSequence
{
public:
typedef uint32_t result_type;
static constexpr size_t N = _N;
static constexpr result_type default_seed = 0x1234567F;
explicit FixedSeedSequence()
{
std::fill(v.begin(), v.end(), default_seed);
}
template<
typename... T, typename std::enable_if<sizeof...(T) == N, int>::type = 0,
typename std::enable_if<(std::is_convertible<T, result_type>::value && ...), int>::type = 0>
explicit FixedSeedSequence(T... s)
: v{ static_cast<result_type>(s)... }
{
}
template<typename _It, typename = decltype(*std::declval<_It&>(), ++std::declval<_It&>(), void())>
explicit FixedSeedSequence(_It begin, _It end)
{
std::copy(begin, end, v.begin());
}
template<typename T>
explicit FixedSeedSequence(std::initializer_list<T> il)
: FixedSeedSequence(il.begin(), il.end())
{
}
template<typename _It> void generate(_It begin, _It end)
{
std::copy_n(v.begin(), std::min((size_t)(end - begin), N), begin);
}
constexpr size_t size() const
{
return N;
}
template<typename _It> constexpr void param(_It ob) const
{
std::copy(v.begin(), v.end(), ob);
}
protected:
std::array<result_type, N> v;
};
typedef FixedSeedSequence<2> Rct2Seed;
/**
* RotateEngine adheres to the _Named Requirement_ `RandomNumberEngine`
* https://en.cppreference.com/w/cpp/named_req/RandomNumberEngine
*/
template<typename UIntType, UIntType __x, size_t __r1, size_t __r2> class RotateEngine
{
static_assert(std::is_unsigned<UIntType>::value, "Type must be unsigned integral.");
public:
typedef UIntType result_type;
static constexpr result_type x = __x;
static constexpr size_t r1 = __r1;
static constexpr size_t r2 = __r2;
static constexpr result_type default_seed = 1;
static constexpr result_type min()
{
return std::numeric_limits<result_type>::min();
}
static constexpr result_type max()
{
return std::numeric_limits<result_type>::max();
}
explicit RotateEngine(result_type seed_value = default_seed)
{
seed(seed_value);
}
RotateEngine(RotateEngine& r)
{
s0 = r.s0;
s1 = r.s1;
}
template<typename Sseq, typename = typename std::enable_if<!std::is_same<Sseq, RotateEngine>::value>::type>
explicit RotateEngine(Sseq& seed_seq)
{
seed(seed_seq);
}
void seed(result_type s = default_seed)
{
s0 = s;
}
template<typename Sseq> typename std::enable_if<std::is_class<Sseq>::value, void>::type seed(Sseq& seed_seq)
{
std::array<result_type, 2> 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;
}
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;
}
protected:
result_type s0;
result_type s1;
};
typedef RotateEngine<uint32_t, 0x1234567F, 7, 3> Rct2Engine;
} // namespace Random

View File

@@ -960,7 +960,7 @@ bool Network::CheckSRAND(uint32_t tick, uint32_t srand0)
void Network::CheckDesynchronizaton() void Network::CheckDesynchronizaton()
{ {
// Check synchronisation // Check synchronisation
if (GetMode() == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, gScenarioSrand0)) if (GetMode() == NETWORK_MODE_CLIENT && !_desynchronised && !CheckSRAND(gCurrentTicks, scenario_rand_seed().first))
{ {
_desynchronised = true; _desynchronised = true;
@@ -1596,7 +1596,7 @@ void Network::Server_Send_TICK()
last_tick_sent_time = ticks; last_tick_sent_time = ticks;
std::unique_ptr<NetworkPacket> packet(NetworkPacket::Allocate()); std::unique_ptr<NetworkPacket> packet(NetworkPacket::Allocate());
*packet << (uint32_t)NETWORK_COMMAND_TICK << gCurrentTicks << gScenarioSrand0; *packet << (uint32_t)NETWORK_COMMAND_TICK << gCurrentTicks << scenario_rand_seed().first;
uint32_t flags = 0; uint32_t flags = 0;
// Simple counter which limits how often a sprite checksum gets sent. // 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, // This can get somewhat expensive, so we don't want to push it every tick in release,

View File

@@ -2451,8 +2451,7 @@ private:
{ {
// Date and srand // Date and srand
gScenarioTicks = _s4.ticks; gScenarioTicks = _s4.ticks;
gScenarioSrand0 = _s4.random_a; scenario_rand_seed(_s4.random_a, _s4.random_b);
gScenarioSrand1 = _s4.random_b;
gDateMonthsElapsed = _s4.month; gDateMonthsElapsed = _s4.month;
gDateMonthTicks = _s4.day; gDateMonthTicks = _s4.day;

View File

@@ -188,8 +188,8 @@ void S6Exporter::Export()
_s6.elapsed_months = gDateMonthsElapsed; _s6.elapsed_months = gDateMonthsElapsed;
_s6.current_day = gDateMonthTicks; _s6.current_day = gDateMonthTicks;
_s6.scenario_ticks = gScenarioTicks; _s6.scenario_ticks = gScenarioTicks;
_s6.scenario_srand_0 = gScenarioSrand0;
_s6.scenario_srand_1 = gScenarioSrand1; std::tie(_s6.scenario_srand_0, _s6.scenario_srand_1) = scenario_rand_seed();
std::memcpy(_s6.tile_elements, gTileElements, sizeof(_s6.tile_elements)); std::memcpy(_s6.tile_elements, gTileElements, sizeof(_s6.tile_elements));

View File

@@ -17,6 +17,7 @@
#include "../core/FileStream.hpp" #include "../core/FileStream.hpp"
#include "../core/IStream.hpp" #include "../core/IStream.hpp"
#include "../core/Path.hpp" #include "../core/Path.hpp"
#include "../core/Random.hpp"
#include "../core/String.hpp" #include "../core/String.hpp"
#include "../interface/Viewport.h" #include "../interface/Viewport.h"
#include "../localisation/Date.h" #include "../localisation/Date.h"
@@ -207,8 +208,8 @@ public:
gDateMonthsElapsed = _s6.elapsed_months; gDateMonthsElapsed = _s6.elapsed_months;
gDateMonthTicks = _s6.current_day; gDateMonthTicks = _s6.current_day;
gScenarioTicks = _s6.scenario_ticks; 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(); ImportTileElements();

View File

@@ -18,6 +18,7 @@
#include "../ParkImporter.h" #include "../ParkImporter.h"
#include "../audio/audio.h" #include "../audio/audio.h"
#include "../config/Config.h" #include "../config/Config.h"
#include "../core/Random.hpp"
#include "../interface/Viewport.h" #include "../interface/Viewport.h"
#include "../localisation/Date.h" #include "../localisation/Date.h"
#include "../localisation/Localisation.h" #include "../localisation/Localisation.h"
@@ -48,6 +49,7 @@
#include "ScenarioSources.h" #include "ScenarioSources.h"
#include <algorithm> #include <algorithm>
#include <sstream>
const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT] = { const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT] = {
STR_BEGINNER_PARKS, STR_CHALLENGING_PARKS, STR_EXPERT_PARKS, STR_REAL_PARKS, STR_OTHER_PARKS, STR_BEGINNER_PARKS, STR_CHALLENGING_PARKS, STR_EXPERT_PARKS, STR_REAL_PARKS, STR_OTHER_PARKS,
@@ -66,8 +68,7 @@ uint16_t gSavedAge;
uint32_t gLastAutoSaveUpdate = 0; uint32_t gLastAutoSaveUpdate = 0;
uint32_t gScenarioTicks; uint32_t gScenarioTicks;
uint32_t gScenarioSrand0; static Random::Rct2Engine rnd;
uint32_t gScenarioSrand1;
uint8_t gScenarioObjectiveType; uint8_t gScenarioObjectiveType;
uint8_t gScenarioObjectiveYear; uint8_t gScenarioObjectiveYear;
@@ -90,8 +91,8 @@ void scenario_begin()
game_load_init(); game_load_init();
// Set the scenario pseudo-random seeds // Set the scenario pseudo-random seeds
gScenarioSrand0 ^= platform_get_ticks(); Random::Rct2Seed s{ 0x1234567F ^ platform_get_ticks(), 0x789FABCD ^ platform_get_ticks() };
gScenarioSrand1 ^= platform_get_ticks(); rnd.seed(s);
gParkFlags &= ~PARK_FLAGS_NO_MONEY; gParkFlags &= ~PARK_FLAGS_NO_MONEY;
if (gParkFlags & PARK_FLAGS_NO_MONEY_SCENARIO) if (gParkFlags & PARK_FLAGS_NO_MONEY_SCENARIO)
@@ -476,6 +477,22 @@ static int32_t scenario_create_ducks()
return 1; return 1;
} }
std::pair<uint32_t, uint32_t> scenario_rand_seed()
{
std::stringstream ss;
ss << rnd;
uint32_t s0, s1;
ss >> s0;
ss >> s1;
return { s0, s1 };
};
void scenario_rand_seed(uint32_t s0, uint32_t s1)
{
Random::Rct2Seed s{ s0, s1 };
rnd.seed(s);
}
/** /**
* *
* rct2: 0x006E37D2 * rct2: 0x006E37D2
@@ -491,10 +508,6 @@ static const char* realm = "LC";
uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data) uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data)
#endif #endif
{ {
uint32_t originalSrand0 = gScenarioSrand0;
gScenarioSrand0 += ror32(gScenarioSrand1 ^ 0x1234567F, 7);
gScenarioSrand1 = ror32(originalSrand0, 3);
#ifdef DEBUG_DESYNC #ifdef DEBUG_DESYNC
if (fp == nullptr) if (fp == nullptr)
{ {
@@ -527,7 +540,7 @@ uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_
} }
#endif #endif
return gScenarioSrand1; return rnd();
} }
#ifdef DEBUG_DESYNC #ifdef DEBUG_DESYNC

View File

@@ -366,8 +366,6 @@ enum
extern const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT]; extern const rct_string_id ScenarioCategoryStringIds[SCENARIO_CATEGORY_COUNT];
extern uint32_t gScenarioTicks; extern uint32_t gScenarioTicks;
extern uint32_t gScenarioSrand0;
extern uint32_t gScenarioSrand1;
extern uint8_t gScenarioObjectiveType; extern uint8_t gScenarioObjectiveType;
extern uint8_t gScenarioObjectiveYear; extern uint8_t gScenarioObjectiveYear;
@@ -394,6 +392,8 @@ void load_from_sc6(const char* path);
void scenario_begin(); void scenario_begin();
void scenario_update(); void scenario_update();
std::pair<uint32_t, uint32_t> scenario_rand_seed();
void scenario_rand_seed(uint32_t s0, uint32_t s1);
#ifdef DEBUG_DESYNC #ifdef DEBUG_DESYNC
uint32_t dbg_scenario_rand(const char* file, const char* function, const uint32_t line, const void* data); 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() dbg_scenario_rand(__FILE__, __FUNCTION__, __LINE__, NULL)

View File

@@ -41,8 +41,7 @@ public:
void SetUp() override void SetUp() override
{ {
// Use a consistent random seed in every test // Use a consistent random seed in every test
gScenarioSrand0 = 0x12345678; scenario_rand_seed(0x12345678, 0x87654321);
gScenarioSrand1 = 0x87654321;
} }
static void TearDownTestCase() static void TearDownTestCase()