Skip to content

Commit 6b7ab5f

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 6b7ab5f

File tree

6 files changed

+153
-52
lines changed

6 files changed

+153
-52
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: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,94 @@
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);
3+
using namespace Pinetime::Controllers;
4+
5+
RNG::RNG() : rng {0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL} {
66
}
77

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;
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);
1315
}
1416

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

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);
86+
template <class OutputIt>
87+
void RNG::param(OutputIt dest) const {
88+
std::size_t i = 0;
89+
const std::size_t n = size();
90+
RNG::result_type* ptr = (RNG::result_type*) this;
91+
for (; i < n; ++i, ++dest, ++ptr) {
92+
*dest = ptr;
93+
}
2294
};

src/components/rng/PCG.h

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,74 @@
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; // RNG state. All values are possible.
34+
uint64_t inc; // Controls which RNG sequence (stream) is
35+
// selected. Must *always* be odd.
36+
};
37+
typedef struct pcg_state_setseq_64 pcg32_random_t;
38+
1039
struct RNG {
40+
pcg32_random_t rng;
41+
// <random> interface
42+
using result_type = uint32_t;
1143

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;
44+
static constexpr result_type min() {
45+
return result_type {};
3746
};
3847

39-
RNG(State pcg_state) {
40-
rng = pcg_state;
48+
static constexpr result_type max() {
49+
return ~result_type {0UL};
4150
};
4251

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

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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,12 @@ 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();
371371
prngBleInit();
372-
systemTask.prngController.rng = prngBleInit;
372+
systemTask.prngController = prngBleInit;
373373

374374
vTaskStartScheduler();
375375

0 commit comments

Comments
 (0)