From 2757aa272bc3f8af733f693f3b4919f74726f459 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 1 Jul 2023 11:20:39 +0200 Subject: [PATCH 01/29] Revert `compute_scheduled_recurrent_grain` --- cores/esp8266/Schedule.cpp | 35 ----------------------------- cores/esp8266/Schedule.h | 5 ----- cores/esp8266/core_esp8266_main.cpp | 15 ++----------- 3 files changed, 2 insertions(+), 53 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index f6c650fcf9..279e78ff6c 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -17,7 +17,6 @@ */ #include -#include #include "Schedule.h" #include "PolledTimeout.h" @@ -35,7 +34,6 @@ static scheduled_fn_t* sFirst = nullptr; static scheduled_fn_t* sLast = nullptr; static scheduled_fn_t* sUnused = nullptr; static int sCount = 0; -static uint32_t recurrent_max_grain_mS = 0; typedef std::function mRecFuncT; struct recurrent_fn_t @@ -132,39 +130,9 @@ bool schedule_recurrent_function_us(const std::function& fn, } rLast = item; - // grain needs to be recomputed - recurrent_max_grain_mS = 0; - return true; } -uint32_t compute_scheduled_recurrent_grain () -{ - if (recurrent_max_grain_mS == 0) - { - if (rFirst) - { - uint32_t recurrent_max_grain_uS = rFirst->callNow.getTimeout(); - for (auto it = rFirst->mNext; it; it = it->mNext) - recurrent_max_grain_uS = std::gcd(recurrent_max_grain_uS, it->callNow.getTimeout()); - if (recurrent_max_grain_uS) - // round to the upper millis - recurrent_max_grain_mS = recurrent_max_grain_uS <= 1000? 1: (recurrent_max_grain_uS + 999) / 1000; - } - -#ifdef DEBUG_ESP_CORE - static uint32_t last_grain = 0; - if (recurrent_max_grain_mS != last_grain) - { - ::printf(":rsf %u->%u\n", last_grain, recurrent_max_grain_mS); - last_grain = recurrent_max_grain_mS; - } -#endif - } - - return recurrent_max_grain_mS; -} - void run_scheduled_functions() { // prevent scheduling of new functions during this run @@ -258,9 +226,6 @@ void run_scheduled_recurrent_functions() } delete(to_ditch); - - // grain needs to be recomputed - recurrent_max_grain_mS = 0; } else { diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index 362d15b5f3..da86e5b7f5 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -39,11 +39,6 @@ // scheduled function happen more often: every yield() (vs every loop()), // and time resolution is microsecond (vs millisecond). Details are below. -// compute_scheduled_recurrent_grain() is used by delay() to give a chance to -// all recurrent functions to run per their timing requirement. - -uint32_t compute_scheduled_recurrent_grain (); - // scheduled functions called once: // // * internal queue is FIFO. diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index d0309cc71f..319c2f041a 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -22,9 +22,6 @@ //This may be used to change user task stack size: //#define CONT_STACKSIZE 4096 - -#include - #include #include "Schedule.h" extern "C" { @@ -175,16 +172,8 @@ bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uin if (expired >= timeout_ms) { return true; // expired } - - // compute greatest chunked delay with respect to scheduled recurrent functions - uint32_t grain_ms = std::gcd(intvl_ms, compute_scheduled_recurrent_grain()); - - // recurrent scheduled functions will be called from esp_delay()->esp_suspend() - esp_delay(grain_ms > 0 ? - std::min((timeout_ms - expired), grain_ms): - (timeout_ms - expired)); - - return false; // expiration must be checked again + esp_delay(std::min((timeout_ms - expired), intvl_ms)); + return false; } extern "C" void __yield() { From a81e544002dfbe9011dc0c5dca893a989359cb76 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 1 Jul 2023 12:05:59 +0200 Subject: [PATCH 02/29] wip --- cores/esp8266/Schedule.cpp | 10 ++++++++++ cores/esp8266/Schedule.h | 5 +++++ cores/esp8266/core_esp8266_main.cpp | 12 ++++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 279e78ff6c..85604d6ad7 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -47,6 +47,7 @@ struct recurrent_fn_t static recurrent_fn_t* rFirst = nullptr; static recurrent_fn_t* rLast = nullptr; +static uint32_t rScheduleTarget = 0; // Returns a pointer to an unused sched_fn_t, // or if none are available allocates a new one, @@ -117,6 +118,10 @@ bool schedule_recurrent_function_us(const std::function& fn, item->mFunc = fn; item->alarm = alarm; +// if (!rScheduleTarget || rScheduleTarget > item.callNow.remaining()) +// { +// rScheduleTarget = item.callNow.remaining(); +// } esp8266::InterruptLock lockAllInterruptsInThisScope; @@ -133,6 +138,11 @@ bool schedule_recurrent_function_us(const std::function& fn, return true; } +uint32_t compute_scheduled_recurrent_grain() +{ + return 0; +} + void run_scheduled_functions() { // prevent scheduling of new functions during this run diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index da86e5b7f5..362d15b5f3 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -39,6 +39,11 @@ // scheduled function happen more often: every yield() (vs every loop()), // and time resolution is microsecond (vs millisecond). Details are below. +// compute_scheduled_recurrent_grain() is used by delay() to give a chance to +// all recurrent functions to run per their timing requirement. + +uint32_t compute_scheduled_recurrent_grain (); + // scheduled functions called once: // // * internal queue is FIFO. diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 319c2f041a..aaa3513bd0 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -172,8 +172,16 @@ bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uin if (expired >= timeout_ms) { return true; // expired } - esp_delay(std::min((timeout_ms - expired), intvl_ms)); - return false; + + // compute greatest delay interval with respect to scheduled recurrent functions + uint32_t grain_ms = std::min(intvl_ms, compute_scheduled_recurrent_grain()); + + // recurrent scheduled functions will be called from esp_delay()->esp_suspend() + esp_delay(grain_ms > 0 ? + std::min((timeout_ms - expired), grain_ms) : + (timeout_ms - expired)); + + return false; // expiration must be checked again } extern "C" void __yield() { From 588e153337c2160f79345b92fd04948270223c69 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 26 Nov 2019 22:13:56 +0100 Subject: [PATCH 03/29] Add function to retrieve the time remaining until the (next) timeout occurs. --- cores/esp8266/PolledTimeout.h | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 1252ce6c29..69e7720e0a 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -224,6 +224,14 @@ class timeoutTemplate return TimePolicyT::toUserUnit(_timeout); } + IRAM_ATTR // fast + timeType expiresIn() + { + if (_neverExpires) return timeMax(); + if (expired()) return TimePolicyT::toUserUnit(0); + return TimePolicyT::toUserUnit(_timeout - (_current - _start)); + } + static constexpr timeType timeMax() { return TimePolicyT::timeMax; @@ -232,11 +240,11 @@ class timeoutTemplate private: IRAM_ATTR // fast - bool checkExpired(const timeType internalUnit) const + bool checkExpired() const { // canWait() is not checked here // returns "can expire" and "time expired" - return (!_neverExpires) && ((internalUnit - _start) >= _timeout); + return (!_neverExpires) && ((_current - _start) >= _timeout); } protected: @@ -247,10 +255,10 @@ class timeoutTemplate if (!canWait()) return true; - timeType current = TimePolicyT::time(); - if(checkExpired(current)) + _current = TimePolicyT::time(); + if(checkExpired()) { - unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) + unsigned long n = (_current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (_current - _start >= _timeout) _start += n * _timeout; return true; } @@ -258,14 +266,16 @@ class timeoutTemplate } IRAM_ATTR // fast - bool expiredOneShot() const + bool expiredOneShot() { + _current = TimePolicyT::time(); // returns "always expired" or "has expired" - return !canWait() || checkExpired(TimePolicyT::time()); + return !canWait() || checkExpired(); } timeType _timeout; timeType _start; + timeType _current; bool _neverExpires; }; From a435fa58f053699cc42b4ff66b8b05c1736c3b46 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 27 Nov 2019 11:52:31 +0100 Subject: [PATCH 04/29] Rename and reformat according to review. --- cores/esp8266/PolledTimeout.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 69e7720e0a..ed2f959915 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -225,10 +225,12 @@ class timeoutTemplate } IRAM_ATTR // fast - timeType expiresIn() + timeType remaining() const { - if (_neverExpires) return timeMax(); - if (expired()) return TimePolicyT::toUserUnit(0); + if (_neverExpires) + return timeMax(); + if (expired()) + return TimePolicyT::toUserUnit(0); return TimePolicyT::toUserUnit(_timeout - (_current - _start)); } From 232961e28ec3ad0d6b4cee5a4eef18c74e6a6ab0 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 27 Nov 2019 12:17:16 +0100 Subject: [PATCH 05/29] Revert _current as member change. --- cores/esp8266/PolledTimeout.h | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index ed2f959915..0cdbc7f49d 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -228,10 +228,11 @@ class timeoutTemplate timeType remaining() const { if (_neverExpires) - return timeMax(); - if (expired()) - return TimePolicyT::toUserUnit(0); - return TimePolicyT::toUserUnit(_timeout - (_current - _start)); + return timeMax(); + timeType current = TimePolicyT::time(); + if (checkExpired(current)) + return TimePolicyT::toUserUnit(0); + return TimePolicyT::toUserUnit(_timeout - (current - _start)); } static constexpr timeType timeMax() @@ -242,11 +243,11 @@ class timeoutTemplate private: IRAM_ATTR // fast - bool checkExpired() const + bool checkExpired(const timeType internalUnit) const { // canWait() is not checked here // returns "can expire" and "time expired" - return (!_neverExpires) && ((_current - _start) >= _timeout); + return (!_neverExpires) && ((internalUnit - _start) >= _timeout); } protected: @@ -257,10 +258,10 @@ class timeoutTemplate if (!canWait()) return true; - _current = TimePolicyT::time(); - if(checkExpired()) + timeType current = TimePolicyT::time(); + if(checkExpired(current)) { - unsigned long n = (_current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (_current - _start >= _timeout) + unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) _start += n * _timeout; return true; } @@ -268,16 +269,14 @@ class timeoutTemplate } IRAM_ATTR // fast - bool expiredOneShot() + bool expiredOneShot() const { - _current = TimePolicyT::time(); // returns "always expired" or "has expired" - return !canWait() || checkExpired(); + return !canWait() || checkExpired(TimePolicyT::time()); } timeType _timeout; timeType _start; - timeType _current; bool _neverExpires; }; From 210c3d4ec8128018d086d0e6894844cfebe513e1 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 27 Nov 2019 12:21:20 +0100 Subject: [PATCH 06/29] Fix expiredOneShot to properly stay expired forever unless reset. --- cores/esp8266/PolledTimeout.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 0cdbc7f49d..15518fa2f1 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -269,10 +269,16 @@ class timeoutTemplate } IRAM_ATTR // fast - bool expiredOneShot() const + bool expiredOneShot() { // returns "always expired" or "has expired" - return !canWait() || checkExpired(TimePolicyT::time()); + return !canWait(); + if (checkExpired(TimePolicyT::time())) + { + _timeout = alwaysExpired; + return true; + } + return false; } timeType _timeout; From a6456d0a728b529e0702b4266419b6d58d955b44 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 27 Nov 2019 14:25:49 +0100 Subject: [PATCH 07/29] Fix "reverse notation return statement" --- cores/esp8266/PolledTimeout.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 15518fa2f1..5fffafc6be 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -272,7 +272,7 @@ class timeoutTemplate bool expiredOneShot() { // returns "always expired" or "has expired" - return !canWait(); + if (!canWait()) return true; if (checkExpired(TimePolicyT::time())) { _timeout = alwaysExpired; From 9863f7136b55a346b3c4eec5d1f70e6a049103eb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 27 Nov 2019 22:22:20 +0100 Subject: [PATCH 08/29] Reset() was wrecked by resetting _timeout. Reverted. --- cores/esp8266/PolledTimeout.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 5fffafc6be..27985313cd 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -177,7 +177,7 @@ class timeoutTemplate bool canWait () const { - return _timeout != alwaysExpired; + return _timeout != alwaysExpired && !_oneShotExpired; } // Resets, will trigger after this new timeout. @@ -194,6 +194,7 @@ class timeoutTemplate void reset() { _start = TimePolicyT::time(); + _oneShotExpired = false; } // Resets to just expired so that on next poll the check will immediately trigger for the user, @@ -246,8 +247,8 @@ class timeoutTemplate bool checkExpired(const timeType internalUnit) const { // canWait() is not checked here - // returns "can expire" and "time expired" - return (!_neverExpires) && ((internalUnit - _start) >= _timeout); + // returns "can expire" and "oneshot not expired" and "time expired" + return (_oneShotExpired) || ((!_neverExpires) && ((internalUnit - _start) >= _timeout)); } protected: @@ -275,7 +276,7 @@ class timeoutTemplate if (!canWait()) return true; if (checkExpired(TimePolicyT::time())) { - _timeout = alwaysExpired; + _oneShotExpired = true; return true; } return false; @@ -284,6 +285,7 @@ class timeoutTemplate timeType _timeout; timeType _start; bool _neverExpires; + bool _oneShotExpired; }; // legacy type names, deprecated (unit is milliseconds) From 9c83f9a9bd07a1ab2215f7fc8ab923e5dce5a492 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 2 Dec 2019 23:44:32 +0100 Subject: [PATCH 09/29] Expired check ALWAYS executes yield policy BEFORE checking expiration - that's wasted time. Expired keeps executing yield policy on already expired oneShot - that's counter intuitive and wasted time. --- cores/esp8266/PolledTimeout.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 27985313cd..5f63a74962 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -158,10 +158,9 @@ class timeoutTemplate IRAM_ATTR // fast bool expired() { - YieldPolicyT::execute(); //in case of DoNothing: gets optimized away - if(PeriodicT) //in case of false: gets optimized away - return expiredRetrigger(); - return expiredOneShot(); + bool hasExpired = PeriodicT ? expiredRetrigger() : expiredOneShot(); + if (!hasExpired) YieldPolicyT::execute(); //in case of DoNothing: gets optimized away + return hasExpired; } IRAM_ATTR // fast From 4cbe06bda3bba61165916ff0fd753a5d3fc0b0a9 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 3 Dec 2019 07:36:41 +0100 Subject: [PATCH 10/29] Reformat --- cores/esp8266/PolledTimeout.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 5f63a74962..542a9e126d 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -159,7 +159,8 @@ class timeoutTemplate bool expired() { bool hasExpired = PeriodicT ? expiredRetrigger() : expiredOneShot(); - if (!hasExpired) YieldPolicyT::execute(); //in case of DoNothing: gets optimized away + if (!hasExpired) //in case of DoNothing: gets optimized away + YieldPolicyT::execute(); return hasExpired; } From 20e6378c8dc3b2241b637ef6c8eed2fcddc77893 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 4 Dec 2019 13:02:20 +0100 Subject: [PATCH 11/29] Fix comment, remove redundant parentheses --- cores/esp8266/PolledTimeout.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 542a9e126d..c195e2d235 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -247,8 +247,8 @@ class timeoutTemplate bool checkExpired(const timeType internalUnit) const { // canWait() is not checked here - // returns "can expire" and "oneshot not expired" and "time expired" - return (_oneShotExpired) || ((!_neverExpires) && ((internalUnit - _start) >= _timeout)); + // returns "oneshot has expired", otherwise returns "can expire" and "time has expired" + return _oneShotExpired || (!_neverExpires && ((internalUnit - _start) >= _timeout)); } protected: From b0eb3457ff0ce066a00c466ea339b1d16a8202e4 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 15 Mar 2021 12:36:54 +0100 Subject: [PATCH 12/29] expired() is not const qualified in PolledTmeout. --- libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h | 2 +- libraries/SoftwareSerial | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h index 4a83f979ae..d1ad2ce11f 100644 --- a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h +++ b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h @@ -61,7 +61,7 @@ class ExpiringTimeTracker : private esp8266::polledTimeout::oneShotMs { * Get the time since the ExpiringTimeTracker instance creation or the last reset(), whichever is more recent. */ uint32_t elapsedTime() const; - bool expired() const; + bool expired(); void reset(); void reset(const uint32_t newDuration); void reset(const calculatorType newDurationCalculator); diff --git a/libraries/SoftwareSerial b/libraries/SoftwareSerial index bcfd6d10e6..bb133a5a17 160000 --- a/libraries/SoftwareSerial +++ b/libraries/SoftwareSerial @@ -1 +1 @@ -Subproject commit bcfd6d10e6a45a0d07705d08728f293defe9cc1d +Subproject commit bb133a5a177e64655f900a419f36c2c09b8590be From a3612ddbb332be364d23f6a7e551cc2831f9b3f7 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Tue, 16 Mar 2021 08:06:21 +0100 Subject: [PATCH 13/29] expiredOneShot() qualifed as const, despite asymmetry with expired() and expiredRetrigger(). _oneShotExpired qualified as mutable member. This is done as intermediate fix for use in derived class ESP8266WiFiMesh ExpiringTimeTracker. --- cores/esp8266/PolledTimeout.h | 4 ++-- libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index c195e2d235..4452fa03e7 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -270,7 +270,7 @@ class timeoutTemplate } IRAM_ATTR // fast - bool expiredOneShot() + bool expiredOneShot() const { // returns "always expired" or "has expired" if (!canWait()) return true; @@ -285,7 +285,7 @@ class timeoutTemplate timeType _timeout; timeType _start; bool _neverExpires; - bool _oneShotExpired; + mutable bool _oneShotExpired; }; // legacy type names, deprecated (unit is milliseconds) diff --git a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h index d1ad2ce11f..4a83f979ae 100644 --- a/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h +++ b/libraries/ESP8266WiFiMesh/src/ExpiringTimeTracker.h @@ -61,7 +61,7 @@ class ExpiringTimeTracker : private esp8266::polledTimeout::oneShotMs { * Get the time since the ExpiringTimeTracker instance creation or the last reset(), whichever is more recent. */ uint32_t elapsedTime() const; - bool expired(); + bool expired() const; void reset(); void reset(const uint32_t newDuration); void reset(const calculatorType newDurationCalculator); From c88af7b855e6450012b92dcbf9ac37f07b0b4419 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 5 Apr 2021 11:26:05 +0200 Subject: [PATCH 14/29] Remove alwaysExpired, it is identical to 0 and obfuscates the code. --- cores/esp8266/PolledTimeout.h | 5 ++--- cores/esp8266/Stream.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 4452fa03e7..0375f9095b 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -146,7 +146,6 @@ class timeoutTemplate using timeType = typename TimePolicyT::timeType; static_assert(std::is_unsigned::value == true, "timeType must be unsigned"); - static constexpr timeType alwaysExpired = 0; static constexpr timeType neverExpires = std::numeric_limits::max(); static constexpr timeType rangeCompensate = TimePolicyT::rangeCompensate; //debug @@ -177,7 +176,7 @@ class timeoutTemplate bool canWait () const { - return _timeout != alwaysExpired && !_oneShotExpired; + return _timeout != 0 && !_oneShotExpired; } // Resets, will trigger after this new timeout. @@ -216,7 +215,7 @@ class timeoutTemplate void resetToNeverExpires () { - _timeout = alwaysExpired + 1; // because canWait() has precedence + _timeout = 1; // because canWait() has precedence _neverExpires = true; } diff --git a/cores/esp8266/Stream.h b/cores/esp8266/Stream.h index 21f319ee6b..3da9e88b84 100644 --- a/cores/esp8266/Stream.h +++ b/cores/esp8266/Stream.h @@ -172,7 +172,7 @@ class Stream: public Print { // transfers already buffered / immediately available data (no timeout) // returns number of transferred bytes - [[deprecated]] size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); } + [[deprecated]] size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, 0); } [[deprecated]] size_t sendAvailable (Print& to) { return sendAvailable(&to); } // transfers data until timeout From 4eab7e4a2fb8b693fc9ac006b873bddd0afe476f Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 5 Apr 2021 11:45:59 +0200 Subject: [PATCH 15/29] Somewhat easier on the human reader. --- cores/esp8266/PolledTimeout.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 0375f9095b..10d348be25 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -176,7 +176,7 @@ class timeoutTemplate bool canWait () const { - return _timeout != 0 && !_oneShotExpired; + return !(_timeout == 0 || _oneShotExpired); } // Resets, will trigger after this new timeout. From b5285c35ea1c99224084d628795fb96073afa812 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 5 Apr 2021 12:32:26 +0200 Subject: [PATCH 16/29] Avoid possible unnecessary time conversion. --- cores/esp8266/PolledTimeout.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 10d348be25..05347a63b8 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -261,7 +261,7 @@ class timeoutTemplate timeType current = TimePolicyT::time(); if(checkExpired(current)) { - unsigned long n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) + timeType n = (current - _start) / _timeout; //how many _timeouts periods have elapsed, will usually be 1 (current - _start >= _timeout) _start += n * _timeout; return true; } From b54576ff5a02babb680384cf443b5fe08e159669 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 5 Apr 2021 17:30:53 +0200 Subject: [PATCH 17/29] Fix one-shot mode in resetToNeverExpires(). Add convenient stop() synonym for same function. --- cores/esp8266/PolledTimeout.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 05347a63b8..af16f7dcfc 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -176,7 +176,7 @@ class timeoutTemplate bool canWait () const { - return !(_timeout == 0 || _oneShotExpired); + return _timeout != 0 && (PeriodicT || !_oneShotExpired); } // Resets, will trigger after this new timeout. @@ -193,7 +193,7 @@ class timeoutTemplate void reset() { _start = TimePolicyT::time(); - _oneShotExpired = false; + if (!PeriodicT) _oneShotExpired = false; } // Resets to just expired so that on next poll the check will immediately trigger for the user, @@ -217,6 +217,12 @@ class timeoutTemplate { _timeout = 1; // because canWait() has precedence _neverExpires = true; + if (!PeriodicT) _oneShotExpired = false; + } + + void stop() + { + resetToNeverExpires(); } timeType getTimeout() const @@ -247,7 +253,7 @@ class timeoutTemplate { // canWait() is not checked here // returns "oneshot has expired", otherwise returns "can expire" and "time has expired" - return _oneShotExpired || (!_neverExpires && ((internalUnit - _start) >= _timeout)); + return !_neverExpires && ((!PeriodicT && _oneShotExpired) || ((internalUnit - _start) >= _timeout)); } protected: @@ -275,7 +281,7 @@ class timeoutTemplate if (!canWait()) return true; if (checkExpired(TimePolicyT::time())) { - _oneShotExpired = true; + if (!PeriodicT) _oneShotExpired = true; return true; } return false; From 100d2c4e06e55129fe6efc5a3860b8d12a7e350d Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Mon, 5 Apr 2021 21:30:50 +0200 Subject: [PATCH 18/29] Due to review and discussion, _oneShotExpired removed again. --- cores/esp8266/PolledTimeout.h | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index af16f7dcfc..0d0c74f4fe 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -176,7 +176,7 @@ class timeoutTemplate bool canWait () const { - return _timeout != 0 && (PeriodicT || !_oneShotExpired); + return _timeout != 0; } // Resets, will trigger after this new timeout. @@ -185,7 +185,7 @@ class timeoutTemplate { reset(); _timeout = TimePolicyT::toTimeTypeUnit(newUserTimeout); - _neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax()); + _neverExpires = newUserTimeout > timeMax(); } // Resets, will trigger after the timeout previously set. @@ -193,7 +193,6 @@ class timeoutTemplate void reset() { _start = TimePolicyT::time(); - if (!PeriodicT) _oneShotExpired = false; } // Resets to just expired so that on next poll the check will immediately trigger for the user, @@ -217,7 +216,6 @@ class timeoutTemplate { _timeout = 1; // because canWait() has precedence _neverExpires = true; - if (!PeriodicT) _oneShotExpired = false; } void stop() @@ -252,8 +250,8 @@ class timeoutTemplate bool checkExpired(const timeType internalUnit) const { // canWait() is not checked here - // returns "oneshot has expired", otherwise returns "can expire" and "time has expired" - return !_neverExpires && ((!PeriodicT && _oneShotExpired) || ((internalUnit - _start) >= _timeout)); + // returns "can expire" and "time has expired" + return (!_neverExpires) && ((internalUnit - _start) >= _timeout); } protected: @@ -278,19 +276,12 @@ class timeoutTemplate bool expiredOneShot() const { // returns "always expired" or "has expired" - if (!canWait()) return true; - if (checkExpired(TimePolicyT::time())) - { - if (!PeriodicT) _oneShotExpired = true; - return true; - } - return false; + return !canWait() || checkExpired(TimePolicyT::time()); } timeType _timeout; timeType _start; bool _neverExpires; - mutable bool _oneShotExpired; }; // legacy type names, deprecated (unit is milliseconds) From 81b90c928f226fa0790a0a740f360e23cc51a2aa Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 1 Jul 2023 12:18:48 +0200 Subject: [PATCH 19/29] Revert removal of PolledTimeout::alwaysExpired --- cores/esp8266/PolledTimeout.h | 5 +++-- cores/esp8266/Stream.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cores/esp8266/PolledTimeout.h b/cores/esp8266/PolledTimeout.h index 0d0c74f4fe..ae1d7b5e73 100644 --- a/cores/esp8266/PolledTimeout.h +++ b/cores/esp8266/PolledTimeout.h @@ -146,6 +146,7 @@ class timeoutTemplate using timeType = typename TimePolicyT::timeType; static_assert(std::is_unsigned::value == true, "timeType must be unsigned"); + static constexpr timeType alwaysExpired = 0; static constexpr timeType neverExpires = std::numeric_limits::max(); static constexpr timeType rangeCompensate = TimePolicyT::rangeCompensate; //debug @@ -176,7 +177,7 @@ class timeoutTemplate bool canWait () const { - return _timeout != 0; + return _timeout != alwaysExpired; } // Resets, will trigger after this new timeout. @@ -214,7 +215,7 @@ class timeoutTemplate void resetToNeverExpires () { - _timeout = 1; // because canWait() has precedence + _timeout = alwaysExpired + 1; // because canWait() has precedence _neverExpires = true; } diff --git a/cores/esp8266/Stream.h b/cores/esp8266/Stream.h index 3da9e88b84..21f319ee6b 100644 --- a/cores/esp8266/Stream.h +++ b/cores/esp8266/Stream.h @@ -172,7 +172,7 @@ class Stream: public Print { // transfers already buffered / immediately available data (no timeout) // returns number of transferred bytes - [[deprecated]] size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, 0); } + [[deprecated]] size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); } [[deprecated]] size_t sendAvailable (Print& to) { return sendAvailable(&to); } // transfers data until timeout From eaf05e9c99961642eb112d486a035dd5f95236a1 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 1 Jul 2023 15:05:28 +0200 Subject: [PATCH 20/29] Finalize scheduled recurrent function delay logic. --- cores/esp8266/Schedule.cpp | 27 ++++++++++++++++++++------- cores/esp8266/Schedule.h | 4 ++-- cores/esp8266/core_esp8266_main.cpp | 6 ++---- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 85604d6ad7..ca9ce5f283 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -47,7 +47,7 @@ struct recurrent_fn_t static recurrent_fn_t* rFirst = nullptr; static recurrent_fn_t* rLast = nullptr; -static uint32_t rScheduleTarget = 0; +static uint32_t rTarget; // Returns a pointer to an unused sched_fn_t, // or if none are available allocates a new one, @@ -118,10 +118,13 @@ bool schedule_recurrent_function_us(const std::function& fn, item->mFunc = fn; item->alarm = alarm; -// if (!rScheduleTarget || rScheduleTarget > item.callNow.remaining()) -// { -// rScheduleTarget = item.callNow.remaining(); -// } + + // prevent new item overwriting an already expired rTarget. + const int32_t rRemaining = rTarget - millis(); + if (!rFirst || (rRemaining > 0 && static_cast(rRemaining) > item->callNow.remaining())) + { + rTarget = millis() + item->callNow.remaining(); + } esp8266::InterruptLock lockAllInterruptsInThisScope; @@ -138,9 +141,12 @@ bool schedule_recurrent_function_us(const std::function& fn, return true; } -uint32_t compute_scheduled_recurrent_grain() +uint32_t get_scheduled_recurrent_delay() { - return 0; + if (!rFirst) return ~static_cast(0); + // handle already expired rTarget. + const int32_t rRemaining = rTarget - millis(); + return (rRemaining > 0) ? static_cast(rRemaining) : 0; } void run_scheduled_functions() @@ -203,6 +209,7 @@ void run_scheduled_recurrent_functions() fence = true; } + rTarget = millis() + current->callNow.remaining(); recurrent_fn_t* prev = nullptr; // prevent scheduling of new functions during this run auto stop = rLast; @@ -241,6 +248,12 @@ void run_scheduled_recurrent_functions() { prev = current; current = current->mNext; + // prevent current item overwriting an already expired rTarget. + const int32_t rRemaining = rTarget - millis(); + if (rRemaining > 0 && static_cast(rRemaining) > current->callNow.remaining()) + { + rTarget = millis() + current->callNow.remaining(); + } } if (yieldNow) diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index 362d15b5f3..dc45dea1b1 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -39,10 +39,10 @@ // scheduled function happen more often: every yield() (vs every loop()), // and time resolution is microsecond (vs millisecond). Details are below. -// compute_scheduled_recurrent_grain() is used by delay() to give a chance to +// get_scheduled_recurrent_delay() is used by delay() to give a chance to // all recurrent functions to run per their timing requirement. -uint32_t compute_scheduled_recurrent_grain (); +uint32_t get_scheduled_recurrent_delay(); // scheduled functions called once: // diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index aaa3513bd0..5305b171e5 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -174,12 +174,10 @@ bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uin } // compute greatest delay interval with respect to scheduled recurrent functions - uint32_t grain_ms = std::min(intvl_ms, compute_scheduled_recurrent_grain()); + const uint32_t max_delay_ms = std::min(intvl_ms, get_scheduled_recurrent_delay()); // recurrent scheduled functions will be called from esp_delay()->esp_suspend() - esp_delay(grain_ms > 0 ? - std::min((timeout_ms - expired), grain_ms) : - (timeout_ms - expired)); + esp_delay(std::min((timeout_ms - expired), max_delay_ms)); return false; // expiration must be checked again } From a09726d672a7cfbb0ceb7006c84a884eaa0a03f2 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 1 Jul 2023 16:03:47 +0200 Subject: [PATCH 21/29] Fix millis/micros mismatch. --- cores/esp8266/Schedule.cpp | 14 +++++++------- cores/esp8266/Schedule.h | 4 ++-- cores/esp8266/core_esp8266_main.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index ca9ce5f283..0074570b93 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -120,10 +120,10 @@ bool schedule_recurrent_function_us(const std::function& fn, item->alarm = alarm; // prevent new item overwriting an already expired rTarget. - const int32_t rRemaining = rTarget - millis(); + const int32_t rRemaining = rTarget - micros(); if (!rFirst || (rRemaining > 0 && static_cast(rRemaining) > item->callNow.remaining())) { - rTarget = millis() + item->callNow.remaining(); + rTarget = micros() + item->callNow.remaining(); } esp8266::InterruptLock lockAllInterruptsInThisScope; @@ -141,11 +141,11 @@ bool schedule_recurrent_function_us(const std::function& fn, return true; } -uint32_t get_scheduled_recurrent_delay() +uint32_t get_scheduled_recurrent_delay_us() { if (!rFirst) return ~static_cast(0); // handle already expired rTarget. - const int32_t rRemaining = rTarget - millis(); + const int32_t rRemaining = rTarget - micros(); return (rRemaining > 0) ? static_cast(rRemaining) : 0; } @@ -209,7 +209,7 @@ void run_scheduled_recurrent_functions() fence = true; } - rTarget = millis() + current->callNow.remaining(); + rTarget = micros() + current->callNow.remaining(); recurrent_fn_t* prev = nullptr; // prevent scheduling of new functions during this run auto stop = rLast; @@ -249,10 +249,10 @@ void run_scheduled_recurrent_functions() prev = current; current = current->mNext; // prevent current item overwriting an already expired rTarget. - const int32_t rRemaining = rTarget - millis(); + const int32_t rRemaining = rTarget - micros(); if (rRemaining > 0 && static_cast(rRemaining) > current->callNow.remaining()) { - rTarget = millis() + current->callNow.remaining(); + rTarget = micros() + current->callNow.remaining(); } } diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index dc45dea1b1..7105212bf3 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -39,10 +39,10 @@ // scheduled function happen more often: every yield() (vs every loop()), // and time resolution is microsecond (vs millisecond). Details are below. -// get_scheduled_recurrent_delay() is used by delay() to give a chance to +// get_scheduled_recurrent_delay_us() is used by delay() to give a chance to // all recurrent functions to run per their timing requirement. -uint32_t get_scheduled_recurrent_delay(); +uint32_t get_scheduled_recurrent_delay_us(); // scheduled functions called once: // diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 5305b171e5..67f12e25ae 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -174,7 +174,7 @@ bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uin } // compute greatest delay interval with respect to scheduled recurrent functions - const uint32_t max_delay_ms = std::min(intvl_ms, get_scheduled_recurrent_delay()); + const uint32_t max_delay_ms = std::min(intvl_ms, get_scheduled_recurrent_delay_us() / 1000); // recurrent scheduled functions will be called from esp_delay()->esp_suspend() esp_delay(std::min((timeout_ms - expired), max_delay_ms)); From 4d3655b38fb3838e7af771d15ccbe3c13af2c5ac Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sat, 1 Jul 2023 16:40:53 +0200 Subject: [PATCH 22/29] ISR safety. --- cores/esp8266/Schedule.cpp | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 0074570b93..ec1fd478ab 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -119,15 +119,15 @@ bool schedule_recurrent_function_us(const std::function& fn, item->mFunc = fn; item->alarm = alarm; + esp8266::InterruptLock lockAllInterruptsInThisScope; + // prevent new item overwriting an already expired rTarget. - const int32_t rRemaining = rTarget - micros(); - if (!rFirst || (rRemaining > 0 && static_cast(rRemaining) > item->callNow.remaining())) + const int32_t remaining = rTarget - micros(); + if (!rFirst || (remaining > 0 && static_cast(remaining) > item->callNow.remaining())) { rTarget = micros() + item->callNow.remaining(); } - esp8266::InterruptLock lockAllInterruptsInThisScope; - if (rLast) { rLast->mNext = item; @@ -145,8 +145,8 @@ uint32_t get_scheduled_recurrent_delay_us() { if (!rFirst) return ~static_cast(0); // handle already expired rTarget. - const int32_t rRemaining = rTarget - micros(); - return (rRemaining > 0) ? static_cast(rRemaining) : 0; + const int32_t remaining = rTarget - micros(); + return (remaining > 0) ? static_cast(remaining) : 0; } void run_scheduled_functions() @@ -209,12 +209,18 @@ void run_scheduled_recurrent_functions() fence = true; } - rTarget = micros() + current->callNow.remaining(); + decltype(rLast) stop; recurrent_fn_t* prev = nullptr; - // prevent scheduling of new functions during this run - auto stop = rLast; - bool done; + + { + esp8266::InterruptLock lockAllInterruptsInThisScope; + + // prevent scheduling of new functions during this run + stop = rLast; + rTarget = micros() + (~static_cast(0) >> 1); + } + do { done = current == stop; @@ -246,14 +252,16 @@ void run_scheduled_recurrent_functions() } else { - prev = current; - current = current->mNext; + esp8266::InterruptLock lockAllInterruptsInThisScope; + // prevent current item overwriting an already expired rTarget. - const int32_t rRemaining = rTarget - micros(); - if (rRemaining > 0 && static_cast(rRemaining) > current->callNow.remaining()) + const int32_t remaining = rTarget - micros(); + if (remaining > 0 && static_cast(remaining) > current->callNow.remaining()) { rTarget = micros() + current->callNow.remaining(); } + prev = current; + current = current->mNext; } if (yieldNow) From 55d465a003e2bb1343ee5a6c45a7f3289d6fd150 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jul 2023 09:10:10 +0200 Subject: [PATCH 23/29] Returning max delay value that is uint-wrap around safe. --- cores/esp8266/Schedule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index ec1fd478ab..64d9767fd1 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -143,7 +143,7 @@ bool schedule_recurrent_function_us(const std::function& fn, uint32_t get_scheduled_recurrent_delay_us() { - if (!rFirst) return ~static_cast(0); + if (!rFirst) return ~static_cast(0) >> 1; // handle already expired rTarget. const int32_t remaining = rTarget - micros(); return (remaining > 0) ? static_cast(remaining) : 0; From c41a2c2afa79a07c2cf9578e49a49c326f96ba76 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 2 Jul 2023 09:28:56 +0200 Subject: [PATCH 24/29] Add pseudo-get delay function for scheduled functions --- cores/esp8266/Schedule.cpp | 5 +++++ cores/esp8266/Schedule.h | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 64d9767fd1..2652051a30 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -149,6 +149,11 @@ uint32_t get_scheduled_recurrent_delay_us() return (remaining > 0) ? static_cast(remaining) : 0; } +uint32_t get_scheduled_delay_us() +{ + return sFirst ? 0 : ~static_cast(0) >> 1; +} + void run_scheduled_functions() { // prevent scheduling of new functions during this run diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index 7105212bf3..56333d08e2 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -60,6 +60,13 @@ uint32_t get_scheduled_recurrent_delay_us(); // * Run the lambda only once next time. // * A scheduled function can schedule a function. +// get_scheduled_delay_us() is named for symmetry to get_scheduled_recurrent_delay_us, +// despite the lack of specific delay times. Therefore it can return only one of two +// values, viz. 0 in case of any pending scheduled functions, or a large delay time if +// there is no function in the queue. + +uint32_t get_scheduled_delay_us(); + bool schedule_function (const std::function& fn); // Run all scheduled functions. From 4e654d1ba8af9ae7c2efe266a21161d3bae9132f Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Thu, 20 Jul 2023 02:14:30 +0200 Subject: [PATCH 25/29] Worked out input from PR review. --- cores/esp8266/Schedule.cpp | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 2652051a30..4a4a5d69e4 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -47,7 +47,8 @@ struct recurrent_fn_t static recurrent_fn_t* rFirst = nullptr; static recurrent_fn_t* rLast = nullptr; -static uint32_t rTarget; +// The target time for scheduling the next timed recurrent function +static decltype(micros()) rTarget; // Returns a pointer to an unused sched_fn_t, // or if none are available allocates a new one, @@ -122,10 +123,12 @@ bool schedule_recurrent_function_us(const std::function& fn, esp8266::InterruptLock lockAllInterruptsInThisScope; // prevent new item overwriting an already expired rTarget. - const int32_t remaining = rTarget - micros(); - if (!rFirst || (remaining > 0 && static_cast(remaining) > item->callNow.remaining())) + const auto now = micros(); + const auto itemRemaining = item->callNow.remaining(); + const int32_t remaining = rTarget - now; + if (!rFirst || (remaining > 0 && static_cast(remaining) > itemRemaining)) { - rTarget = micros() + item->callNow.remaining(); + rTarget = now + itemRemaining; } if (rLast) @@ -218,13 +221,9 @@ void run_scheduled_recurrent_functions() recurrent_fn_t* prev = nullptr; bool done; - { - esp8266::InterruptLock lockAllInterruptsInThisScope; - - // prevent scheduling of new functions during this run - stop = rLast; - rTarget = micros() + (~static_cast(0) >> 1); - } + // prevent scheduling of new functions during this run + stop = rLast; + rTarget = micros() + (~static_cast(0) >> 1); do { @@ -260,11 +259,14 @@ void run_scheduled_recurrent_functions() esp8266::InterruptLock lockAllInterruptsInThisScope; // prevent current item overwriting an already expired rTarget. - const int32_t remaining = rTarget - micros(); - if (remaining > 0 && static_cast(remaining) > current->callNow.remaining()) + const auto now = micros(); + const auto currentRemaining = current->callNow.remaining(); + const int32_t remaining = rTarget - now; + if (remaining > 0 && static_cast(remaining) > currentRemaining) { - rTarget = micros() + current->callNow.remaining(); + rTarget = now + currentRemaining; } + prev = current; current = current->mNext; } From 717f4ad4a5e68e3991350b32b7f2dec6160f1777 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Fri, 28 Jul 2023 00:38:56 +0200 Subject: [PATCH 26/29] use single constexpr definition instead of repeated expression. --- cores/esp8266/Schedule.cpp | 21 +++++++++++---------- cores/esp8266/Schedule.h | 8 +++++--- cores/esp8266/core_esp8266_main.cpp | 3 ++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 4a4a5d69e4..68a79e5ef8 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -21,7 +21,6 @@ #include "Schedule.h" #include "PolledTimeout.h" #include "interrupts.h" -#include "coredecls.h" typedef std::function mSchedFuncT; struct scheduled_fn_t @@ -50,6 +49,8 @@ static recurrent_fn_t* rLast = nullptr; // The target time for scheduling the next timed recurrent function static decltype(micros()) rTarget; +constexpr decltype(micros()) HALF_MAX_MICROS = ~static_cast(0) >> 1; + // Returns a pointer to an unused sched_fn_t, // or if none are available allocates a new one, // or nullptr if limit is reached @@ -106,7 +107,7 @@ bool schedule_function(const std::function& fn) IRAM_ATTR // (not only) called from ISR bool schedule_recurrent_function_us(const std::function& fn, - uint32_t repeat_us, const std::function& alarm) + decltype(micros()) repeat_us, const std::function& alarm) { assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s) @@ -126,7 +127,7 @@ bool schedule_recurrent_function_us(const std::function& fn, const auto now = micros(); const auto itemRemaining = item->callNow.remaining(); const int32_t remaining = rTarget - now; - if (!rFirst || (remaining > 0 && static_cast(remaining) > itemRemaining)) + if (!rFirst || (remaining > 0 && static_cast(remaining) > itemRemaining)) { rTarget = now + itemRemaining; } @@ -144,17 +145,17 @@ bool schedule_recurrent_function_us(const std::function& fn, return true; } -uint32_t get_scheduled_recurrent_delay_us() +decltype(micros()) get_scheduled_recurrent_delay_us() { - if (!rFirst) return ~static_cast(0) >> 1; + if (!rFirst) return HALF_MAX_MICROS; // handle already expired rTarget. const int32_t remaining = rTarget - micros(); - return (remaining > 0) ? static_cast(remaining) : 0; + return (remaining > 0) ? static_cast(remaining) : 0; } -uint32_t get_scheduled_delay_us() +decltype(micros()) get_scheduled_delay_us() { - return sFirst ? 0 : ~static_cast(0) >> 1; + return sFirst ? 0 : HALF_MAX_MICROS; } void run_scheduled_functions() @@ -223,7 +224,7 @@ void run_scheduled_recurrent_functions() // prevent scheduling of new functions during this run stop = rLast; - rTarget = micros() + (~static_cast(0) >> 1); + rTarget = micros() + HALF_MAX_MICROS; do { @@ -262,7 +263,7 @@ void run_scheduled_recurrent_functions() const auto now = micros(); const auto currentRemaining = current->callNow.remaining(); const int32_t remaining = rTarget - now; - if (remaining > 0 && static_cast(remaining) > currentRemaining) + if (remaining > 0 && static_cast(remaining) > currentRemaining) { rTarget = now + currentRemaining; } diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index 56333d08e2..730e77d513 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -22,6 +22,8 @@ #include #include +#include "coredecls.h" + #define SCHEDULED_FN_MAX_COUNT 32 // The purpose of scheduled functions is to trigger, from SYS stack (like in @@ -42,7 +44,7 @@ // get_scheduled_recurrent_delay_us() is used by delay() to give a chance to // all recurrent functions to run per their timing requirement. -uint32_t get_scheduled_recurrent_delay_us(); +decltype(micros()) get_scheduled_recurrent_delay_us(); // scheduled functions called once: // @@ -65,7 +67,7 @@ uint32_t get_scheduled_recurrent_delay_us(); // values, viz. 0 in case of any pending scheduled functions, or a large delay time if // there is no function in the queue. -uint32_t get_scheduled_delay_us(); +decltype(micros()) get_scheduled_delay_us(); bool schedule_function (const std::function& fn); @@ -93,7 +95,7 @@ void run_scheduled_functions(); // any remaining delay from repeat_us is disregarded, and fn is executed. bool schedule_recurrent_function_us(const std::function& fn, - uint32_t repeat_us, const std::function& alarm = nullptr); + decltype(micros()) repeat_us, const std::function& alarm = nullptr); // Test recurrence and run recurrent scheduled functions. // (internally called at every `yield()` and `loop()`) diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 67f12e25ae..a0cfb8533c 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -174,7 +174,8 @@ bool esp_try_delay(const uint32_t start_ms, const uint32_t timeout_ms, const uin } // compute greatest delay interval with respect to scheduled recurrent functions - const uint32_t max_delay_ms = std::min(intvl_ms, get_scheduled_recurrent_delay_us() / 1000); + const uint32_t scheduled_recurrent_delay_ms = get_scheduled_recurrent_delay_us() / 1000UL; + const uint32_t max_delay_ms = std::min(intvl_ms, scheduled_recurrent_delay_ms); // recurrent scheduled functions will be called from esp_delay()->esp_suspend() esp_delay(std::min((timeout_ms - expired), max_delay_ms)); From 538025ada2b637cf1e949066b8996ef4839c41cb Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 30 Jul 2023 10:08:52 +0200 Subject: [PATCH 27/29] Simplified roll-over logic --- cores/esp8266/Schedule.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 68a79e5ef8..4c92e37489 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -126,8 +126,8 @@ bool schedule_recurrent_function_us(const std::function& fn, // prevent new item overwriting an already expired rTarget. const auto now = micros(); const auto itemRemaining = item->callNow.remaining(); - const int32_t remaining = rTarget - now; - if (!rFirst || (remaining > 0 && static_cast(remaining) > itemRemaining)) + const auto remaining = rTarget - now; + if (!rFirst || (remaining <= HALF_MAX_MICROS && remaining > itemRemaining)) { rTarget = now + itemRemaining; } @@ -149,8 +149,9 @@ decltype(micros()) get_scheduled_recurrent_delay_us() { if (!rFirst) return HALF_MAX_MICROS; // handle already expired rTarget. - const int32_t remaining = rTarget - micros(); - return (remaining > 0) ? static_cast(remaining) : 0; + const auto now = micros(); + const auto remaining = rTarget - now; + return (remaining <= HALF_MAX_MICROS) ? remaining : 0; } decltype(micros()) get_scheduled_delay_us() @@ -262,8 +263,8 @@ void run_scheduled_recurrent_functions() // prevent current item overwriting an already expired rTarget. const auto now = micros(); const auto currentRemaining = current->callNow.remaining(); - const int32_t remaining = rTarget - now; - if (remaining > 0 && static_cast(remaining) > currentRemaining) + const auto remaining = rTarget - now; + if (remaining <= HALF_MAX_MICROS && remaining > currentRemaining) { rTarget = now + currentRemaining; } From 861cf02f437a14df5c640c37b66a61a6867ba04b Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Sun, 30 Jul 2023 10:13:26 +0200 Subject: [PATCH 28/29] Add rationale for HALF_MAX_MICROS --- cores/esp8266/Schedule.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 4c92e37489..98e64d5701 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -49,6 +49,16 @@ static recurrent_fn_t* rLast = nullptr; // The target time for scheduling the next timed recurrent function static decltype(micros()) rTarget; +// As 32 bit unsigned integer, micros() rolls over every 71.6 minutes. +// For unambiguous earlier/later order between two timestamps, +// despite roll over, there is a limit on the maximum duration +// that can be requested, if full expiration must be observable: +// later - earlier >= 0 for both later >= earlier or (rolled over) later <= earlier +// Also, expiration should remain observable for a useful duration of time: +// now - (start + period) >= 0 for now - start >= 0 despite (start + period) >= now +// A well-balanced solution, not breaking on two's compliment signed arithmetic, +// is limiting durations to the maximum signed value of the same word size +// as the original unsigned word. constexpr decltype(micros()) HALF_MAX_MICROS = ~static_cast(0) >> 1; // Returns a pointer to an unused sched_fn_t, From b3b4aa0e8717e2144eba55d0c084bb914f4f3272 Mon Sep 17 00:00:00 2001 From: "Dirk O. Kaar" Date: Wed, 6 Sep 2023 08:33:10 +0200 Subject: [PATCH 29/29] Use CAS to minimize critical sections. --- cores/esp8266/Schedule.cpp | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index 98e64d5701..deab9e04eb 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -21,6 +21,7 @@ #include "Schedule.h" #include "PolledTimeout.h" #include "interrupts.h" +#include typedef std::function mSchedFuncT; struct scheduled_fn_t @@ -47,7 +48,7 @@ struct recurrent_fn_t static recurrent_fn_t* rFirst = nullptr; static recurrent_fn_t* rLast = nullptr; // The target time for scheduling the next timed recurrent function -static decltype(micros()) rTarget; +static std::atomic rTarget; // As 32 bit unsigned integer, micros() rolls over every 71.6 minutes. // For unambiguous earlier/later order between two timestamps, @@ -133,13 +134,17 @@ bool schedule_recurrent_function_us(const std::function& fn, esp8266::InterruptLock lockAllInterruptsInThisScope; - // prevent new item overwriting an already expired rTarget. const auto now = micros(); const auto itemRemaining = item->callNow.remaining(); - const auto remaining = rTarget - now; - if (!rFirst || (remaining <= HALF_MAX_MICROS && remaining > itemRemaining)) + for (auto _rTarget = rTarget.load(); ;) { - rTarget = now + itemRemaining; + const auto remaining = _rTarget - now; + if (!rFirst || (remaining <= HALF_MAX_MICROS && remaining > itemRemaining)) + { + // if (!rTarget.compare_exchange_weak(_rTarget, now + itemRemaining)) continue; + rTarget = now + itemRemaining; // interrupt lock is active, no ABA issue + } + break; } if (rLast) @@ -158,9 +163,8 @@ bool schedule_recurrent_function_us(const std::function& fn, decltype(micros()) get_scheduled_recurrent_delay_us() { if (!rFirst) return HALF_MAX_MICROS; - // handle already expired rTarget. const auto now = micros(); - const auto remaining = rTarget - now; + const auto remaining = rTarget.load() - now; return (remaining <= HALF_MAX_MICROS) ? remaining : 0; } @@ -233,9 +237,9 @@ void run_scheduled_recurrent_functions() recurrent_fn_t* prev = nullptr; bool done; + rTarget.store(micros() + HALF_MAX_MICROS); // prevent scheduling of new functions during this run stop = rLast; - rTarget = micros() + HALF_MAX_MICROS; do { @@ -268,17 +272,20 @@ void run_scheduled_recurrent_functions() } else { - esp8266::InterruptLock lockAllInterruptsInThisScope; - - // prevent current item overwriting an already expired rTarget. const auto now = micros(); const auto currentRemaining = current->callNow.remaining(); - const auto remaining = rTarget - now; - if (remaining <= HALF_MAX_MICROS && remaining > currentRemaining) + for (auto _rTarget = rTarget.load(); ;) { - rTarget = now + currentRemaining; + const auto remaining = _rTarget - now; + if (remaining <= HALF_MAX_MICROS && remaining > currentRemaining) + { + // if (!rTarget.compare_exchange_weak(_rTarget, now + currentRemaining)) continue; + esp8266::InterruptLock lockAllInterruptsInThisScope; + if (rTarget != _rTarget) { _rTarget = rTarget; continue; } + rTarget = now + currentRemaining; + } + break; } - prev = current; current = current->mNext; }