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 e4a354df64..8e9c3f14f9 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) @@ -473,6 +472,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 +663,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..3008230702 --- /dev/null +++ b/src/components/rng/PCG.cpp @@ -0,0 +1,94 @@ +#include "components/rng/PCG.h" + +using namespace Pinetime::Controllers; + +RNG::RNG() : rng {0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL} { +} + +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); +} + +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::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)(); +}; + +std::size_t RNG::size() const noexcept { + return sizeof(rng) / sizeof(RNG::result_type); +}; + +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 new file mode 100644 index 0000000000..0bdf48570a --- /dev/null +++ b/src/components/rng/PCG.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include +#include "components/motion/MotionController.h" + +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; // RNG state. All values are possible. + uint64_t inc; // Controls which RNG sequence (stream) is + // selected. Must *always* be odd. + }; + typedef struct pcg_state_setseq_64 pcg32_random_t; + + struct RNG { + pcg32_random_t rng; + // interface + using result_type = uint32_t; + + static constexpr result_type min() { + return result_type {}; + }; + + static constexpr result_type max() { + return ~result_type {0UL}; + }; + + // Constructors + RNG(); + explicit RNG(result_type value); + + template + explicit RNG(SeedSeq& seq); + + RNG(const RNG& other); + + template + void seed(SeedSeq& seq); + + 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 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..e2cc364dea 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.seed(prngController); 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(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(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(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..b20ca40f08 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.seed(prngController); // Pinetime::Controllers::RNG {prngController(), prngController()}; // = 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(nEmpty); - if ((rand() % 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/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..16b1351b1b 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,14 @@ 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 prngBleInit; + ble_ll_rand_data_get((uint8_t*) &prngBleInit, sizeof(prngBleInit)); + // TODO: Seed with lifetime stats + *((uint32_t*) &prngBleInit) ^= xTaskGetTickCount(); + prngBleInit(); + systemTask.prngController = 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 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;