Skip to content

Commit fbe0658

Browse files
fix: harden deterministic replay rng handling
Assisted-by: pi:gpt-5.5 Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
1 parent 028a4eb commit fbe0658

9 files changed

Lines changed: 656 additions & 524 deletions

File tree

src/random/rng.cpp

Lines changed: 121 additions & 173 deletions
Large diffs are not rendered by default.

src/random/rng.h

Lines changed: 93 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#pragma once
22

3+
#include "coordinates.h"
4+
#include "units_angle.h"
5+
#include "units_probability.h"
6+
37
#include <array>
48
#include <cstdint>
59
#include <functional>
@@ -8,21 +12,16 @@
812
#include <random>
913
#include <type_traits>
1014

11-
#include "coordinates.h"
12-
#include "units_angle.h"
13-
#include "units_probability.h"
14-
1515
class map;
1616
class time_duration;
17-
template<typename Tripoint>
18-
class tripoint_range;
17+
template <typename Tripoint> class tripoint_range;
1918
struct tripoint;
2019

2120
// All PRNG functions use an engine, see the C++11 <random> header
2221
// By default, that engine is seeded by time on first call to such a function.
2322
// If this function is called with a non-zero seed then the engine will be
2423
// seeded (or re-seeded) with the given seed.
25-
void rng_set_engine_seed( unsigned int seed );
24+
auto rng_set_engine_seed(unsigned int seed) -> void;
2625

2726
using cata_default_random_engine = std::minstd_rand0;
2827

@@ -31,34 +30,34 @@ struct rng_deterministic_key {
3130
std::uint64_t id = 0;
3231
};
3332

