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;