Skip to content

Commit 7dab95f

Browse files
committed
Use Melissa E. O'Neill's pcg for rng and Lemire's unbiased bounded rand
Mersenne Twister's state size is huge (kb's of state), instead we'll swap with a pcg. The pcg's state is n bits and has a equidistributed 2^(n/2) period (plenty for a quick rng). To replicate std::distribution we use lemire's unbiased bounded random method. Adjust the state size as desired between 32 and 128 bits.
1 parent 66b5977 commit 7dab95f

File tree

12 files changed

+173
-24
lines changed

12 files changed

+173
-24
lines changed

src/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,8 @@ list(APPEND SOURCE_FILES
473473
components/stopwatch/StopWatchController.cpp
474474
components/alarm/AlarmController.cpp
475475
components/fs/FS.cpp
476+
components/rng/PCG.cpp
477+
476478
drivers/Cst816s.cpp
477479
FreeRTOS/port.c
478480
FreeRTOS/port_cmsis_systick.c
@@ -662,6 +664,7 @@ set(INCLUDE_FILES
662664
components/timer/Timer.h
663665
components/stopwatch/StopWatchController.h
664666
components/alarm/AlarmController.h
667+
components/rng/PCG.h
665668
drivers/Cst816s.h
666669
FreeRTOS/portmacro.h
667670
FreeRTOS/portmacro_cmsis.h

src/components/rng/PCG.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#include "components/rng/PCG.h"
2+
3+
Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed(Pinetime::Controllers::RNG::rng_uint s,
4+
Pinetime::Controllers::RNG::rng_uint i) {
5+
rng.state = 0u;
6+
rng.inc = i | 1u;
7+
Generate();
8+
rng.state += s;
9+
Generate();
10+
return rng;
11+
}
12+
13+
Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed() {
14+
using namespace Pinetime::Controllers;
15+
Pinetime::Controllers::RNG::State new_rng;
16+
new_rng.state = (RNG::rng_uint) Generate() << (sizeof(new_rng.state) * 4) ^ (RNG::rng_uint) Generate();
17+
new_rng.inc = (RNG::rng_uint) Generate() << (sizeof(new_rng.inc) * 4) ^ (RNG::rng_uint) Generate();
18+
return new_rng;
19+
}
20+
21+
// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
22+
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)
23+
// Website: https://www.pcg-random.org/download.html
24+
// See: https://www.apache.org/licenses/GPL-compatibility.html
25+
/*
26+
uint64_t oldstate = rng.state;
27+
// Advance internal state
28+
rng.state = oldstate * 6364136223846793005ULL + (rng.inc | 1);
29+
// Calculate output function (XSH RR), uses old state for max ILP
30+
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
31+
uint32_t rot = oldstate >> 59u;
32+
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
33+
*/
34+
Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::Generate() {
35+
using namespace Pinetime::Controllers;
36+
// See magic numbers in https://github.com/imneme/pcg-cpp/blob/master/include/pcg_random.hpp
37+
constexpr uint32_t tbits = sizeof(rng.state) * 8;
38+
constexpr uint32_t sbits = sizeof(rng) * 8;
39+
constexpr uint32_t obits = sizeof(RNG::rng_out) * 8;
40+
41+
constexpr rng_uint multiplier = tbits >= 64 ? 1442695040888963407ULL : tbits >= 32 ? 747796405U : tbits >= 16 ? 12829U : 141u;
42+
43+
constexpr uint32_t opbits = sbits - 5 >= 64 ? 5u : // 128 bits -> 5
44+
sbits - 4 >= 32 ? 4u
45+
: // 64 bits -> 4
46+
sbits - 3 >= 16 ? 3u
47+
: // 32 bits -> 3
48+
sbits - 2 >= 8 ? 2u
49+
: // 16 bits -> 2
50+
sbits - 1 >= 1 ? 1u
51+
: // 8 bits -> 1
52+
0u;
53+
54+
auto oldstate = rng.state;
55+
rng.state = oldstate * multiplier + (rng.inc | 1);
56+
// 64 bits of state, 32 output (64 - 5 = 59, 32 - 5 = 27 and floor((5+32)/2) = 18)
57+
// 2^5 = 32*
58+
// output = rotate<s3,s2,s1>(state ^ (state >> s1)) >> s2, state >> s3)
59+
// 32 bits of state, 16 output (32 - 4 = 28, 16 - 4 = 12, and floor((4 + 16)/2) = 10)
60+
// 2^4 = 16*
61+
constexpr uint32_t s3 = tbits - opbits;
62+
constexpr uint32_t s2 = obits - opbits;
63+
constexpr uint32_t s1 = (obits + 5) / 2;
64+
65+
constexpr RNG::rng_out mask = (1 << opbits) - 1;
66+
RNG::rng_out xorshifted = ((oldstate >> s1) ^ oldstate) >> s2;
67+
rng_out rot = oldstate >> s3;
68+
// rotate
69+
return (xorshifted >> rot) | (xorshifted << ((-rot) & mask));
70+
};
71+
72+
// Lemire's Method (slight rewrite) [0, range)
73+
Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::GenerateBounded(Pinetime::Controllers::RNG::rng_out range) {
74+
using namespace Pinetime::Controllers;
75+
rng_uint m;
76+
RNG::rng_out t = (-range) % range;
77+
RNG::rng_out l;
78+
79+
do {
80+
RNG::rng_out x = Generate();
81+
m = RNG::rng_uint(x) * RNG::rng_uint(range);
82+
l = RNG::rng_out(m);
83+
} while (l < t);
84+
85+
return m >> (sizeof(RNG::rng_out) * 8);
86+
};

src/components/rng/PCG.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#pragma once
2+
#include <cstdint>
3+
#include <FreeRTOS.h>
4+
#include <timers.h>
5+
#include "components/motion/MotionController.h"
6+
7+
namespace Pinetime {
8+
namespace Controllers {
9+
struct RNG {
10+
using rng_uint = uint32_t;
11+
using rng_uint2 = uint64_t;
12+
using rng_out = uint16_t;
13+
14+
struct pcg_random_t {
15+
rng_uint state = {};
16+
rng_uint inc = {};
17+
};
18+
19+
using State = pcg_random_t;
20+
State rng = {};
21+
22+
State Seed(rng_uint s, rng_uint i);
23+
// Generate another RNG struct with data generated via this one
24+
State Seed();
25+
// Produces an unsigned result within the full range of the data type
26+
rng_out Generate();
27+
// Produces an unsigned result within [0, range)
28+
rng_out GenerateBounded(rng_out range);
29+
30+
RNG() : rng() {};
31+
32+
RNG& operator=(const State& pcg_state) {
33+
rng = pcg_state;
34+
};
35+
36+
RNG(State pcg_state) {
37+
rng = pcg_state;
38+
};
39+
40+
~RNG() = default;
41+
};
42+
}
43+
}

src/displayapp/Controllers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace Pinetime {
2626
class Timer;
2727
class MusicService;
2828
class NavigationService;
29+
class RNG;
2930
}
3031

3132
namespace System {
@@ -53,6 +54,7 @@ namespace Pinetime {
5354
Pinetime::Components::LittleVgl& lvgl;
5455
Pinetime::Controllers::MusicService* musicService;
5556
Pinetime::Controllers::NavigationService* navigationService;
57+
Pinetime::Controllers::RNG* prngController;
5658
};
5759
}
5860
}

