Permalink
Browse files

Replace Sauers old RNG with the C++ standard RNGs

The new api provides more generic functions and is
thread-safe.
  • Loading branch information...
koraa authored and a-teammate committed Jun 12, 2015
1 parent 7cdd662 commit 8b7dac4d71ef9b8fc7cbebe9da7ca40ba03546f8
Showing with 339 additions and 73 deletions.
  1. +3 −1 inexor/fpsgame/server.cpp
  2. +0 −55 inexor/shared/tools.cpp
  3. +5 −17 inexor/shared/tools.h
  4. +159 −0 inexor/test/util/random.cpp
  5. +14 −0 inexor/util/random.cpp
  6. +158 −0 inexor/util/random.h
@@ -1,5 +1,7 @@
#include "inexor/fpsgame/game.h"
#include "inexor/util/random.h"
namespace game
{
void parseoptions(vector<const char *> &args)
@@ -2800,7 +2802,7 @@ namespace server
userinfo *u = users.access(userkey(ci->authname, ci->authdesc));
if(u)
{
uint seed[3] = { ::hthash(serverauth) + detrnd(size_t(ci) + size_t(user) + size_t(desc), 0x10000), uint(totalmillis), randomMT() };
uint seed[3] = { ::hthash(serverauth) + detrnd(size_t(ci) + size_t(user) + size_t(desc), 0x10000), uint(totalmillis), inexor::util::rnd_raw<uint>() };
vector<char> buf;
ci->authchallenge = genchallenge(u->pubkey, seed, sizeof(seed), buf);
sendf(ci->clientnum, 1, "risis", N_AUTHCHAL, desc, ci->authreq, buf.getbuf());
@@ -5,11 +5,6 @@
#include <unistd.h>
#endif
#include <cstdlib>
#include <limits>
////////////////////////// strings ////////////////////////////////////////
static string tmpstr[4];
static int tmpidx = 0;
@@ -26,56 +21,6 @@ char *tempformatstring(const char *fmt, ...)
return buf;
}
////////////////////////// rnd numbers ////////////////////////////////////////
#define N (624)
#define M (397)
#define K (0x9908B0DFU)
static uint state[N];
static int next = N;
void seedMT(uint seed)
{
state[0] = seed;
for(uint i = 1; i < N; i++)
state[i] = seed = 1812433253U * (seed ^ (seed >> 30)) + i;
next = 0;
}
uint randomMT()
{
int cur = next;
if(++next >= N)
{
if(next > N) { seedMT(5489U + time(NULL)); cur = next++; }
else next = 0;
}
uint y = (state[cur] & 0x80000000U) | (state[next] & 0x7FFFFFFFU);
state[cur] = y = state[cur < N-M ? cur + M : cur + M-N] ^ (y >> 1) ^ (-int(y & 1U) & K);
y ^= (y >> 11);
y ^= (y << 7) & 0x9D2C5680U;
y ^= (y << 15) & 0xEFC60000U;
y ^= (y >> 18);
return y;
}
int rnd(const int x) {
return abs((int)randomMT()) % x;
}
float rndscale(const double x) {
double int_max = std::numeric_limits<int>::max();
return abs((int)randomMT()) * x / int_max;
}
int detrnd(const uint seed, const int x) {
return ( (seed*1103515245 + 12345) >>16) %x;
}
///////////////////////// network ///////////////////////
// all network traffic is in 32bit ints, which are then compressed using the following simple scheme (assumes that most values are small).
template<class T>
@@ -10,8 +10,9 @@
#include <boost/algorithm/clamp.hpp>
#include "inexor/util/random.h"
#include "inexor/util/util.h"
/// type definitions sauerbraten uses.
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
@@ -67,22 +68,9 @@ static inline int bitscan(uint mask)
#endif
#endif
/// Generate a random integer between 0 and x, excluding x
///
/// ```rnd(2) // can producd 0 and 1```
extern int rnd(const int x);
/// Generate a random float between 0 and x
///
/// ```rndscale(1) // min: 0, max: 0.99...```
extern float rndscale(const double x);
/// Generate a deterministic pseudo-random number
///
/// This means, that given the same seed and the same
/// maximum value (`x`) this function will always return the
/// same.
extern int detrnd(const uint seed, const int x);
INEXOR_FUNCTION_ALIAS(rnd, inexor::util::rnd<int>);
INEXOR_FUNCTION_ALIAS(rndscale, inexor::util::rnd<float>);
INEXOR_FUNCTION_ALIAS(detrnd, inexor::util::deterministic_rnd<int>);
/// "for"-loop macro definitions
/// DEPRECATED: Use c++ range based loops instead
@@ -0,0 +1,159 @@
#include <boost/thread/thread.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/mutex.hpp>
#include <array>
#include <unordered_map>
#include <unordered_set>
#include "inexor/util/util.h"
#include "inexor/util/random.h"
#include "gtest/gtest.h"
#include "inexor/test/helpers.h"
using namespace std;
using namespace boost;
using namespace inexor::util;
class range {
public:
int a, // minimum of the range
z, // maximum of the range
tries, // Statistical tries
same_compares; // Number of checks, whether the deterministic
};
// Seeds for the deterministic rng
array<int, 24> seeds = {
0, -2000, 1, -1,
-2000000, 999, 256, -1024,
12516, 9124, 882, 8843,
23619, 17610, 3270, 19176,
21357, 8158, 7774, 19288,
2382, 1890, 22445, 5097
};
array<range, 5> ranges {
range({0, 2, 10, 20}),
range({-10, 10, 100, 2}),
range({-30000, -29900, 100, 1}),
range({5000, 7000, 100, 1}),
range({300,301, 4, 20})
};
TEST(DeterministicRandom, XToYRange) {
for (range &r : ranges) {
// Here we check with different seeds and try if we
// stay within the given range
for (int seed : seeds) {
int r1, r0 = deterministic_rnd(seed, r.a, r.z);
// Here we check whether the same seed and range
// produces the same result.
for (int i=1; i<r.same_compares; i++) {
r1 = deterministic_rnd(seed, r.a, r.z);
EXPECT_EQ(r0, r1) << "Expected the"
"deterministic RNG to provide the same "
"result for the same seed.";
r0 = r1;
}
EXPECT_GE(r0, r.a) << "Expected "
"deterministic_ rnd(" << seed << ", "
<< r.a << ", " << r.z << ") = " << r0 <<
"to produce values x >= " << r.a << ".";
EXPECT_LE(r0, r.z) << "Expected "
"deterministic_rnd(" << seed << ", "
<< r.a << ", " << r.z << ") = " << r0 <<
"to produce values x <= " << r.z << ".";
}
}
}
TEST(PseudoRandom, XToYRange) {
for (range &r : ranges) {
int r0 = rnd(r.a, r.z);
bool diff = false;
for (int i=0; i<10; i++) {
if (rnd(r.a, r.z) != r0) {
diff = true;
break;
}
}
EXPECT_TRUE(diff) << "Expected at least one of ten random"
"numbers to be different when calling rnd("
<< r.a << ", " << r.z << ") = " << r0;
// Here we check with different seeds and try if we
// stay within the given range
for (int i=0; i<r.tries; i++) {
int r0 = rnd(r.a, r.z);
EXPECT_GE(r0, r.a) << "Expected "
"rnd(" << r.a << ", " << r.z << ") "
"to produce values x >= " << r.a << ".";
EXPECT_LE(r0, r.z) << "Expected "
"rnd(" << r.a << ", " << r.z << ") "
"to produce values x <= " << r.z << ".";
}
}
}
TEST(PseudoRandom, TypeFullRange) {
typedef unsigned char uchar;
unordered_set<uchar> dist;
for (int i=0; i<200000; i++)
dist.insert(rnd_raw<uchar>());
EXPECT_EQ((unsigned int)256, dist.size()) << "Expected "
"20000 random unsigned chars to contain every "
"possible uchar but could find only "
<< dist.size() << " values";
}
TEST(PseudoRandom, NumbersDifferAmongThreads) {
typedef std::default_random_engine::result_type rand_type;
const static int threadno = 200;
array<rand_type, threadno> first_numbers;
array<thread, threadno> threads;
// Start $threadno threads; generate one number and save
// in first_numbers; no locking because each thread owns
// one slot in first_numbers
for (int i=0; i<threadno; i++) {
threads[i] = thread([i, &first_numbers](){
first_numbers[i] =
(*inexor::util::random::generator)();
});
}
// Wait for all threads to finish
for (thread& t : threads) t.join();
// Compute the distribution of numbers: number -> frequency
unordered_map<rand_type, int> res(threadno*10);
for (rand_type n : first_numbers) {
bool has_key = res.find(n) == res.end();
res[n] = (has_key ? res[n] : 0) + 1;
}
// And now check if there is any number whose frequency != 1
// (usually will use uint_fast_32, so the collision
// probability is OK, but not good)
// TODO: Check the seeds directly
for (auto &cont : res)
EXPECT_EQ(1, cont.second) << "Expcting random numbers"
"to be uniq in a set of " << threadno << "threads, "
"but random number " << cont.first << " occurred "
<< cont.second << " times.";
}
@@ -0,0 +1,14 @@
#include "inexor/util/random.h"
#include <chrono>
namespace inexor {
namespace util {
namespace random {
thread_local_ptr<auto_seeded<rng_engine>> generator;
thread_local_ptr<rng_engine> deterministic_generator;
}
}
}
Oops, something went wrong.

0 comments on commit 8b7dac4

Please sign in to comment.