34-
class rng_deterministic_task_scope
35-
{
36-
public:
37-
explicit rng_deterministic_task_scope( unsigned int seed );
38-
~rng_deterministic_task_scope();
39-
40-
rng_deterministic_task_scope( const rng_deterministic_task_scope & ) = delete;
41-
rng_deterministic_task_scope &operator=( const rng_deterministic_task_scope & ) = delete;
42-
43-
private:
44-
bool old_has_task_engine_ = false;
45-
cata_default_random_engine old_task_engine_;
46-
bool old_has_task_context_ = false;
47-
std::uint64_t old_task_context_seed_ = 0;
48-
std::uint64_t old_task_child_counter_ = 0;
33+
class rng_deterministic_task_scope {
34+
public:
35+
explicit rng_deterministic_task_scope(unsigned int seed);
36+
~rng_deterministic_task_scope();
37+
38+
rng_deterministic_task_scope(const rng_deterministic_task_scope&) = delete;
39+
auto operator=(const rng_deterministic_task_scope&)
40+
-> rng_deterministic_task_scope& = delete; // *NOPAD*
41+
42+
private:
43+
bool old_has_task_engine_ = false;
44+
cata_default_random_engine old_task_engine_;
45+
bool old_has_task_context_ = false;
46+
std::uint64_t old_task_context_seed_ = 0;
47+
std::uint64_t old_task_child_counter_ = 0;
4948
};
5049

51-
cata_default_random_engine &rng_get_engine();
52-
unsigned int rng_bits();
53-
auto rng_set_deterministic_seed( unsigned int seed ) -> void;
50+
auto rng_get_engine() -> cata_default_random_engine&; // *NOPAD*
51+
auto rng_bits() -> unsigned int;
52+
auto rng_set_deterministic_seed(unsigned int seed) -> void;
5453
auto rng_clear_deterministic_seed() -> void;
5554
auto rng_deterministic_seed_active() -> bool;
56-
auto rng_deterministic_seed_for( const rng_deterministic_key &key ) -> unsigned int;
57-
auto rng_deterministic_child_seed( unsigned int parent_seed, const rng_deterministic_key &key )
58-
-> unsigned int;
59-
auto rng_deterministic_seed_for_current_context( const rng_deterministic_key &key )
60-
-> std::optional<unsigned int>;
61-
auto rng_next_deterministic_call_seed( std::uint64_t stream ) -> std::optional<unsigned int>;
55+
auto rng_deterministic_seed_for(const rng_deterministic_key& key) -> unsigned int;
56+
auto rng_deterministic_child_seed(unsigned int parent_seed, const rng_deterministic_key& key)
57+
-> unsigned int;
58+
auto rng_deterministic_seed_for_current_context(const rng_deterministic_key& key)
59+
-> std::optional<unsigned int>;
60+
auto rng_next_deterministic_call_seed(std::uint64_t stream) -> std::optional<unsigned int>;
6261

6362
/**
6463
* Thread-local RNG support for worker threads.
@@ -71,52 +70,41 @@ auto rng_next_deterministic_call_seed( std::uint64_t stream ) -> std::optional<u
7170
* The main thread must never call rng_set_worker_seed(); it always uses the
7271
* global engine returned by rng_get_engine().
7372
*/
74-
void rng_set_worker_seed( unsigned int seed );
73+
auto rng_set_worker_seed(unsigned int seed) -> void;
7574

76-
int rng( int lo, int hi );
77-
double rng_float( double lo, double hi );
75+
int rng(int lo, int hi);
76+
double rng_float(double lo, double hi);
7877

79-
template<typename U>
80-
units::quantity<double, U> rng_float( units::quantity<double, U> lo,
81-
units::quantity<double, U> hi )
82-
{
83-
return { rng_float( lo.value(), hi.value() ), U{} };
78+
template <typename U>
79+
units::quantity<double, U> rng_float(units::quantity<double, U> lo, units::quantity<double, U> hi) {
80+
return {rng_float(lo.value(), hi.value()), U{}};
8481
}
8582

8683
units::angle random_direction();
8784

88-
bool one_in( int chance );
89-
bool one_turn_in( const time_duration &duration );
90-
bool x_in_y( double x, double y );
91-
bool check( units::probability p );
92-
int dice( int number, int sides );
85+
bool one_in(int chance);
86+
bool one_turn_in(const time_duration& duration);
87+
bool x_in_y(double x, double y);
88+
bool check(units::probability p);
89+
int dice(int number, int sides);
9390

9491
// Returns x + x_in_y( x-int(x), 1 )
95-
int roll_remainder( double value );
96-
inline int roll_remainder( float value )
97-
{
98-
return roll_remainder( static_cast<double>( value ) );
99-
}
92+
int roll_remainder(double value);
93+
inline int roll_remainder(float value) { return roll_remainder(static_cast<double>(value)); }
10094

101-
int djb2_hash( const unsigned char *input );
95+
int djb2_hash(const unsigned char* input);
10296

103-
double rng_normal( double lo, double hi );
97+
double rng_normal(double lo, double hi);
10498

105-
inline double rng_normal( double hi )
106-
{
107-
return rng_normal( 0.0, hi );
108-
}
99+
inline double rng_normal(double hi) { return rng_normal(0.0, hi); }
109100

110-
double normal_roll( double mean, double stddev );
101+
double normal_roll(double mean, double stddev);
111102

112-
double rng_exponential( double min, double mean );
103+
double rng_exponential(double min, double mean);
113104

114-
inline double rng_exponential( double mean )
115-
{
116-
return rng_exponential( 0.0, mean );
117-
}
105+
inline double rng_exponential(double mean) { return rng_exponential(0.0, mean); }
118106

119-
double exponential_roll( double lambda );
107+
double exponential_roll(double lambda);
120108

121109
/**
122110
* Returns a random entry in the container.
@@ -129,14 +117,11 @@ double exponential_roll( double lambda );
129117
* a temporary object that is not valid after this function has left:
130118
* \code random_entry( vect, std::string("default") ); \endcode
131119
*/
132-
template<typename C, typename D, typename V = typename C::value_type>
133-
inline V random_entry( const C &container, D default_value )
134-
{
135-
if( container.empty() ) {
136-
return default_value;
137-
}
120+
template <typename C, typename D, typename V = typename C::value_type>
121+
inline V random_entry(const C& container, D default_value) {
122+
if (container.empty()) { return default_value; }
138123
auto iter = container.begin();
139-
std::advance( iter, rng( 0, container.size() - 1 ) );
124+
std::advance(iter, rng(0, container.size() - 1));
140125
return *iter;
141126
}
142127
/**
@@ -145,106 +130,84 @@ inline V random_entry( const C &container, D default_value )
145130
* This function handles empty containers without requiring an instance of the
146131
* contained type when container is empty.
147132
*/
148-
template<typename C>
149-
inline auto random_entry_opt( C &container ) ->
150-
std::optional<decltype( std::ref( *container.begin() ) )>
151-
{
152-
if( container.empty() ) {
153-
return std::nullopt;
154-
}
133+
template <typename C>
134+
inline auto random_entry_opt(C& container)
135+
-> std::optional<decltype(std::ref(*container.begin()))> {
136+
if (container.empty()) { return std::nullopt; }
155137
auto iter = container.begin();
156-
std::advance( iter, rng( 0, container.size() - 1 ) );
157-
return std::ref( *iter );
138+
std::advance(iter, rng(0, container.size() - 1));
139+
return std::ref(*iter);
158140
}
159141
/**
160142
* Same as above, but returns a default constructed value if the container
161143
* is empty.
162144
*/
163-
template<typename C, typename V = typename C::value_type>
164-
inline V random_entry( const C &container )
165-
{
166-
if( container.empty() ) {
167-
return V();
168-
}
145+
template <typename C, typename V = typename C::value_type>
146+
inline V random_entry(const C& container) {
147+
if (container.empty()) { return V(); }
169148
auto iter = container.begin();
170-
std::advance( iter, rng( 0, container.size() - 1 ) );
149+
std::advance(iter, rng(0, container.size() - 1));
171150
return *iter;
172151
}
173152

174-
template<typename ...T>
175-
class is_std_array_helper : public std::false_type
176-
{
177-
};
178-
template<typename T, std::size_t N>
179-
class is_std_array_helper<std::array<T, N>> : public std::true_type
180-
{
181-
};
182-
template<typename T>
183-
class is_std_array : public is_std_array_helper<std::decay_t<T>>
184-
{
185-
};
153+
template <typename... T> class is_std_array_helper: public std::false_type {};
154+
template <typename T, std::size_t N>
155+
class is_std_array_helper<std::array<T, N>>: public std::true_type {};
156+
template <typename T> class is_std_array: public is_std_array_helper<std::decay_t<T>> {};
186157

187158
/**
188159
* Same as above, but with a statically allocated default value (using the default
189160
* constructor). This allows to return a reference, either into the given container
190161
* or to the default value.
191162
*/
192-
template<typename C, typename V = typename C::value_type>
193-
inline typename std::enable_if < !is_std_array<C>::value,
194-
const V & >::type random_entry_ref( const C &container )
195-
{
196-
if( container.empty() ) {
163+
template <typename C, typename V = typename C::value_type>
164+
inline typename std::enable_if<!is_std_array<C>::value, const V&>::type random_entry_ref(
165+
const C& container) {
166+
if (container.empty()) {
197167
static const V default_value = V();
198168
return default_value;
199169
}
200170
auto iter = container.begin();
201-
std::advance( iter, rng( 0, container.size() - 1 ) );
171+
std::advance(iter, rng(0, container.size() - 1));
202172
return *iter;
203173
}
204-
template<typename V, std::size_t N>
205-
inline const V &random_entry_ref( const std::array<V, N> &container )
206-
{
207-
static_assert( N > 0, "Need a non-empty array to get a random value from it" );
208-
return container[rng( 0, N - 1 )];
174+
template <typename V, std::size_t N>
175+
inline const V& random_entry_ref(const std::array<V, N>& container) {
176+
static_assert(N > 0, "Need a non-empty array to get a random value from it");
177+
return container[rng(0, N - 1)];
209178
}
210179
/**
211180
* Returns a random entry in the container and removes it from the container.
212181
* The container must not be empty!
213182
*/
214-
template<typename C, typename V = typename C::value_type>
215-
inline V random_entry_removed( C &container )
216-
{
183+
template <typename C, typename V = typename C::value_type>
184+
inline V random_entry_removed(C& container) {
217185
auto iter = container.begin();
218-
std::advance( iter, rng( 0, container.size() - 1 ) );
219-
const V result = std::move( *iter ); // Copy because the original is removed and thereby destroyed
220-
container.erase( iter );
186+
std::advance(iter, rng(0, container.size() - 1));
187+
const V result = std::move(*iter); // Copy because the original is removed and thereby destroyed
188+
container.erase(iter);
221189
return result;
222190
}
223191

224192

225-
template<typename T>
226-
class detached_ptr;
227-
template<typename T>
228-
class location_vector;
193+
template <typename T> class detached_ptr;
194+
template <typename T> class location_vector;
229195

230-
template<typename C>
231-
inline detached_ptr<C> random_entry_detached( location_vector<C> &container )
232-
{
196+
template <typename C> inline detached_ptr<C> random_entry_detached(location_vector<C>& container) {
233197
auto iter = container.begin();
234-
std::advance( iter, rng( 0, container.size() - 1 ) );
198+
std::advance(iter, rng(0, container.size() - 1));
235199
detached_ptr<C> ret;
236-
container.erase( iter, &ret );
200+
container.erase(iter, &ret);
237201
return ret;
238202
}
239203

240204

241205
/// Returns a range enclosing all valid points of the map.
242-
tripoint_range<tripoint_bub_ms> points_in_range( const map &m );
206+
tripoint_range<tripoint_bub_ms> points_in_range(const map& m);
243207
/// Returns a random point in the given range that satisfies the given predicate ( if any ).
244-
std::optional<tripoint_bub_ms> random_point( const tripoint_range<tripoint_bub_ms> &range,
245-
const std::function<bool( const tripoint_bub_ms & )> &predicate );
208+
std::optional<tripoint_bub_ms> random_point(
209+
const tripoint_range<tripoint_bub_ms>& range,
210+
const std::function<bool(const tripoint_bub_ms&)>& predicate);
246211
/// Same as other random_point with a range enclosing all valid points of the map.
247-
std::optional<tripoint_bub_ms> random_point( const map &m,
248-
const std::function<bool( const tripoint_bub_ms & )> &predicate );
249-
250-
212+
std::optional<tripoint_bub_ms> random_point(
213+
const map& m, const std::function<bool(const tripoint_bub_ms&)>& predicate);

0 commit comments

Comments
 (0)