Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add FastRNG to ax ns and more improvements #2057

Merged
merged 16 commits into from
Jul 28, 2024
71 changes: 39 additions & 32 deletions core/math/FastRNG.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
/****************************************************************************
Copyright (c) 2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).

https://axmol.dev/
Expand All @@ -28,6 +25,10 @@
#ifndef __FAST_RNG_H__
#define __FAST_RNG_H__

#include<math/MathBase.h>
DelinWorks marked this conversation as resolved.
Show resolved Hide resolved

NS_AX_MATH_BEGIN

/** A fast more effective seeded random number generator struct, uses xoshiro128**.
* It uses a simple algorithm to improve the speed of generating random numbers with a decent quality,
* Use this if you're planning to generate large amounts of random numbers in a single frame.
Expand All @@ -36,36 +37,56 @@
*/
struct FastRNG
{
private:
uint32_t s[4];

// SplitMix64 implementation, doesn't modify any state for this instance
// but it is used to seed xoshiro128** state
uint64_t nextSeed(uint64_t& state)
static inline uint64_t nextSeed(uint64_t& state)
{
uint64_t z = (state += 0x9e3779b97f4a7c15);
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
return z ^ (z >> 31);
}

// returns a copy of x rotated k bits to the left
static inline uint32_t rotL(const uint32_t x, int k) { return (x << k) | (x >> (32 - k)); }

// generates a random integer from 0 to max exclusive that is uniformly distributed using fastrange algorithm
uint32_t nextMax(uint32_t max)
{
uint64_t multiresult = static_cast<uint64_t>(next()) * max;
uint32_t leftover = static_cast<uint32_t>(multiresult);
if (leftover < max)
{
uint32_t threshold = (0 - max) % max;
while (leftover < threshold)
{
multiresult = static_cast<uint64_t>(next()) * max;
leftover = static_cast<uint32_t>(multiresult);
}
}
return multiresult >> 32;
}

public:
FastRNG() { seed(static_cast<uint64_t>(rand()) << 32 | rand()); }
FastRNG(uint64_t _seed) { seed(_seed); }

// there is no need to seed this instance of FastRNG
// because it has already been seeded with rand() by constructor
// because it's already been seeded with rand() in constructor
// you can override the seed by giving your own 64-bit seed
void seed(uint64_t seed)
{
uint64_t state = seed;
uint64_t states[2];
memset(states, 0, 16);
states[0] = nextSeed(state);
states[1] = nextSeed(state);
states[0] = FastRNG::nextSeed(state);
states[1] = FastRNG::nextSeed(state);
memcpy(s, states, 16);
}

// returns a copy of x rotated k bits to the left
static inline uint32_t rotL(const uint32_t x, int k) { return (x << k) | (x >> (32 - k)); }

// steps once into the state, returns a random from 0 to UINT32_MAX
uint32_t next()
{
Expand All @@ -89,11 +110,12 @@ struct FastRNG
template <typename T>
T nextReal()
{
if (std::is_same<T, float>::value)
if constexpr (std::is_same<T, float>::value)
return static_cast<T>(next() >> 8) * 0x1.0p-24f;
else if (std::is_same<T, double>::value)
else if constexpr (std::is_same<T, double>::value)
return static_cast<T>((static_cast<uint64_t>(next()) << 32 | next()) >> 11) * 0x1.0p-53;
return 0; // possibly assert?
else
AXASSERT(false, "datatype not implemented.");
}

// generates a random real that ranges from min to max
Expand All @@ -110,23 +132,6 @@ struct FastRNG
return min + static_cast<T>(nextMax(static_cast<uint32_t>(max - min)));
}

// generates a random integer from 0 to max exclusive that is uniformly distributed using fastrange algorithm
uint32_t nextMax(uint32_t max)
{
uint64_t multiresult = static_cast<uint64_t>(next()) * max;
uint32_t leftover = static_cast<uint32_t>(multiresult);
if (leftover < max)
{
uint32_t threshold = (0 - max) % max;
while (leftover < threshold)
{
multiresult = static_cast<uint64_t>(next()) * max;
leftover = static_cast<uint32_t>(multiresult);
}
}
return multiresult >> 32;
}

// wrapper for nextInt<int32_t>(min, max)
int32_t range(int32_t min, int32_t max) { return nextInt<int32_t>(min, max); }
// wrapper for nextInt<int32_t>(0, max)
Expand All @@ -150,9 +155,11 @@ struct FastRNG
// wrapper for nextReal<float>()
float float01() { return nextReal<float>(); }
// wrapper for nextReal<double>()
float double01() { return nextReal<double>(); }
double double01() { return nextReal<double>(); }
// wrapper for next() & 1, true or false based on LSB
float bool01() { return next() & 1; }
bool bool01() { return static_cast<bool>(next() & 1); }
};

