Skip to content

Commit b58ab64

Browse files
committed
Rewrite rng component using <random> as reference
Incorporate interface for <random> engines and std::seed_seq into single struct. Use operator()() and operator()(uint32_t) to retrieve unbounded and bounded results. Melissa's PCG is unbiased, so <random> is not required in order to use the RNG struct as is. Use generate(It start, It end) to fill target range with data (as per std::seed_seq). Use seed functions with something like std::seed_seq with a generate function to fill the internal state of the PCG with random data. Or alternatively use the seed function with an integer to select a fairly random PCG stream.
1 parent c083748 commit b58ab64

File tree

7 files changed

+175
-56
lines changed

7 files changed

+175
-56
lines changed

src/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,6 @@ set(INCLUDE_FILES
684684
buttonhandler/ButtonHandler.h
685685
touchhandler/TouchHandler.h
686686
utility/Math.h
687-
pcg-cpp/include/pcg_random.hpp
688687
)
689688

690689
include_directories(

src/components/rng/PCG.cpp

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,107 @@
11
#include "components/rng/PCG.h"
22

3-
Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed(Pinetime::Controllers::RNG::rng_uint s,
4-
Pinetime::Controllers::RNG::rng_uint i) {
5-
return rng = State(s, i);
6-
}
3+
using namespace Pinetime::Controllers;
74

8-
Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed() {
9-
using namespace Pinetime::Controllers;
10-
Pinetime::Controllers::RNG::State new_rng((uint64_t) Generate() << 32 ^ (uint64_t) Generate(),
11-
(uint64_t) Generate() << 32 ^ (uint64_t) Generate());
12-
return new_rng;
13-
}
5+
//pcg_state_setseq_64::pcg_state_setseq_64(uint64_t s, uint64_t i) : state(s), inc(i) {};
6+
//RNG::RNG() : rng {0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL} {};
147

15-
Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::Generate() {
16-
return rng();
8+
RNG::RNG(result_type value) {
9+
// pcg32_srandom_r(&rng, 0x853c49e6748fea9bULL, value);
10+
rng.state = 0U;
11+
rng.inc = (value << 1u) | 1u;
12+
(*this)(); // pcg32_random_r(rng);
13+
rng.state += 0x853c49e6748fea9bULL;
14+
(*this)(); // pcg32_random_r(rng);
1715
};
1816

19-
// See pcg-cpp/sample/codebook.cpp
20-
Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::GenerateBounded(Pinetime::Controllers::RNG::rng_out range) {
21-
return rng(range);
17+
RNG& RNG::operator=(const pcg32_random_t& other) {
18+
rng.state = other.state;
19+
rng.inc = other.inc | 1;
20+
return *this;
21+
};
22+
23+
template <class SeedSeq>
24+
RNG::RNG(SeedSeq& seq) {
25+
seed(seq);
26+
};
27+
/*
28+
RNG::RNG(const RNG& other) {
29+
RNG tmp = other;
30+
RNG::result_type* ptr = (RNG::result_type*) this;
31+
tmp.generate(ptr, ptr + (sizeof(RNG) / sizeof(RNG::result_type)));
32+
};
33+
*/
34+
template <typename SeedSeq>
35+
void RNG::seed(SeedSeq& seq) {
36+
RNG::result_type* ptr = (RNG::result_type*) this;
37+
seq.generate(ptr, ptr + (sizeof(RNG) / sizeof(RNG::result_type)));
38+
rng.inc |= 1;
39+
};
40+
41+
void RNG::seed(RNG& other) {
42+
RNG::result_type* ptr = (RNG::result_type*) this;
43+
other.generate(ptr, ptr + (sizeof(RNG) / sizeof(RNG::result_type)));
44+
rng.inc |= 1;
45+
};
46+
47+
RNG::result_type RNG::operator()() {
48+
// return pcg32_random_r(&rng);
49+
uint64_t oldstate = rng.state;
50+
rng.state = oldstate * 6364136223846793005ULL + rng.inc;
51+
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
52+
uint32_t rot = oldstate >> 59u;
53+
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
54+
};
55+
56+
RNG::result_type RNG::operator()(RNG::result_type bound) {
57+
// return pcg32_boundedrand_r(&rng, bounds);
58+
// To avoid bias, we need to make the range of the RNG a multiple of
59+
// bound, which we do by dropping output less than a threshold.
60+
// A naive scheme to calculate the threshold would be to do
61+
//
62+
// uint32_t threshold = 0x100000000ull % bound;
63+
//
64+
// but 64-bit div/mod is slower than 32-bit div/mod (especially on
65+
// 32-bit platforms). In essence, we do
66+
//
67+
// uint32_t threshold = (0x100000000ull-bound) % bound;
68+
//
69+
// because this version will calculate the same modulus, but the LHS
70+
// value is less than 2^32.
71+
72+
uint32_t threshold = -bound % bound;
73+
74+
// Uniformity guarantees that this loop will terminate. In practice, it
75+
// should usually terminate quickly; on average (assuming all bounds are
76+
// equally likely), 82.25% of the time, we can expect it to require just
77+
// one iteration. In the worst case, someone passes a bound of 2^31 + 1
78+
// (i.e., 2147483649), which invalidates almost 50% of the range. In
79+
// practice, bounds are typically small and only a tiny amount of the range
80+
// is eliminated.
81+
for (;;) {
82+
uint32_t r = (*this)(); // pcg32_random_r(rng);
83+
if (r >= threshold)
84+
return r % bound;
85+
}
86+
};
87+
88+
// std::seed_seq interface
89+
template <typename It>
90+
void RNG::generate(It start, It end) {
91+
for (; start != end; ++start)
92+
*start = (*this)();
93+
};
94+
95+
std::size_t RNG::size() const noexcept {
96+
return sizeof(rng) / sizeof(RNG::result_type);
97+
};
98+
99+
template <class OutputIt>
100+
void RNG::param(OutputIt dest) const {
101+
std::size_t i = 0;
102+
const std::size_t n = size();
103+
RNG::result_type* ptr = (RNG::result_type*) this;
104+
for (; i < n; ++i, ++dest, ++ptr) {
105+
*dest = ptr;
106+
}
22107
};

src/components/rng/PCG.h

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,80 @@
33
#include <FreeRTOS.h>
44
#include <timers.h>
55
#include "components/motion/MotionController.h"
6-
#include "pcg-cpp/include/pcg_random.hpp"
76

87
namespace Pinetime {
98
namespace Controllers {
9+
/*
10+
* PCG Random Number Generation for C.
11+
*
12+
* Copyright 2014 Melissa O'Neill <[email protected]>
13+
*
14+
* Licensed under the Apache License, Version 2.0 (the "License");
15+
* you may not use this file except in compliance with the License.
16+
* You may obtain a copy of the License at
17+
*
18+
* http://www.apache.org/licenses/LICENSE-2.0
19+
*
20+
* Unless required by applicable law or agreed to in writing, software
21+
* distributed under the License is distributed on an "AS IS" BASIS,
22+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23+
* See the License for the specific language governing permissions and
24+
* limitations under the License.
25+
*
26+
* For additional information about the PCG random number generation scheme,
27+
* including its license and other licensing options, visit
28+
*
29+
* http://www.pcg-random.org
30+
*/
31+
32+
struct pcg_state_setseq_64 { // Internals are *Private*.
33+
uint64_t state = 0x853c49e6748fea9bULL; // RNG state. All values are possible.
34+
uint64_t inc = 0xda3e39cb94b95bdbULL; // Controls which RNG sequence (stream) is
35+
// selected. Must *always* be odd.
36+
//pcg_state_setseq_64() = default;
37+
//pcg_state_setseq_64(uint64_t s, uint64_t i);
38+
};
39+
typedef struct pcg_state_setseq_64 pcg32_random_t;
40+
1041
struct RNG {
42+
pcg32_random_t rng = {};
43+
// <random> interface
44+
using result_type = uint32_t;
1145

12-
/*
13-
struct pcg_random_t {
14-
rng_uint state = {};
15-
rng_uint inc = {};
16-
};
17-
*/
18-
using State = pcg32;
19-
using rng_uint = State::state_type;// uint32_t;
20-
using rng_out = State::result_type;// uint16_t;
21-
22-
State rng = {};
23-
24-
State Seed(rng_uint s, rng_uint i);
25-
// Generate another RNG struct with data generated via this one
26-
State Seed();
27-
// Produces an unsigned result within the full range of the data type
28-
rng_out Generate();
29-
// Produces an unsigned result within [0, range)
30-
rng_out GenerateBounded(rng_out range);
31-
32-
RNG() : rng() {};
33-
34-
RNG& operator=(const State& pcg_state) {
35-
rng = pcg_state;
36-
return *this;
46+
static constexpr result_type min() {
47+
return result_type {};
3748
};
3849

39-
RNG(State pcg_state) {
40-
rng = pcg_state;
50+
static constexpr result_type max() {
51+
return ~result_type {0UL};
4152
};
4253

43-
~RNG() = default;
54+
// Constructors
55+
// RNG();
56+
RNG() = default;
57+
explicit RNG(result_type value);
58+
59+
template <class SeedSeq>
60+
explicit RNG(SeedSeq& seq);
61+
62+
RNG& operator=(const pcg32_random_t& other);
63+
//RNG(const RNG& other);
64+
65+
template <typename SeedSeq>
66+
void seed(SeedSeq& seq);
67+
68+
void seed(RNG& other);
69+
70+
result_type operator()();
71+
result_type operator()(result_type bound);
72+
// std::seed_seq interface
73+
template <typename It>
74+
void generate(It start, It end);
75+
76+
std::size_t size() const noexcept;
77+
78+
template <class OutputIt>
79+
void param(OutputIt dest) const;
4480
};
4581
}
4682
}

src/displayapp/Controllers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace Pinetime {
2626
class Timer;
2727
class MusicService;
2828
class NavigationService;
29-
class RNG;
29+
struct RNG;
3030
}
3131

3232
namespace System {

src/displayapp/screens/Dice.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Dice::Dice(Controllers::MotionController& motionController,
4444
Controllers::Settings& settingsController,
4545
Controllers::RNG& prngController)
4646
: motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
47-
rng = prngController.Seed();
47+
rng.seed(prngController);
4848

4949
lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
5050
LV_COLOR_WHITE,
@@ -76,7 +76,7 @@ Dice::Dice(Controllers::MotionController& motionController,
7676
lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
7777
dCounter.SetValue(6);
7878

79-
currentColorIndex = rng.GenerateBounded(resultColors.size());
79+
currentColorIndex = rng(resultColors.size());
8080

8181
resultTotalLabel = MakeLabel(&jetbrains_mono_42,
8282
resultColors[currentColorIndex],
@@ -156,7 +156,7 @@ void Dice::Roll() {
156156
lv_label_set_text(resultIndividualLabel, "");
157157

158158
if (nCounter.GetValue() == 1) {
159-
resultTotal = rng.GenerateBounded(dCounter.GetValue()) + 1;
159+
resultTotal = rng(dCounter.GetValue()) + 1;
160160
if (dCounter.GetValue() == 2) {
161161
switch (resultTotal) {
162162
case 1:
@@ -169,7 +169,7 @@ void Dice::Roll() {
169169
}
170170
} else {
171171
for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
172-
resultIndividual = rng.GenerateBounded(dCounter.GetValue()) + 1;
172+
resultIndividual = rng(dCounter.GetValue()) + 1;
173173
resultTotal += resultIndividual;
174174
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
175175
if (i < (nCounter.GetValue() - 1)) {

src/displayapp/screens/Twos.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using namespace Pinetime::Applications::Screens;
77

88
Twos::Twos(Pinetime::Controllers::RNG& prngController) {
9-
rng = prngController.Seed();
9+
rng.seed(prngController); // Pinetime::Controllers::RNG {prngController(), prngController()}; // = prngController.Seed();
1010

1111
struct colorPair {
1212
lv_color_t bg;
@@ -87,9 +87,9 @@ bool Twos::placeNewTile() {
8787
return false; // game lost
8888
}
8989

90-
int random = rng.GenerateBounded(nEmpty);
90+
int random = rng(nEmpty);
9191

92-
if (rng.GenerateBounded(100) < 90) {
92+
if (rng(100) < 90) {
9393
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 2;
9494
} else {
9595
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 4;

src/main.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,11 @@ int main() {
364364
systemTask.Start();
365365
nimble_port_init();
366366
// ble_ll functions should probably be called after ble_ll_init which is called from nimble_port_init
367-
Pinetime::Controllers::RNG::State prngBleInit;
367+
Pinetime::Controllers::RNG prngBleInit;
368368
ble_ll_rand_data_get((uint8_t*) &prngBleInit, sizeof(prngBleInit));
369369
// TODO: Seed with lifetime stats
370370
*((uint32_t*) &prngBleInit) ^= xTaskGetTickCount();
371-
prngBleInit();
372-
systemTask.prngController.rng = prngBleInit;
371+
systemTask.prngController = prngBleInit;
373372

374373
vTaskStartScheduler();
375374

0 commit comments

Comments
 (0)