From e42049daa0fb3eaa61693348304019888467de80 Mon Sep 17 00:00:00 2001 From: Andersama Date: Tue, 16 Dec 2025 12:20:29 -0800 Subject: [PATCH 1/3] 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. --- src/CMakeLists.txt | 3 ++ src/components/rng/PCG.cpp | 86 +++++++++++++++++++++++++++++++++ src/components/rng/PCG.h | 44 +++++++++++++++++ src/displayapp/Controllers.h | 2 + src/displayapp/DisplayApp.cpp | 1 + src/displayapp/screens/Dice.cpp | 18 +++---- src/displayapp/screens/Dice.h | 13 +++-- src/displayapp/screens/Twos.cpp | 7 +-- src/displayapp/screens/Twos.h | 9 ++-- src/main.cpp | 7 ++- src/systemtask/SystemTask.cpp | 3 +- src/systemtask/SystemTask.h | 5 ++ 12 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 src/components/rng/PCG.cpp create mode 100644 src/components/rng/PCG.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4a354df64..0496aee6a5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -473,6 +473,8 @@ list(APPEND SOURCE_FILES components/stopwatch/StopWatchController.cpp components/alarm/AlarmController.cpp components/fs/FS.cpp + components/rng/PCG.cpp + drivers/Cst816s.cpp FreeRTOS/port.c FreeRTOS/port_cmsis_systick.c @@ -662,6 +664,7 @@ set(INCLUDE_FILES components/timer/Timer.h components/stopwatch/StopWatchController.h components/alarm/AlarmController.h + components/rng/PCG.h drivers/Cst816s.h FreeRTOS/portmacro.h FreeRTOS/portmacro_cmsis.h diff --git a/src/components/rng/PCG.cpp b/src/components/rng/PCG.cpp new file mode 100644 index 0000000000..46d9f1169e --- /dev/null +++ b/src/components/rng/PCG.cpp @@ -0,0 +1,86 @@ +#include "components/rng/PCG.h" + +Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed(Pinetime::Controllers::RNG::rng_uint s, + Pinetime::Controllers::RNG::rng_uint i) { + rng.state = 0u; + rng.inc = i | 1u; + Generate(); + rng.state += s; + Generate(); + return rng; +} + +Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed() { + using namespace Pinetime::Controllers; + Pinetime::Controllers::RNG::State new_rng; + new_rng.state = (RNG::rng_uint) Generate() << (sizeof(new_rng.state) * 4) ^ (RNG::rng_uint) Generate(); + new_rng.inc = (RNG::rng_uint) Generate() << (sizeof(new_rng.inc) * 4) ^ (RNG::rng_uint) Generate(); + return new_rng; +} + +// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org +// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website) +// Website: https://www.pcg-random.org/download.html +// See: https://www.apache.org/licenses/GPL-compatibility.html +/* + uint64_t oldstate = rng.state; + // Advance internal state + rng.state = oldstate * 6364136223846793005ULL + (rng.inc | 1); + // Calculate output function (XSH RR), uses old state for max ILP + uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + uint32_t rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +*/ +Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::Generate() { + using namespace Pinetime::Controllers; + // See magic numbers in https://github.com/imneme/pcg-cpp/blob/master/include/pcg_random.hpp + constexpr uint32_t tbits = sizeof(rng.state) * 8; + constexpr uint32_t sbits = sizeof(rng) * 8; + constexpr uint32_t obits = sizeof(RNG::rng_out) * 8; + + constexpr rng_uint multiplier = tbits >= 64 ? 1442695040888963407ULL : tbits >= 32 ? 747796405U : tbits >= 16 ? 12829U : 141u; + + constexpr uint32_t opbits = sbits - 5 >= 64 ? 5u : // 128 bits -> 5 + sbits - 4 >= 32 ? 4u + : // 64 bits -> 4 + sbits - 3 >= 16 ? 3u + : // 32 bits -> 3 + sbits - 2 >= 8 ? 2u + : // 16 bits -> 2 + sbits - 1 >= 1 ? 1u + : // 8 bits -> 1 + 0u; + + auto oldstate = rng.state; + rng.state = oldstate * multiplier + (rng.inc | 1); + // 64 bits of state, 32 output (64 - 5 = 59, 32 - 5 = 27 and floor((5+32)/2) = 18) + // 2^5 = 32* + // output = rotate(state ^ (state >> s1)) >> s2, state >> s3) + // 32 bits of state, 16 output (32 - 4 = 28, 16 - 4 = 12, and floor((4 + 16)/2) = 10) + // 2^4 = 16* + constexpr uint32_t s3 = tbits - opbits; + constexpr uint32_t s2 = obits - opbits; + constexpr uint32_t s1 = (obits + 5) / 2; + + constexpr RNG::rng_out mask = (1 << opbits) - 1; + RNG::rng_out xorshifted = ((oldstate >> s1) ^ oldstate) >> s2; + rng_out rot = oldstate >> s3; + // rotate + return (xorshifted >> rot) | (xorshifted << ((-rot) & mask)); +}; + +// Lemire's Method (slight rewrite) [0, range) +Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::GenerateBounded(Pinetime::Controllers::RNG::rng_out range) { + using namespace Pinetime::Controllers; + rng_uint m; + RNG::rng_out t = (-range) % range; + RNG::rng_out l; + + do { + RNG::rng_out x = Generate(); + m = RNG::rng_uint(x) * RNG::rng_uint(range); + l = RNG::rng_out(m); + } while (l < t); + + return m >> (sizeof(RNG::rng_out) * 8); +}; \ No newline at end of file diff --git a/src/components/rng/PCG.h b/src/components/rng/PCG.h new file mode 100644 index 0000000000..5dba5bb3b2 --- /dev/null +++ b/src/components/rng/PCG.h @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include +#include "components/motion/MotionController.h" + +namespace Pinetime { + namespace Controllers { + struct RNG { + using rng_uint = uint32_t; + using rng_uint2 = uint64_t; + using rng_out = uint16_t; + + struct pcg_random_t { + rng_uint state = {}; + rng_uint inc = {}; + }; + + using State = pcg_random_t; + State rng = {}; + + State Seed(rng_uint s, rng_uint i); + // Generate another RNG struct with data generated via this one + State Seed(); + // Produces an unsigned result within the full range of the data type + rng_out Generate(); + // Produces an unsigned result within [0, range) + rng_out GenerateBounded(rng_out range); + + RNG() : rng() {}; + + RNG& operator=(const State& pcg_state) { + rng = pcg_state; + return *this; + }; + + RNG(State pcg_state) { + rng = pcg_state; + }; + + ~RNG() = default; + }; + } +} \ No newline at end of file diff --git a/src/displayapp/Controllers.h b/src/displayapp/Controllers.h index 223c7c699e..0a375f51c7 100644 --- a/src/displayapp/Controllers.h +++ b/src/displayapp/Controllers.h @@ -26,6 +26,7 @@ namespace Pinetime { class Timer; class MusicService; class NavigationService; + class RNG; } namespace System { @@ -53,6 +54,7 @@ namespace Pinetime { Pinetime::Components::LittleVgl& lvgl; Pinetime::Controllers::MusicService* musicService; Pinetime::Controllers::NavigationService* navigationService; + Pinetime::Controllers::RNG* prngController; }; } } diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 35330fb7f7..1065347843 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -717,6 +717,7 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) { void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) { this->systemTask = systemTask; this->controllers.systemTask = systemTask; + this->controllers.prngController = &(systemTask->prngController); } void DisplayApp::Register(Pinetime::Controllers::SimpleWeatherService* weatherService) { diff --git a/src/displayapp/screens/Dice.cpp b/src/displayapp/screens/Dice.cpp index 302c5f3fb2..270f15f486 100644 --- a/src/displayapp/screens/Dice.cpp +++ b/src/displayapp/screens/Dice.cpp @@ -41,13 +41,10 @@ namespace { Dice::Dice(Controllers::MotionController& motionController, Controllers::MotorController& motorController, - Controllers::Settings& settingsController) + Controllers::Settings& settingsController, + Controllers::RNG& prngController) : motorController {motorController}, motionController {motionController}, settingsController {settingsController} { - std::seed_seq sseq {static_cast(xTaskGetTickCount()), - static_cast(motionController.X()), - static_cast(motionController.Y()), - static_cast(motionController.Z())}; - gen.seed(sseq); + rng = prngController.Seed(); lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20, LV_COLOR_WHITE, @@ -79,8 +76,7 @@ Dice::Dice(Controllers::MotionController& motionController, lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); dCounter.SetValue(6); - std::uniform_int_distribution<> distrib(0, resultColors.size() - 1); - currentColorIndex = distrib(gen); + currentColorIndex = rng.GenerateBounded(resultColors.size()); resultTotalLabel = MakeLabel(&jetbrains_mono_42, resultColors[currentColorIndex], @@ -157,12 +153,10 @@ void Dice::Refresh() { void Dice::Roll() { uint8_t resultIndividual; uint16_t resultTotal = 0; - std::uniform_int_distribution<> distrib(1, dCounter.GetValue()); - lv_label_set_text(resultIndividualLabel, ""); if (nCounter.GetValue() == 1) { - resultTotal = distrib(gen); + resultTotal = rng.GenerateBounded(dCounter.GetValue()) + 1; if (dCounter.GetValue() == 2) { switch (resultTotal) { case 1: @@ -175,7 +169,7 @@ void Dice::Roll() { } } else { for (uint8_t i = 0; i < nCounter.GetValue(); i++) { - resultIndividual = distrib(gen); + resultIndividual = rng.GenerateBounded(dCounter.GetValue()) + 1; resultTotal += resultIndividual; lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str()); if (i < (nCounter.GetValue() - 1)) { diff --git a/src/displayapp/screens/Dice.h b/src/displayapp/screens/Dice.h index d12848d3cf..4f5a287413 100644 --- a/src/displayapp/screens/Dice.h +++ b/src/displayapp/screens/Dice.h @@ -4,10 +4,11 @@ #include "displayapp/screens/Screen.h" #include "displayapp/widgets/Counter.h" #include "displayapp/Controllers.h" +#include "components/rng/PCG.h" #include "Symbols.h" #include -#include +#include namespace Pinetime { namespace Applications { @@ -16,7 +17,8 @@ namespace Pinetime { public: Dice(Controllers::MotionController& motionController, Controllers::MotorController& motorController, - Controllers::Settings& settingsController); + Controllers::Settings& settingsController, + Controllers::RNG& prngController); ~Dice() override; void Roll(); void Refresh() override; @@ -29,7 +31,7 @@ namespace Pinetime { lv_task_t* refreshTask; bool enableShakeForDice = false; - std::mt19937 gen; + Controllers::RNG rng; std::array resultColors = {LV_COLOR_YELLOW, LV_COLOR_MAGENTA, LV_COLOR_AQUA}; uint8_t currentColorIndex; @@ -54,7 +56,10 @@ namespace Pinetime { static constexpr const char* icon = Screens::Symbols::dice; static Screens::Screen* Create(AppControllers& controllers) { - return new Screens::Dice(controllers.motionController, controllers.motorController, controllers.settingsController); + return new Screens::Dice(controllers.motionController, + controllers.motorController, + controllers.settingsController, + *controllers.prngController); }; static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { diff --git a/src/displayapp/screens/Twos.cpp b/src/displayapp/screens/Twos.cpp index 6f2eff40c4..6cd7a96d88 100644 --- a/src/displayapp/screens/Twos.cpp +++ b/src/displayapp/screens/Twos.cpp @@ -5,7 +5,8 @@ using namespace Pinetime::Applications::Screens; -Twos::Twos() { +Twos::Twos(Pinetime::Controllers::RNG& prngController) { + rng = prngController.Seed(); struct colorPair { lv_color_t bg; @@ -86,9 +87,9 @@ bool Twos::placeNewTile() { return false; // game lost } - int random = rand() % nEmpty; + int random = rng.GenerateBounded(nEmpty); - if ((rand() % 100) < 90) { + if (rng.GenerateBounded(100) < 90) { grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 2; } else { grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 4; diff --git a/src/displayapp/screens/Twos.h b/src/displayapp/screens/Twos.h index 9ebd7f2e44..886b82e559 100644 --- a/src/displayapp/screens/Twos.h +++ b/src/displayapp/screens/Twos.h @@ -4,6 +4,8 @@ #include "displayapp/screens/Screen.h" #include "displayapp/Controllers.h" +#include "displayapp/screens/Dice.h" + namespace Pinetime { namespace Applications { struct TwosTile { @@ -14,7 +16,7 @@ namespace Pinetime { namespace Screens { class Twos : public Screen { public: - Twos(); + Twos(Controllers::RNG& prngController); ~Twos() override; bool OnTouchEvent(TouchEvents event) override; @@ -29,6 +31,7 @@ namespace Pinetime { static constexpr int nRows = 4; static constexpr int nCells = nCols * nRows; TwosTile grid[nRows][nCols]; + Controllers::RNG rng; unsigned int score = 0; void updateGridDisplay(); bool tryMerge(int newRow, int newCol, int oldRow, int oldCol); @@ -42,8 +45,8 @@ namespace Pinetime { static constexpr Apps app = Apps::Twos; static constexpr const char* icon = "2"; - static Screens::Screen* Create(AppControllers& /*controllers*/) { - return new Screens::Twos(); + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Twos(*controllers.prngController); }; static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { diff --git a/src/main.cpp b/src/main.cpp index d0ab3e4887..3bbab2fc14 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -37,6 +37,7 @@ #include "components/heartrate/HeartRateController.h" #include "components/stopwatch/StopWatchController.h" #include "components/fs/FS.h" +#include "components/rng/PCG.h" #include "drivers/Spi.h" #include "drivers/SpiMaster.h" #include "drivers/SpiNorFlash.h" @@ -361,8 +362,12 @@ int main() { } systemTask.Start(); - nimble_port_init(); + // ble_ll functions should probably be called after ble_ll_init which is called from nimble_port_init + Pinetime::Controllers::RNG::State prngBleInit; + ble_ll_rand_data_get((uint8_t*) &prngBleInit, sizeof(prngBleInit)); + prngBleInit.state ^= (decltype(prngBleInit.state)) xTaskGetTickCount(); + systemTask.prngController.rng = prngBleInit; vTaskStartScheduler(); diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 56bf9273e1..9b3b9504a9 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -82,7 +82,8 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, spiNorFlash, heartRateController, motionController, - fs) { + fs), + prngController {} { } void SystemTask::Start() { diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index 606ddd3492..b3b2ab2709 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -18,6 +18,7 @@ #include "components/stopwatch/StopWatchController.h" #include "components/alarm/AlarmController.h" #include "components/fs/FS.h" +#include "components/rng/PCG.h" #include "touchhandler/TouchHandler.h" #include "buttonhandler/ButtonHandler.h" #include "buttonhandler/ButtonActions.h" @@ -128,6 +129,10 @@ namespace Pinetime { Pinetime::Controllers::ButtonHandler& buttonHandler; Pinetime::Controllers::NimbleController nimbleController; + public: + Pinetime::Controllers::RNG prngController; + + private: static void Process(void* instance); void Work(); bool isBleDiscoveryTimerRunning = false; From c0837487f57fb4a5e2fb62aa0567e5d38ef920e7 Mon Sep 17 00:00:00 2001 From: Andersama Date: Fri, 26 Dec 2025 14:47:40 -0800 Subject: [PATCH 2/3] Use pcg-cpp git repo as submodule --- .gitmodules | 3 ++ src/CMakeLists.txt | 2 +- src/components/rng/PCG.cpp | 76 +++----------------------------------- src/components/rng/PCG.h | 10 +++-- src/main.cpp | 4 +- src/pcg-cpp | 1 + 6 files changed, 20 insertions(+), 76 deletions(-) create mode 160000 src/pcg-cpp diff --git a/.gitmodules b/.gitmodules index a6b4d5fd6f..785e59164e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "src/libs/arduinoFFT"] path = src/libs/arduinoFFT url = https://github.com/kosme/arduinoFFT.git +[submodule "src/pcg-cpp"] + path = src/pcg-cpp + url = https://github.com/imneme/pcg-cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0496aee6a5..be36611718 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.10) project(pinetime-app C CXX ASM) - # define some variables just for this example to determine file locations set(NRF_PROJECT_NAME pinetime-app) set(NRF_BOARD pca10040) @@ -685,6 +684,7 @@ set(INCLUDE_FILES buttonhandler/ButtonHandler.h touchhandler/TouchHandler.h utility/Math.h + pcg-cpp/include/pcg_random.hpp ) include_directories( diff --git a/src/components/rng/PCG.cpp b/src/components/rng/PCG.cpp index 46d9f1169e..30b32c3f0d 100644 --- a/src/components/rng/PCG.cpp +++ b/src/components/rng/PCG.cpp @@ -2,85 +2,21 @@ Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed(Pinetime::Controllers::RNG::rng_uint s, Pinetime::Controllers::RNG::rng_uint i) { - rng.state = 0u; - rng.inc = i | 1u; - Generate(); - rng.state += s; - Generate(); - return rng; + return rng = State(s, i); } Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed() { using namespace Pinetime::Controllers; - Pinetime::Controllers::RNG::State new_rng; - new_rng.state = (RNG::rng_uint) Generate() << (sizeof(new_rng.state) * 4) ^ (RNG::rng_uint) Generate(); - new_rng.inc = (RNG::rng_uint) Generate() << (sizeof(new_rng.inc) * 4) ^ (RNG::rng_uint) Generate(); + Pinetime::Controllers::RNG::State new_rng((uint64_t) Generate() << 32 ^ (uint64_t) Generate(), + (uint64_t) Generate() << 32 ^ (uint64_t) Generate()); return new_rng; } -// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org -// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website) -// Website: https://www.pcg-random.org/download.html -// See: https://www.apache.org/licenses/GPL-compatibility.html -/* - uint64_t oldstate = rng.state; - // Advance internal state - rng.state = oldstate * 6364136223846793005ULL + (rng.inc | 1); - // Calculate output function (XSH RR), uses old state for max ILP - uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; - uint32_t rot = oldstate >> 59u; - return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); -*/ Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::Generate() { - using namespace Pinetime::Controllers; - // See magic numbers in https://github.com/imneme/pcg-cpp/blob/master/include/pcg_random.hpp - constexpr uint32_t tbits = sizeof(rng.state) * 8; - constexpr uint32_t sbits = sizeof(rng) * 8; - constexpr uint32_t obits = sizeof(RNG::rng_out) * 8; - - constexpr rng_uint multiplier = tbits >= 64 ? 1442695040888963407ULL : tbits >= 32 ? 747796405U : tbits >= 16 ? 12829U : 141u; - - constexpr uint32_t opbits = sbits - 5 >= 64 ? 5u : // 128 bits -> 5 - sbits - 4 >= 32 ? 4u - : // 64 bits -> 4 - sbits - 3 >= 16 ? 3u - : // 32 bits -> 3 - sbits - 2 >= 8 ? 2u - : // 16 bits -> 2 - sbits - 1 >= 1 ? 1u - : // 8 bits -> 1 - 0u; - - auto oldstate = rng.state; - rng.state = oldstate * multiplier + (rng.inc | 1); - // 64 bits of state, 32 output (64 - 5 = 59, 32 - 5 = 27 and floor((5+32)/2) = 18) - // 2^5 = 32* - // output = rotate(state ^ (state >> s1)) >> s2, state >> s3) - // 32 bits of state, 16 output (32 - 4 = 28, 16 - 4 = 12, and floor((4 + 16)/2) = 10) - // 2^4 = 16* - constexpr uint32_t s3 = tbits - opbits; - constexpr uint32_t s2 = obits - opbits; - constexpr uint32_t s1 = (obits + 5) / 2; - - constexpr RNG::rng_out mask = (1 << opbits) - 1; - RNG::rng_out xorshifted = ((oldstate >> s1) ^ oldstate) >> s2; - rng_out rot = oldstate >> s3; - // rotate - return (xorshifted >> rot) | (xorshifted << ((-rot) & mask)); + return rng(); }; -// Lemire's Method (slight rewrite) [0, range) +// See pcg-cpp/sample/codebook.cpp Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::GenerateBounded(Pinetime::Controllers::RNG::rng_out range) { - using namespace Pinetime::Controllers; - rng_uint m; - RNG::rng_out t = (-range) % range; - RNG::rng_out l; - - do { - RNG::rng_out x = Generate(); - m = RNG::rng_uint(x) * RNG::rng_uint(range); - l = RNG::rng_out(m); - } while (l < t); - - return m >> (sizeof(RNG::rng_out) * 8); + return rng(range); }; \ No newline at end of file diff --git a/src/components/rng/PCG.h b/src/components/rng/PCG.h index 5dba5bb3b2..40b431c94e 100644 --- a/src/components/rng/PCG.h +++ b/src/components/rng/PCG.h @@ -3,20 +3,22 @@ #include #include #include "components/motion/MotionController.h" +#include "pcg-cpp/include/pcg_random.hpp" namespace Pinetime { namespace Controllers { struct RNG { - using rng_uint = uint32_t; - using rng_uint2 = uint64_t; - using rng_out = uint16_t; + /* struct pcg_random_t { rng_uint state = {}; rng_uint inc = {}; }; + */ + using State = pcg32; + using rng_uint = State::state_type;// uint32_t; + using rng_out = State::result_type;// uint16_t; - using State = pcg_random_t; State rng = {}; State Seed(rng_uint s, rng_uint i); diff --git a/src/main.cpp b/src/main.cpp index 3bbab2fc14..ce087e725e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -366,7 +366,9 @@ int main() { // ble_ll functions should probably be called after ble_ll_init which is called from nimble_port_init Pinetime::Controllers::RNG::State prngBleInit; ble_ll_rand_data_get((uint8_t*) &prngBleInit, sizeof(prngBleInit)); - prngBleInit.state ^= (decltype(prngBleInit.state)) xTaskGetTickCount(); + // TODO: Seed with lifetime stats + *((uint32_t*) &prngBleInit) ^= xTaskGetTickCount(); + prngBleInit(); systemTask.prngController.rng = prngBleInit; vTaskStartScheduler(); diff --git a/src/pcg-cpp b/src/pcg-cpp new file mode 160000 index 0000000000..428802d1a5 --- /dev/null +++ b/src/pcg-cpp @@ -0,0 +1 @@ +Subproject commit 428802d1a5634f96bcd0705fab379ff0113bcf13 From 1be0d2bb60a5c228150004f6697d3fbb815e10ca Mon Sep 17 00:00:00 2001 From: Andersama Date: Sun, 28 Dec 2025 01:01:23 -0800 Subject: [PATCH 3/3] Rewrite rng component using as reference Incorporate interface for 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 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. --- src/CMakeLists.txt | 1 - src/components/rng/PCG.cpp | 113 +++++++++++++++++++++++++++----- src/components/rng/PCG.h | 93 ++++++++++++++++++-------- src/displayapp/Controllers.h | 2 +- src/displayapp/screens/Dice.cpp | 8 +-- src/displayapp/screens/Twos.cpp | 6 +- src/main.cpp | 5 +- src/pcg-cpp | 1 - 8 files changed, 172 insertions(+), 57 deletions(-) delete mode 160000 src/pcg-cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index be36611718..8e9c3f14f9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -684,7 +684,6 @@ set(INCLUDE_FILES buttonhandler/ButtonHandler.h touchhandler/TouchHandler.h utility/Math.h - pcg-cpp/include/pcg_random.hpp ) include_directories( diff --git a/src/components/rng/PCG.cpp b/src/components/rng/PCG.cpp index 30b32c3f0d..a046dfb5b4 100644 --- a/src/components/rng/PCG.cpp +++ b/src/components/rng/PCG.cpp @@ -1,22 +1,105 @@ #include "components/rng/PCG.h" -Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed(Pinetime::Controllers::RNG::rng_uint s, - Pinetime::Controllers::RNG::rng_uint i) { - return rng = State(s, i); -} +using namespace Pinetime::Controllers; -Pinetime::Controllers::RNG::State Pinetime::Controllers::RNG::Seed() { - using namespace Pinetime::Controllers; - Pinetime::Controllers::RNG::State new_rng((uint64_t) Generate() << 32 ^ (uint64_t) Generate(), - (uint64_t) Generate() << 32 ^ (uint64_t) Generate()); - return new_rng; -} +RNG::RNG(result_type value) { + // pcg32_srandom_r(&rng, 0x853c49e6748fea9bULL, value); + rng.state = 0U; + rng.inc = (value << 1u) | 1u; + (*this)(); // pcg32_random_r(rng); + rng.state += 0x853c49e6748fea9bULL; + (*this)(); // pcg32_random_r(rng); +}; + +RNG& RNG::operator=(const pcg32_random_t& other) { + rng.state = other.state; + rng.inc = other.inc | 1; + return *this; +}; + +template +RNG::RNG(SeedSeq& seq) { + seed(seq); +}; + +/* +RNG::RNG(const RNG& other) { + RNG tmp = other; + RNG::result_type* ptr = (RNG::result_type*) this; + tmp.generate(ptr, ptr + (sizeof(RNG) / sizeof(RNG::result_type))); +}; +*/ +template +void RNG::seed(SeedSeq& seq) { + RNG::result_type* ptr = (RNG::result_type*) this; + seq.generate(ptr, ptr + (sizeof(RNG) / sizeof(RNG::result_type))); + rng.inc |= 1; +}; + +void RNG::seed(RNG& other) { + RNG::result_type* ptr = (RNG::result_type*) this; + other.generate(ptr, ptr + (sizeof(RNG) / sizeof(RNG::result_type))); + rng.inc |= 1; +}; + +RNG::result_type RNG::operator()() { + // return pcg32_random_r(&rng); + uint64_t oldstate = rng.state; + rng.state = oldstate * 6364136223846793005ULL + rng.inc; + uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + uint32_t rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +}; + +RNG::result_type RNG::operator()(RNG::result_type bound) { + // return pcg32_boundedrand_r(&rng, bounds); + // To avoid bias, we need to make the range of the RNG a multiple of + // bound, which we do by dropping output less than a threshold. + // A naive scheme to calculate the threshold would be to do + // + // uint32_t threshold = 0x100000000ull % bound; + // + // but 64-bit div/mod is slower than 32-bit div/mod (especially on + // 32-bit platforms). In essence, we do + // + // uint32_t threshold = (0x100000000ull-bound) % bound; + // + // because this version will calculate the same modulus, but the LHS + // value is less than 2^32. + + uint32_t threshold = -bound % bound; + + // Uniformity guarantees that this loop will terminate. In practice, it + // should usually terminate quickly; on average (assuming all bounds are + // equally likely), 82.25% of the time, we can expect it to require just + // one iteration. In the worst case, someone passes a bound of 2^31 + 1 + // (i.e., 2147483649), which invalidates almost 50% of the range. In + // practice, bounds are typically small and only a tiny amount of the range + // is eliminated. + for (;;) { + uint32_t r = (*this)(); // pcg32_random_r(rng); + if (r >= threshold) + return r % bound; + } +}; + +// std::seed_seq interface +template +void RNG::generate(It start, It end) { + for (; start != end; ++start) + *start = (*this)(); +}; -Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::Generate() { - return rng(); +std::size_t RNG::size() const noexcept { + return sizeof(rng) / sizeof(RNG::result_type); }; -// See pcg-cpp/sample/codebook.cpp -Pinetime::Controllers::RNG::rng_out Pinetime::Controllers::RNG::GenerateBounded(Pinetime::Controllers::RNG::rng_out range) { - return rng(range); +template +void RNG::param(OutputIt dest) const { + std::size_t i = 0; + const std::size_t n = size(); + RNG::result_type* ptr = (RNG::result_type*) this; + for (; i < n; ++i, ++dest, ++ptr) { + *dest = ptr; + } }; \ No newline at end of file diff --git a/src/components/rng/PCG.h b/src/components/rng/PCG.h index 40b431c94e..aa959fc1fd 100644 --- a/src/components/rng/PCG.h +++ b/src/components/rng/PCG.h @@ -3,44 +3,79 @@ #include #include #include "components/motion/MotionController.h" -#include "pcg-cpp/include/pcg_random.hpp" namespace Pinetime { namespace Controllers { + /* + * PCG Random Number Generation for C. + * + * Copyright 2014 Melissa O'Neill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For additional information about the PCG random number generation scheme, + * including its license and other licensing options, visit + * + * http://www.pcg-random.org + */ + + struct pcg_state_setseq_64 { // Internals are *Private*. + uint64_t state = 0x853c49e6748fea9bULL; // RNG state. All values are possible. + uint64_t inc = 0xda3e39cb94b95bdbULL; // Controls which RNG sequence (stream) is + // selected. Must *always* be odd. + // pcg_state_setseq_64() = default; + // pcg_state_setseq_64(uint64_t s, uint64_t i); + }; + typedef struct pcg_state_setseq_64 pcg32_random_t; + struct RNG { + pcg32_random_t rng = {}; + // interface + using result_type = uint32_t; - /* - struct pcg_random_t { - rng_uint state = {}; - rng_uint inc = {}; - }; - */ - using State = pcg32; - using rng_uint = State::state_type;// uint32_t; - using rng_out = State::result_type;// uint16_t; - - State rng = {}; - - State Seed(rng_uint s, rng_uint i); - // Generate another RNG struct with data generated via this one - State Seed(); - // Produces an unsigned result within the full range of the data type - rng_out Generate(); - // Produces an unsigned result within [0, range) - rng_out GenerateBounded(rng_out range); - - RNG() : rng() {}; - - RNG& operator=(const State& pcg_state) { - rng = pcg_state; - return *this; + static constexpr result_type min() { + return result_type {}; }; - RNG(State pcg_state) { - rng = pcg_state; + static constexpr result_type max() { + return ~result_type {0UL}; }; - ~RNG() = default; + // Constructors + // RNG(); + RNG() = default; + explicit RNG(result_type value); + + template + explicit RNG(SeedSeq& seq); + + RNG& operator=(const pcg32_random_t& other); + + template + void seed(SeedSeq& seq); + + void seed(RNG& other); + + result_type operator()(); + result_type operator()(result_type bound); + // std::seed_seq interface + template + void generate(It start, It end); + + std::size_t size() const noexcept; + + template + void param(OutputIt dest) const; }; } } \ No newline at end of file diff --git a/src/displayapp/Controllers.h b/src/displayapp/Controllers.h index 0a375f51c7..0151f1b7fa 100644 --- a/src/displayapp/Controllers.h +++ b/src/displayapp/Controllers.h @@ -26,7 +26,7 @@ namespace Pinetime { class Timer; class MusicService; class NavigationService; - class RNG; + struct RNG; } namespace System { diff --git a/src/displayapp/screens/Dice.cpp b/src/displayapp/screens/Dice.cpp index 270f15f486..e2cc364dea 100644 --- a/src/displayapp/screens/Dice.cpp +++ b/src/displayapp/screens/Dice.cpp @@ -44,7 +44,7 @@ Dice::Dice(Controllers::MotionController& motionController, Controllers::Settings& settingsController, Controllers::RNG& prngController) : motorController {motorController}, motionController {motionController}, settingsController {settingsController} { - rng = prngController.Seed(); + rng.seed(prngController); lv_obj_t* nCounterLabel = MakeLabel(&jetbrains_mono_bold_20, LV_COLOR_WHITE, @@ -76,7 +76,7 @@ Dice::Dice(Controllers::MotionController& motionController, lv_obj_align(dCounter.GetObject(), dCounterLabel, LV_ALIGN_OUT_BOTTOM_MID, 0, 10); dCounter.SetValue(6); - currentColorIndex = rng.GenerateBounded(resultColors.size()); + currentColorIndex = rng(resultColors.size()); resultTotalLabel = MakeLabel(&jetbrains_mono_42, resultColors[currentColorIndex], @@ -156,7 +156,7 @@ void Dice::Roll() { lv_label_set_text(resultIndividualLabel, ""); if (nCounter.GetValue() == 1) { - resultTotal = rng.GenerateBounded(dCounter.GetValue()) + 1; + resultTotal = rng(dCounter.GetValue()) + 1; if (dCounter.GetValue() == 2) { switch (resultTotal) { case 1: @@ -169,7 +169,7 @@ void Dice::Roll() { } } else { for (uint8_t i = 0; i < nCounter.GetValue(); i++) { - resultIndividual = rng.GenerateBounded(dCounter.GetValue()) + 1; + resultIndividual = rng(dCounter.GetValue()) + 1; resultTotal += resultIndividual; lv_label_ins_text(resultIndividualLabel, LV_LABEL_POS_LAST, std::to_string(resultIndividual).c_str()); if (i < (nCounter.GetValue() - 1)) { diff --git a/src/displayapp/screens/Twos.cpp b/src/displayapp/screens/Twos.cpp index 6cd7a96d88..b20ca40f08 100644 --- a/src/displayapp/screens/Twos.cpp +++ b/src/displayapp/screens/Twos.cpp @@ -6,7 +6,7 @@ using namespace Pinetime::Applications::Screens; Twos::Twos(Pinetime::Controllers::RNG& prngController) { - rng = prngController.Seed(); + rng.seed(prngController); // Pinetime::Controllers::RNG {prngController(), prngController()}; // = prngController.Seed(); struct colorPair { lv_color_t bg; @@ -87,9 +87,9 @@ bool Twos::placeNewTile() { return false; // game lost } - int random = rng.GenerateBounded(nEmpty); + int random = rng(nEmpty); - if (rng.GenerateBounded(100) < 90) { + if (rng(100) < 90) { grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 2; } else { grid[emptyCells[random] / nCols][emptyCells[random] % nCols].value = 4; diff --git a/src/main.cpp b/src/main.cpp index ce087e725e..237d75ab3d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -364,12 +364,11 @@ int main() { systemTask.Start(); nimble_port_init(); // ble_ll functions should probably be called after ble_ll_init which is called from nimble_port_init - Pinetime::Controllers::RNG::State prngBleInit; + Pinetime::Controllers::RNG prngBleInit; ble_ll_rand_data_get((uint8_t*) &prngBleInit, sizeof(prngBleInit)); // TODO: Seed with lifetime stats *((uint32_t*) &prngBleInit) ^= xTaskGetTickCount(); - prngBleInit(); - systemTask.prngController.rng = prngBleInit; + systemTask.prngController = prngBleInit; vTaskStartScheduler(); diff --git a/src/pcg-cpp b/src/pcg-cpp deleted file mode 160000 index 428802d1a5..0000000000 --- a/src/pcg-cpp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 428802d1a5634f96bcd0705fab379ff0113bcf13