NS_AX_MATH_END

#endif // __FAST_RNG_H__
58 changes: 15 additions & 43 deletions tests/unit-tests/Source/core/math/FastRNGTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,15 @@


TEST_SUITE("math/FastRNG") {
TEST_CASE("nextSeed")
{
auto rng = FastRNG();

uint64_t s = 0x1234'5678'9ABC'EFFF;
CHECK_EQ(2299331620237437860u, rng.nextSeed(s));
CHECK_EQ(8718988738428180276u, rng.nextSeed(s));
}

TEST_CASE("rotL")
{
auto rng = FastRNG();

uint32_t s = 0x1357'9BDF;
CHECK_EQ(2939665958, rng.rotL(s, 9));
CHECK_EQ(4084982378, rng.rotL(s, 13));
}

TEST_CASE("next") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(1695105466, rng.next());
CHECK_EQ(1423115009, rng.next());
CHECK_EQ(634581793, rng.next());

auto rng2 = FastRNG();
auto rng2 = ax::FastRNG();
rng2.seed(2);

CHECK_EQ(1086064458, rng2.next());
Expand All @@ -65,7 +47,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("nextInt")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(12);

CHECK_EQ(30, rng.nextInt<int8_t>(INT8_MIN, INT8_MAX));
Expand All @@ -80,7 +62,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("nextReal")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(14);

CHECK_EQ(doctest::Approx(0.927014), rng.nextReal<float>());
Expand All @@ -92,18 +74,8 @@ TEST_SUITE("math/FastRNG") {
CHECK_EQ(doctest::Approx(9.94378e+307), rng.nextReal<double>(DBL_MIN, DBL_MAX));
}

TEST_CASE("nextMax")
{
auto rng = FastRNG();
rng.seed(16);

CHECK_EQ(1381652921, rng.nextMax(UINT32_MAX));
CHECK_EQ(46435, rng.nextMax(UINT16_MAX));
CHECK_EQ(84, rng.nextMax(UINT8_MAX));
}

TEST_CASE("range") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(12345);

CHECK_EQ(0, rng.range(0, 1));
Expand All @@ -119,7 +91,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("max")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(0, rng.max(1));
Expand All @@ -129,7 +101,7 @@ TEST_SUITE("math/FastRNG") {


TEST_CASE("rangeu") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(0u, rng.rangeu(0u, 1u));
Expand All @@ -141,7 +113,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("maxu")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(0u, rng.maxu(1));
Expand All @@ -150,7 +122,7 @@ TEST_SUITE("math/FastRNG") {
}

TEST_CASE("rangef") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(-0.210655), rng.rangef(-1.0f, 1.0f));
Expand All @@ -163,7 +135,7 @@ TEST_SUITE("math/FastRNG") {


TEST_CASE("maxf") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(0.394672), rng.maxf(1.0f));
Expand All @@ -175,7 +147,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("ranged")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(-0.210655), rng.ranged(-1.0, 1.0));
Expand All @@ -185,7 +157,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("maxd")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(0.394672), rng.maxd(1.0f));
Expand All @@ -196,7 +168,7 @@ TEST_SUITE("math/FastRNG") {
}

TEST_CASE("float01") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(0.394672), rng.float01());
Expand All @@ -207,7 +179,7 @@ TEST_SUITE("math/FastRNG") {

TEST_CASE("double01")
{
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

CHECK_EQ(doctest::Approx(0.394672), rng.double01());
Expand All @@ -217,7 +189,7 @@ TEST_SUITE("math/FastRNG") {
}

TEST_CASE("bool01") {
auto rng = FastRNG();
auto rng = ax::FastRNG();
rng.seed(1);

auto t = 0;
Expand Down