src/displayapp/DisplayApp.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,7 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
717717
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
718718
this->systemTask = systemTask;
719719
this->controllers.systemTask = systemTask;
720+
this->controllers.prngController = &(systemTask->prngController);
720721
}
721722

722723
void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* weatherService) {

src/displayapp/screens/Dice.cpp

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,10 @@ namespace {
4141

4242
Dice::Dice(Controllers::MotionController& motionController,
4343
Controllers::MotorController& motorController,
44-
Controllers::Settings& settingsController)
44+
Controllers::Settings& settingsController,
45+
Controllers::RNG& prngController)
4546
: motorController {motorController}, motionController {motionController}, settingsController {settingsController} {
46-
std::seed_seq sseq {static_cast<uint32_t>(xTaskGetTickCount()),
47-
static_cast<uint32_t>(motionController.X()),
48-
static_cast<uint32_t>(motionController.Y()),
49-
static_cast<uint32_t>(motionController.Z())};
50-
gen.seed(sseq);
47+
rng = prngController.Seed();
5148

5249
lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20,
5350
LV_COLOR_WHITE,
@@ -79,8 +76,7 @@ Dice::Dice(Controllers::MotionController& motionController,
7976
lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
8077
dCounter.SetValue(6);
8178

82-
std::uniform_int_distribution<> distrib(0, resultColors.size() - 1);
83-
currentColorIndex = distrib(gen);
79+
currentColorIndex = rng.GenerateBounded(resultColors.size());
8480

8581
resultTotalLabel = MakeLabel(&jetbrains_mono_42,
8682
resultColors[currentColorIndex],
@@ -157,12 +153,10 @@ void Dice::Refresh() {
157153
void Dice::Roll() {
158154
uint8_t resultIndividual;
159155
uint16_t resultTotal = 0;
160-
std::uniform_int_distribution<> distrib(1, dCounter.GetValue());
161-
162156
lv_label_set_text(resultIndividualLabel, "");
163157

164158
if (nCounter.GetValue() == 1) {
165-
resultTotal = distrib(gen);
159+
resultTotal = rng.GenerateBounded(dCounter.GetValue()) + 1;
166160
if (dCounter.GetValue() == 2) {
167161
switch (resultTotal) {
168162
case 1:
@@ -175,7 +169,7 @@ void Dice::Roll() {
175169
}
176170
} else {
177171
for (uint8_t i = 0; i < nCounter.GetValue(); i++) {
178-
resultIndividual = distrib(gen);
172+
resultIndividual = rng.GenerateBounded(dCounter.GetValue()) + 1;
179173
resultTotal += resultIndividual;
180174
lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str());
181175
if (i < (nCounter.GetValue() - 1)) {

src/displayapp/screens/Dice.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
#include "displayapp/screens/Screen.h"
55
#include "displayapp/widgets/Counter.h"
66
#include "displayapp/Controllers.h"
7+
#include "components/rng/PCG.h"
78
#include "Symbols.h"
89

910
#include <array>
10-
#include <random>
11+
#include <cstdint>
1112

1213
namespace Pinetime {
1314
namespace Applications {
@@ -16,7 +17,8 @@ namespace Pinetime {
1617
public:
1718
Dice(Controllers::MotionController& motionController,
1819
Controllers::MotorController& motorController,
19-
Controllers::Settings& settingsController);
20+
Controllers::Settings& settingsController,
21+
Controllers::RNG& prngController);
2022
~Dice() override;
2123
void Roll();
2224
void Refresh() override;
@@ -29,7 +31,7 @@ namespace Pinetime {
2931
lv_task_t* refreshTask;
3032
bool enableShakeForDice = false;
3133

32-
std::mt19937 gen;
34+
Controllers::RNG rng;
3335

3436
std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
3537
uint8_t currentColorIndex;
@@ -54,7 +56,10 @@ namespace Pinetime {
5456
static constexpr const char* icon = Screens::Symbols::dice;
5557

5658
static Screens::Screen* Create(AppControllers& controllers) {
57-
return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
59+
return new Screens::Dice(controllers.motionController,
60+
controllers.motorController,
61+
controllers.settingsController,
62+
*controllers.prngController);
5863
};
5964

6065
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {

src/displayapp/screens/Twos.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
using namespace Pinetime::Applications::Screens;
77

8-
Twos::Twos() {
8+
Twos::Twos(Pinetime::Controllers::RNG& prngController) {
9+
rng = prngController.Seed();
910

1011
struct colorPair {
1112
lv_color_t bg;
@@ -86,9 +87,9 @@ bool Twos::placeNewTile() {
8687
return false; // game lost
8788
}
8889

89-
int random = rand() % nEmpty;
90+
int random = rng.GenerateBounded(nEmpty);
9091

91-
if ((rand() % 100) < 90) {
92+
if (rng.GenerateBounded(100) < 90) {
9293
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 2;
9394
} else {
9495
grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 4;

src/displayapp/screens/Twos.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "displayapp/screens/Screen.h"
55
#include "displayapp/Controllers.h"
66

7+
#include "displayapp/screens/Dice.h"
8+
79
namespace Pinetime {
810
namespace Applications {
911
struct TwosTile {
@@ -14,7 +16,7 @@ namespace Pinetime {
1416
namespace Screens {
1517
class Twos : public Screen {
1618
public:
17-
Twos();
19+
Twos(Controllers::RNG& prngController);
1820
~Twos() override;
1921

2022
bool OnTouchEvent(TouchEvents event) override;
@@ -29,6 +31,7 @@ namespace Pinetime {
2931
static constexpr int nRows = 4;
3032
static constexpr int nCells = nCols * nRows;
3133
TwosTile grid[nRows][nCols];
34+
Controllers::RNG rng;
3235
unsigned int score = 0;
3336
void updateGridDisplay();
3437
bool tryMerge(int newRow, int newCol, int oldRow, int oldCol);
@@ -42,8 +45,8 @@ namespace Pinetime {
4245
static constexpr Apps app = Apps::Twos;
4346
static constexpr const char* icon = "2";
4447

45-
static Screens::Screen* Create(AppControllers& /*controllers*/) {
46-
return new Screens::Twos();
48+
static Screens::Screen* Create(AppControllers& controllers) {
49+
return new Screens::Twos(*controllers.prngController);
4750
};
4851

4952
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {

src/main.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "components/heartrate/HeartRateController.h"
3838
#include "components/stopwatch/StopWatchController.h"
3939
#include "components/fs/FS.h"
40+
#include "components/rng/PCG.h"
4041
#include "drivers/Spi.h"
4142
#include "drivers/SpiMaster.h"
4243
#include "drivers/SpiNorFlash.h"
@@ -361,8 +362,12 @@ int main() {
361362
}
362363

363364
systemTask.Start();
364-
365365
nimble_port_init();
366+
// ble_ll functions should probably be called after ble_ll_init which is called from nimble_port_init
367+
Pinetime::Controllers::RNG::State prngBleInit;
368+
ble_ll_rand_data_get((uint8_t*) &prngBleInit, sizeof(prngBleInit));
369+
prngBleInit.state ^= (decltype(prngBleInit.state)) xTaskGetTickCount();
370+
systemTask.prngController.rng = prngBleInit;
366371

367372
vTaskStartScheduler();
368373

0 commit comments

Comments
 (0)