Skip to content

Commit efe9569

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 64 bits and has a equidistributed 2^32 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 efe9569

File tree

12 files changed

+177
-25
lines changed

12 files changed

+177
-25
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: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
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>
12+
13+
1114

1215
namespace Pinetime {
1316
namespace Applications {
@@ -16,7 +19,8 @@ namespace Pinetime {
1619
public:
1720
Dice(Controllers::MotionController& motionController,
1821
Controllers::MotorController& motorController,
19-
Controllers::Settings& settingsController);
22+
Controllers::Settings& settingsController,
23+
Controllers::RNG& prngController);
2024
~Dice() override;
2125
void Roll();
2226
void Refresh() override;
@@ -29,7 +33,7 @@ namespace Pinetime {
2933
lv_task_t* refreshTask;
3034
bool enableShakeForDice = false;
3135

32-
std::mt19937 gen;
36+
Controllers::RNG rng;
3337

3438
std::array<lv_color_t, 3> resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA};
3539
uint8_t currentColorIndex;
@@ -54,7 +58,10 @@ namespace Pinetime {
5458
static constexpr const char* icon = Screens::Symbols::dice;
5559

5660
static Screens::Screen* Create(AppControllers& controllers) {
57-
return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController);
61+
return new Screens::Dice(controllers.motionController,
62+
controllers.motorController,
63+
controllers.settingsController,
64+
*controllers.prngController);
5865
};
5966

6067
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: 8 additions & 2 deletions
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"
@@ -359,10 +360,15 @@ int main() {
359360
memset(&__start_noinit_data, 0, (uintptr_t) &__stop_noinit_data - (uintptr_t) &__start_noinit_data);
360361
NoInit_MagicWord = NoInit_MagicValue;
361362
}
362-
363+
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;
371+
366372

367373
vTaskStartScheduler();
368374

0 commit comments

Comments
 (0)