mirror of https://github.com/OpenRCT2/OpenRCT2.git
Merge pull request #8661 from tomlankhorst/refactor-random
Refactor random engine
This commit is contained in:
commit
604da7ce01
|
@ -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
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue