Merge pull request #8661 from tomlankhorst/refactor-random

Refactor random engine
This commit is contained in:
Michał Janiszewski 2019-02-03 23:29:10 +01:00 committed by GitHub
commit 604da7ce01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 260 additions and 23 deletions

View File

@ -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 <type_traits>
namespace Meta
{
/**
* Meta function for checking that all Conditions are true types.
*/
template<typename... _TConditions> struct all : std::true_type
{
};
template<typename _TCondition, typename... _TConditions>
struct all<_TCondition, _TConditions...> : std::conditional<_TCondition::value, all<_TConditions...>, std::false_type>::type
{
};
template<typename _TType, typename... _TTypes> using all_convertible = all<std::is_convertible<_TTypes, _TType>...>;
} // namespace Meta

View File

@ -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 <algorithm>
#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 _TNum = 0> 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<sizeof...(_TTypes) == N, int>::type = 0,
typename std::enable_if<Meta::all_convertible<result_type, _TTypes...>::value, int>::type = 0>
explicit FixedSeedSequence(_TTypes... s)
: v{ static_cast<result_type>(s)... }
{
}
template<typename _TIt, typename = decltype(*std::declval<_TIt&>(), ++std::declval<_TIt&>(), void())>
explicit FixedSeedSequence(_TIt begin, _TIt end)
{
std::copy(begin, end, v.begin());
}
template<typename _TType>
explicit FixedSeedSequence(std::initializer_list<_TType> il)
: FixedSeedSequence(il.begin(), il.end())
{
}
template<typename _TIt> 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<typename _TIt> constexpr void param(_TIt ob) const
{
std::copy(v.begin(), v.end(), ob);
}
protected:
std::array<result_type, N> v;
};
template<typename _TUIntType> 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<typename _TUIntType, _TUIntType _TX, size_t _TR1, size_t _TR2>
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<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 _TSseq, typename = typename std::enable_if<!std::is_same<_TSseq, RotateEngine>::value>::type>
explicit RotateEngine(_TSseq& seed_seq)
{
seed(seed_seq);
}
void seed(result_type s = default_seed)
{
s0 = s;
s1 = s;
}
template<typename _TSseq> typename std::enable_if<std::is_class<_TSseq>::value, void>::type seed(_TSseq& 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;
}
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<uint32_t, 0x1234567F, 7, 3>;
using Seed = FixedSeedSequence<2>;
using State = Engine::state_type;
} // namespace Rct2
} // namespace Random

View File

@ -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<NetworkPacket> 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,

View File

@ -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;

View File

@ -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));

View File

@ -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();

View File

@ -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

View File

@ -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);

View File

@ -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()