From c4e5d14e0375708d1c073bd66fa07bc5acb34ea7 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Wed, 17 Jul 2019 00:40:06 +0200
Subject: [PATCH 01/15] Fix for scheduling recurrent functions while inside
 scheduled function

---
 cores/esp8266/Schedule.cpp | 32 +++++++++++++-------------------
 1 file changed, 13 insertions(+), 19 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index ed39736e55..1f5c92500a 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -27,7 +27,7 @@ struct recurrent_fn_t
     recurrent_fn_t (esp8266::polledTimeout::periodicFastUs interval): callNow(interval) { }
 };
 
-static recurrent_fn_t* rFirst = nullptr; // fifo not needed
+static recurrent_fn_t rFirst(0); // fifo not needed
 
 // Returns a pointer to an unused sched_fn_t,
 // or if none are available allocates a new one,
@@ -92,10 +92,8 @@ bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32
 
     item->mFunc = fn;
 
-    if (rFirst)
-        item->mNext = rFirst;
-
-    rFirst = item;
+    item->mNext = rFirst.mNext;
+    rFirst.mNext = item;
 
     return true;
 }
@@ -136,7 +134,8 @@ void run_scheduled_recurrent_functions ()
     // its purpose is that it is never called from an interrupt
     // (always on cont stack).
 
-    if (!rFirst)
+    recurrent_fn_t* current = rFirst.mNext;
+    if (!current)
         return;
 
     static bool fence = false;
@@ -152,10 +151,9 @@ void run_scheduled_recurrent_functions ()
         fence = true;
     }
 
-    recurrent_fn_t* prev = nullptr;
-    recurrent_fn_t* current = rFirst;
+    recurrent_fn_t* prev = &rFirst;
 
-    while (current)
+    do
     {
         if (current->callNow && !current->mFunc())
         {
@@ -164,16 +162,11 @@ void run_scheduled_recurrent_functions ()
 
             auto to_ditch = current;
 
-            if (prev)
-            {
-                current = current->mNext;
-                prev->mNext = current;
-            }
-            else
-            {
-                rFirst = rFirst->mNext;
-                current = rFirst;
-            }
+	    // current function recursively scheduled something
+	    if (prev->mNext != current) prev = prev->mNext;
+
+            current = current->mNext;
+            prev->mNext = current;
 
             delete(to_ditch);
         }
@@ -183,6 +176,7 @@ void run_scheduled_recurrent_functions ()
             current = current->mNext;
         }
     }
+    while (current);
 
     fence = false;
 }

From b81dfbbe9167f860bc7f555cbb8b4161bd431c7f Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Wed, 17 Jul 2019 09:24:53 +0200
Subject: [PATCH 02/15] Check that fn are valid. Invoking invalid functions
 throws otherwise.

---
 cores/esp8266/Schedule.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 1f5c92500a..bfad24887c 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -62,6 +62,9 @@ static void recycle_fn_unsafe (scheduled_fn_t* fn)
 IRAM_ATTR // (not only) called from ISR
 bool schedule_function (const std::function<void(void)>& fn)
 {
+    if (!fn)
+        return false;
+
     esp8266::InterruptLock lockAllInterruptsInThisScope;
 
     scheduled_fn_t* item = get_fn_unsafe();
@@ -84,6 +87,9 @@ bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32
 {
     assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
 
+    if (!fn)
+        return false;
+
     esp8266::InterruptLock lockAllInterruptsInThisScope;
 
     recurrent_fn_t* item = new (std::nothrow) recurrent_fn_t(repeat_us);

From d6b314e499714665950ebc01f096545654b993b4 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Thu, 29 Aug 2019 14:23:10 +0200
Subject: [PATCH 03/15] Added wakeup token to scheduler. Toggling the token
 value breaks a scheduled function

out from a delayed execution and makes it run on the next scheduler iteration.
---
 cores/esp8266/Schedule.cpp | 17 +++++++++++++----
 cores/esp8266/Schedule.h   |  7 +++++--
 2 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index bfad24887c..1fdf5e7a19 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -24,6 +24,8 @@ struct recurrent_fn_t
     recurrent_fn_t* mNext = nullptr;
     mRecFuncT mFunc;
     esp8266::polledTimeout::periodicFastUs callNow;
+    const std::atomic<bool>* wakeupToken = nullptr;
+    bool wakeupTokenCmp = false;
     recurrent_fn_t (esp8266::polledTimeout::periodicFastUs interval): callNow(interval) { }
 };
 
@@ -83,7 +85,8 @@ bool schedule_function (const std::function<void(void)>& fn)
     return true;
 }
 
-bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us)
+bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us,
+    const std::atomic<bool>* wakeupToken)
 {
     assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
 
@@ -97,6 +100,8 @@ bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32
         return false;
 
     item->mFunc = fn;
+    item->wakeupToken = wakeupToken;
+    item->wakeupTokenCmp = wakeupToken && wakeupToken->load();
 
     item->mNext = rFirst.mNext;
     rFirst.mNext = item;
@@ -161,15 +166,19 @@ void run_scheduled_recurrent_functions ()
 
     do
     {
-        if (current->callNow && !current->mFunc())
+        const bool wakeupToken = current->wakeupToken && current->wakeupToken->load();
+        const bool wakeup = current->wakeupTokenCmp != wakeupToken;
+        if (wakeup) current->wakeupTokenCmp = wakeupToken;
+
+        if ((wakeup || current->callNow) && !current->mFunc())
         {
             // remove function from stack
             esp8266::InterruptLock lockAllInterruptsInThisScope;
 
             auto to_ditch = current;
 
-	    // current function recursively scheduled something
-	    if (prev->mNext != current) prev = prev->mNext;
+        // current function recursively scheduled something
+        if (prev->mNext != current) prev = prev->mNext;
 
             current = current->mNext;
             prev->mNext = current;
diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
index 50fc88fc8b..898f35ced1 100644
--- a/cores/esp8266/Schedule.h
+++ b/cores/esp8266/Schedule.h
@@ -2,6 +2,7 @@
 #define ESP_SCHEDULE_H
 
 #include <functional>
+#include <atomic>
 
 #define SCHEDULED_FN_MAX_COUNT 32
 
@@ -60,8 +61,10 @@ void run_scheduled_functions();
 //   recurrent function.
 // * A recurrent function currently must not schedule another recurrent
 //   functions.
-
-bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us);
+// * If a wakeupToken is used, if its value toggles, any remaining
+//   delay is disregarded, and the lambda runs on the next scheduler iteration.
+bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us,
+    const std::atomic<bool>* wakeupToken = nullptr);
 
 // Test recurrence and run recurrent scheduled functions.
 // (internally called at every `yield()` and `loop()`)

From 3ce09a71ca38cd5e4c21344fc9e33ef516c52332 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Tue, 3 Sep 2019 13:05:22 +0200
Subject: [PATCH 04/15] Timer reset reliability fix.

---
 cores/esp8266/Schedule.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 1fdf5e7a19..67af71815c 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -169,8 +169,9 @@ void run_scheduled_recurrent_functions ()
         const bool wakeupToken = current->wakeupToken && current->wakeupToken->load();
         const bool wakeup = current->wakeupTokenCmp != wakeupToken;
         if (wakeup) current->wakeupTokenCmp = wakeupToken;
+        bool callNow = current->callNow;
 
-        if ((wakeup || current->callNow) && !current->mFunc())
+        if ((wakeup || callNow) && !current->mFunc())
         {
             // remove function from stack
             esp8266::InterruptLock lockAllInterruptsInThisScope;

From 93be83ace40aae7108ea3ee3c2515d39a54abb0d Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Tue, 10 Sep 2019 20:04:47 +0200
Subject: [PATCH 05/15] Shrink interrupts-locked regions.

Add check for periodic yield to scheduled functions run-loop.
---
 cores/esp8266/Schedule.cpp | 41 +++++++++++++++++++++++---------------
 cores/esp8266/Schedule.h   | 10 ++++------
 2 files changed, 29 insertions(+), 22 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 67af71815c..19345d30af 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -26,7 +26,7 @@ struct recurrent_fn_t
     esp8266::polledTimeout::periodicFastUs callNow;
     const std::atomic<bool>* wakeupToken = nullptr;
     bool wakeupTokenCmp = false;
-    recurrent_fn_t (esp8266::polledTimeout::periodicFastUs interval): callNow(interval) { }
+    recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { }
 };
 
 static recurrent_fn_t rFirst(0); // fifo not needed
@@ -35,7 +35,7 @@ static recurrent_fn_t rFirst(0); // fifo not needed
 // or if none are available allocates a new one,
 // or nullptr if limit is reached
 IRAM_ATTR // called from ISR
-static scheduled_fn_t* get_fn_unsafe ()
+static scheduled_fn_t* get_fn_unsafe()
 {
     scheduled_fn_t* result = nullptr;
     // try to get an item from unused items list
@@ -54,7 +54,7 @@ static scheduled_fn_t* get_fn_unsafe ()
     return result;
 }
 
-static void recycle_fn_unsafe (scheduled_fn_t* fn)
+static void recycle_fn_unsafe(scheduled_fn_t* fn)
 {
     fn->mFunc = nullptr; // special overload in c++ std lib
     fn->mNext = sUnused;
@@ -62,7 +62,7 @@ static void recycle_fn_unsafe (scheduled_fn_t* fn)
 }
 
 IRAM_ATTR // (not only) called from ISR
-bool schedule_function (const std::function<void(void)>& fn)
+bool schedule_function(const std::function<void(void)>& fn)
 {
     if (!fn)
         return false;
@@ -85,7 +85,7 @@ bool schedule_function (const std::function<void(void)>& fn)
     return true;
 }
 
-bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us,
+bool schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us,
     const std::atomic<bool>* wakeupToken)
 {
     assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
@@ -93,8 +93,6 @@ bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32
     if (!fn)
         return false;
 
-    esp8266::InterruptLock lockAllInterruptsInThisScope;
-
     recurrent_fn_t* item = new (std::nothrow) recurrent_fn_t(repeat_us);
     if (!item)
         return false;
@@ -103,13 +101,15 @@ bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32
     item->wakeupToken = wakeupToken;
     item->wakeupTokenCmp = wakeupToken && wakeupToken->load();
 
+    esp8266::InterruptLock lockAllInterruptsInThisScope;
+
     item->mNext = rFirst.mNext;
     rFirst.mNext = item;
 
     return true;
 }
 
-void run_scheduled_functions ()
+void run_scheduled_functions()
 {
     esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
 
@@ -124,9 +124,9 @@ void run_scheduled_functions ()
             sFirst = sFirst->mNext;
             if (!sFirst)
                 sLast = nullptr;
-            recycle_fn_unsafe(to_recycle);
-        }
-
+			recycle_fn_unsafe(to_recycle);
+		}
+    	
         if (yieldNow)
         {
             // because scheduled function are allowed to last:
@@ -137,8 +137,10 @@ void run_scheduled_functions ()
     }
 }
 
-void run_scheduled_recurrent_functions ()
+void run_scheduled_recurrent_functions()
 {
+    esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
+
     // Note to the reader:
     // There is no exposed API to remove a scheduled function:
     // Scheduled functions are removed only from this function, and
@@ -178,8 +180,8 @@ void run_scheduled_recurrent_functions ()
 
             auto to_ditch = current;
 
-        // current function recursively scheduled something
-        if (prev->mNext != current) prev = prev->mNext;
+            // current function recursively scheduled something
+            if (prev->mNext != current) prev = prev->mNext;
 
             current = current->mNext;
             prev->mNext = current;
@@ -191,8 +193,15 @@ void run_scheduled_recurrent_functions ()
             prev = current;
             current = current->mNext;
         }
-    }
-    while (current);
+
+        if (yieldNow)
+        {
+            // because scheduled function might last too long for watchdog etc,
+            // this is yield() in cont stack:
+            esp_schedule();
+            cont_yield(g_pcont);
+        }
+    } while (current);
 
     fence = false;
 }
diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
index 898f35ced1..7caaa5fc5f 100644
--- a/cores/esp8266/Schedule.h
+++ b/cores/esp8266/Schedule.h
@@ -11,7 +11,7 @@
 // in user stack (called CONT stack) without the common restrictions from
 // system context.  Details are below.
 
-// The purpose of recurrent scheduled function is to independantly execute
+// The purpose of recurrent scheduled function is to independently execute
 // user code in CONT stack on a regular basis.
 // It has been introduced with ethernet service in mind, it can also be used
 // for all libraries in the need of a regular `libdaemon_handlestuff()`.
@@ -37,7 +37,7 @@
 // * Run the lambda only once next time.
 // * A scheduled function can schedule a function.
 
-bool schedule_function (const std::function<void(void)>& fn);
+bool schedule_function(const std::function<void(void)> & fn);
 
 // Run all scheduled functions.
 // Use this function if your are not using `loop`,
@@ -59,16 +59,14 @@ void run_scheduled_functions();
 //   functions.  However a user function returning false will cancel itself.
 // * Long running operations or yield() or delay() are not allowed in the
 //   recurrent function.
-// * A recurrent function currently must not schedule another recurrent
-//   functions.
 // * If a wakeupToken is used, if its value toggles, any remaining
 //   delay is disregarded, and the lambda runs on the next scheduler iteration.
-bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us,
+bool schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us,
     const std::atomic<bool>* wakeupToken = nullptr);
 
 // Test recurrence and run recurrent scheduled functions.
 // (internally called at every `yield()` and `loop()`)
 
-void run_scheduled_recurrent_functions ();
+void run_scheduled_recurrent_functions();
 
 #endif // ESP_SCHEDULE_H

From 28c02222ecdf4d8143b40259822a9d32229b368c Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Wed, 11 Sep 2019 10:40:59 +0200
Subject: [PATCH 06/15] Ordered, more predictable, scheduling. Before, it had
 different ordering compared to

FastScheduler as well as sequential calls from loop().
---
 cores/esp8266/Schedule.cpp | 29 +++++++++++++++++------------
 1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 19345d30af..1bcd79bffa 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -29,7 +29,8 @@ struct recurrent_fn_t
     recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { }
 };
 
-static recurrent_fn_t rFirst(0); // fifo not needed
+static recurrent_fn_t rFirst(0);
+static recurrent_fn_t* rLast = &rFirst;
 
 // Returns a pointer to an unused sched_fn_t,
 // or if none are available allocates a new one,
@@ -98,13 +99,14 @@ bool schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_
         return false;
 
     item->mFunc = fn;
+    item->mNext = nullptr;
     item->wakeupToken = wakeupToken;
     item->wakeupTokenCmp = wakeupToken && wakeupToken->load();
 
     esp8266::InterruptLock lockAllInterruptsInThisScope;
 
-    item->mNext = rFirst.mNext;
-    rFirst.mNext = item;
+    rLast->mNext = item;
+    rLast = item;
 
     return true;
 }
@@ -124,9 +126,9 @@ void run_scheduled_functions()
             sFirst = sFirst->mNext;
             if (!sFirst)
                 sLast = nullptr;
-			recycle_fn_unsafe(to_recycle);
-		}
-    	
+            recycle_fn_unsafe(to_recycle);
+        }
+
         if (yieldNow)
         {
             // because scheduled function are allowed to last:
@@ -147,7 +149,7 @@ void run_scheduled_recurrent_functions()
     // its purpose is that it is never called from an interrupt
     // (always on cont stack).
 
-    recurrent_fn_t* current = rFirst.mNext;
+    auto current = rFirst.mNext;
     if (!current)
         return;
 
@@ -164,10 +166,14 @@ void run_scheduled_recurrent_functions()
         fence = true;
     }
 
-    recurrent_fn_t* prev = &rFirst;
+    auto prev = &rFirst;
+    // prevent scheduling of new functions during this run
+    auto stop = rLast;
 
+    bool done;
     do
     {
+        done = current == stop;
         const bool wakeupToken = current->wakeupToken && current->wakeupToken->load();
         const bool wakeup = current->wakeupTokenCmp != wakeupToken;
         if (wakeup) current->wakeupTokenCmp = wakeupToken;
@@ -180,11 +186,10 @@ void run_scheduled_recurrent_functions()
 
             auto to_ditch = current;
 
-            // current function recursively scheduled something
-            if (prev->mNext != current) prev = prev->mNext;
-
             current = current->mNext;
             prev->mNext = current;
+            // removing rLast
+            if (!current) rLast = prev;
 
             delete(to_ditch);
         }
@@ -201,7 +206,7 @@ void run_scheduled_recurrent_functions()
             esp_schedule();
             cont_yield(g_pcont);
         }
-    } while (current);
+    } while (current && !done);
 
     fence = false;
 }

From fc2ef796fbff7caa332d5338a99f23877ebef1f8 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Wed, 11 Sep 2019 11:17:25 +0200
Subject: [PATCH 07/15] Optional, for the paranoid: revert changes to
 (non-recurrent) schedule_function() / run_scheduled_functions().

---
 cores/esp8266/Schedule.cpp | 13 +++++++------
 cores/esp8266/Schedule.h   |  2 +-
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 1bcd79bffa..0654bfd449 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -36,7 +36,7 @@ static recurrent_fn_t* rLast = &rFirst;
 // or if none are available allocates a new one,
 // or nullptr if limit is reached
 IRAM_ATTR // called from ISR
-static scheduled_fn_t* get_fn_unsafe()
+static scheduled_fn_t* get_fn_unsafe ()
 {
     scheduled_fn_t* result = nullptr;
     // try to get an item from unused items list
@@ -55,7 +55,7 @@ static scheduled_fn_t* get_fn_unsafe()
     return result;
 }
 
-static void recycle_fn_unsafe(scheduled_fn_t* fn)
+static void recycle_fn_unsafe (scheduled_fn_t* fn)
 {
     fn->mFunc = nullptr; // special overload in c++ std lib
     fn->mNext = sUnused;
@@ -63,7 +63,7 @@ static void recycle_fn_unsafe(scheduled_fn_t* fn)
 }
 
 IRAM_ATTR // (not only) called from ISR
-bool schedule_function(const std::function<void(void)>& fn)
+bool schedule_function (const std::function<void(void)>& fn)
 {
     if (!fn)
         return false;
@@ -111,7 +111,7 @@ bool schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_
     return true;
 }
 
-void run_scheduled_functions()
+void run_scheduled_functions ()
 {
     esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
 
@@ -186,10 +186,11 @@ void run_scheduled_recurrent_functions()
 
             auto to_ditch = current;
 
+            // removing rLast
+            if (rLast == current) rLast = prev;
+
             current = current->mNext;
             prev->mNext = current;
-            // removing rLast
-            if (!current) rLast = prev;
 
             delete(to_ditch);
         }
diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
index 7caaa5fc5f..5083576112 100644
--- a/cores/esp8266/Schedule.h
+++ b/cores/esp8266/Schedule.h
@@ -37,7 +37,7 @@
 // * Run the lambda only once next time.
 // * A scheduled function can schedule a function.
 
-bool schedule_function(const std::function<void(void)> & fn);
+bool schedule_function (const std::function<void(void)>& fn);
 
 // Run all scheduled functions.
 // Use this function if your are not using `loop`,

From 61cd2489292326c1400603b196b9fedde1390fa6 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Mon, 16 Sep 2019 09:57:21 +0200
Subject: [PATCH 08/15] Comment

---
 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 0654bfd449..63346172f1 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -202,7 +202,7 @@ void run_scheduled_recurrent_functions()
 
         if (yieldNow)
         {
-            // because scheduled function might last too long for watchdog etc,
+            // because scheduled functions might last too long for watchdog etc,
             // this is yield() in cont stack:
             esp_schedule();
             cont_yield(g_pcont);

From 46d1b34e0076ad40c9532b708e03174dc35f0347 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Sun, 29 Sep 2019 16:36:15 +0200
Subject: [PATCH 09/15] Adapt one-line ifs to general style in same source
 file.

---
 cores/esp8266/Schedule.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 63346172f1..0e78ad682f 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -176,7 +176,8 @@ void run_scheduled_recurrent_functions()
         done = current == stop;
         const bool wakeupToken = current->wakeupToken && current->wakeupToken->load();
         const bool wakeup = current->wakeupTokenCmp != wakeupToken;
-        if (wakeup) current->wakeupTokenCmp = wakeupToken;
+        if (wakeup)
+            current->wakeupTokenCmp = wakeupToken;
         bool callNow = current->callNow;
 
         if ((wakeup || callNow) && !current->mFunc())
@@ -187,7 +188,8 @@ void run_scheduled_recurrent_functions()
             auto to_ditch = current;
 
             // removing rLast
-            if (rLast == current) rLast = prev;
+            if (rLast == current)
+                rLast = prev;
 
             current = current->mNext;
             prev->mNext = current;

From 72b28d548dadc4290ba214c192d80fade3f0a4ed Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Mon, 30 Sep 2019 14:49:31 +0200
Subject: [PATCH 10/15] Fix wakeupToken handling - don't respond to toggle, but
 to different value

vs. that at registering function with scheduler.
---
 cores/esp8266/Schedule.cpp | 2 --
 cores/esp8266/Schedule.h   | 4 ++--
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 0e78ad682f..f00a3701f3 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -176,8 +176,6 @@ void run_scheduled_recurrent_functions()
         done = current == stop;
         const bool wakeupToken = current->wakeupToken && current->wakeupToken->load();
         const bool wakeup = current->wakeupTokenCmp != wakeupToken;
-        if (wakeup)
-            current->wakeupTokenCmp = wakeupToken;
         bool callNow = current->callNow;
 
         if ((wakeup || callNow) && !current->mFunc())
diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
index 5083576112..85d5a0a42f 100644
--- a/cores/esp8266/Schedule.h
+++ b/cores/esp8266/Schedule.h
@@ -59,8 +59,8 @@ void run_scheduled_functions();
 //   functions.  However a user function returning false will cancel itself.
 // * Long running operations or yield() or delay() are not allowed in the
 //   recurrent function.
-// * If a wakeupToken is used, if its value toggles, any remaining
-//   delay is disregarded, and the lambda runs on the next scheduler iteration.
+// * If a wakeupToken is used, anytime during scheduling when its value differs from that
+//   during this call, any remaining delay from repeat_us is disregarded, and fn is executed.
 bool schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us,
     const std::atomic<bool>* wakeupToken = nullptr);
 

From 8a531b7fe30632d98a6b4f52a2f2012fb757e8e7 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Mon, 30 Sep 2019 23:09:22 +0200
Subject: [PATCH 11/15] Reword comment.

---
 cores/esp8266/Schedule.h | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
index 85d5a0a42f..2ed53f7706 100644
--- a/cores/esp8266/Schedule.h
+++ b/cores/esp8266/Schedule.h
@@ -59,10 +59,11 @@ void run_scheduled_functions();
 //   functions.  However a user function returning false will cancel itself.
 // * Long running operations or yield() or delay() are not allowed in the
 //   recurrent function.
-// * If a wakeupToken is used, anytime during scheduling when its value differs from that
-//   during this call, any remaining delay from repeat_us is disregarded, and fn is executed.
-bool schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us,
-    const std::atomic<bool>* wakeupToken = nullptr);
+// * If a wakeupToken is used, anytime during scheduling when its value differs
+//   from the original one in this call, any remaining delay from repeat_us is
+//   disregarded, and fn is executed.
+bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
+    uint32_t repeat_us, const std::atomic<bool>* wakeupToken = nullptr);
 
 // Test recurrence and run recurrent scheduled functions.
 // (internally called at every `yield()` and `loop()`)

From 88302e86df0577b747d29a876e3003faf054d00d Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Tue, 1 Oct 2019 17:50:31 +0200
Subject: [PATCH 12/15] Putting aside std::atomic concerns, use a callback for
 scheduler alarming.

In the future, async future's .then() might take advantage of this direction.
---
 cores/esp8266/Schedule.cpp | 13 +++++--------
 cores/esp8266/Schedule.h   |  7 +++----
 2 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index f00a3701f3..0832159717 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -24,8 +24,7 @@ struct recurrent_fn_t
     recurrent_fn_t* mNext = nullptr;
     mRecFuncT mFunc;
     esp8266::polledTimeout::periodicFastUs callNow;
-    const std::atomic<bool>* wakeupToken = nullptr;
-    bool wakeupTokenCmp = false;
+    std::function<bool()> alarm = nullptr;
     recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { }
 };
 
@@ -86,8 +85,8 @@ bool schedule_function (const std::function<void(void)>& fn)
     return true;
 }
 
-bool schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us,
-    const std::atomic<bool>* wakeupToken)
+bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
+    uint32_t repeat_us, std::function<bool()> alarm)
 {
     assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
 
@@ -100,8 +99,7 @@ bool schedule_recurrent_function_us(const std::function<bool(void)>& fn, uint32_
 
     item->mFunc = fn;
     item->mNext = nullptr;
-    item->wakeupToken = wakeupToken;
-    item->wakeupTokenCmp = wakeupToken && wakeupToken->load();
+    item->alarm = std::move(alarm);
 
     esp8266::InterruptLock lockAllInterruptsInThisScope;
 
@@ -174,8 +172,7 @@ void run_scheduled_recurrent_functions()
     do
     {
         done = current == stop;
-        const bool wakeupToken = current->wakeupToken && current->wakeupToken->load();
-        const bool wakeup = current->wakeupTokenCmp != wakeupToken;
+        const bool wakeup = current->alarm && current->alarm();
         bool callNow = current->callNow;
 
         if ((wakeup || callNow) && !current->mFunc())
diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
index 2ed53f7706..4c4ef3d54b 100644
--- a/cores/esp8266/Schedule.h
+++ b/cores/esp8266/Schedule.h
@@ -59,11 +59,10 @@ void run_scheduled_functions();
 //   functions.  However a user function returning false will cancel itself.
 // * Long running operations or yield() or delay() are not allowed in the
 //   recurrent function.
-// * If a wakeupToken is used, anytime during scheduling when its value differs
-//   from the original one in this call, any remaining delay from repeat_us is
-//   disregarded, and fn is executed.
+// * If alarm is used, anytime during scheduling when it returns true,
+//   any remaining delay from repeat_us is disregarded, and fn is executed.
 bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
-    uint32_t repeat_us, const std::atomic<bool>* wakeupToken = nullptr);
+    uint32_t repeat_us, std::function<bool()> alarm = nullptr);
 
 // Test recurrence and run recurrent scheduled functions.
 // (internally called at every `yield()` and `loop()`)

From 2812b989ceb8677736717a75f2341a2336c7fa34 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Tue, 1 Oct 2019 18:48:14 +0200
Subject: [PATCH 13/15] Drop atomic include, align function type syntax.

---
 cores/esp8266/Schedule.cpp | 4 ++--
 cores/esp8266/Schedule.h   | 3 +--
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 0832159717..1021c2ae30 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -24,7 +24,7 @@ struct recurrent_fn_t
     recurrent_fn_t* mNext = nullptr;
     mRecFuncT mFunc;
     esp8266::polledTimeout::periodicFastUs callNow;
-    std::function<bool()> alarm = nullptr;
+    std::function<bool(void)> alarm = nullptr;
     recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { }
 };
 
@@ -86,7 +86,7 @@ bool schedule_function (const std::function<void(void)>& fn)
 }
 
 bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
-    uint32_t repeat_us, std::function<bool()> alarm)
+    uint32_t repeat_us, std::function<bool(void)> alarm)
 {
     assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
 
diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
index 4c4ef3d54b..65c771f91f 100644
--- a/cores/esp8266/Schedule.h
+++ b/cores/esp8266/Schedule.h
@@ -2,7 +2,6 @@
 #define ESP_SCHEDULE_H
 
 #include <functional>
-#include <atomic>
 
 #define SCHEDULED_FN_MAX_COUNT 32
 
@@ -62,7 +61,7 @@ void run_scheduled_functions();
 // * If alarm is used, anytime during scheduling when it returns true,
 //   any remaining delay from repeat_us is disregarded, and fn is executed.
 bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
-    uint32_t repeat_us, std::function<bool()> alarm = nullptr);
+    uint32_t repeat_us, std::function<bool(void)> alarm = nullptr);
 
 // Test recurrence and run recurrent scheduled functions.
 // (internally called at every `yield()` and `loop()`)

From 52f9dcf420ca797f2afd3e52e3f2ebdf1c224392 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Fri, 11 Oct 2019 14:33:04 +0200
Subject: [PATCH 14/15] Reduce flash use.

---
 cores/esp8266/Schedule.cpp | 35 ++++++++++++++++++++++++-----------
 1 file changed, 24 insertions(+), 11 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 1021c2ae30..47a73c61e3 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -28,14 +28,14 @@ struct recurrent_fn_t
     recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { }
 };
 
-static recurrent_fn_t rFirst(0);
-static recurrent_fn_t* rLast = &rFirst;
+static recurrent_fn_t* rFirst = nullptr;
+static recurrent_fn_t* rLast = nullptr;
 
 // Returns a pointer to an unused sched_fn_t,
 // or if none are available allocates a new one,
 // or nullptr if limit is reached
 IRAM_ATTR // called from ISR
-static scheduled_fn_t* get_fn_unsafe ()
+static scheduled_fn_t* get_fn_unsafe()
 {
     scheduled_fn_t* result = nullptr;
     // try to get an item from unused items list
@@ -54,7 +54,7 @@ static scheduled_fn_t* get_fn_unsafe ()
     return result;
 }
 
-static void recycle_fn_unsafe (scheduled_fn_t* fn)
+static void recycle_fn_unsafe(scheduled_fn_t* fn)
 {
     fn->mFunc = nullptr; // special overload in c++ std lib
     fn->mNext = sUnused;
@@ -62,7 +62,7 @@ static void recycle_fn_unsafe (scheduled_fn_t* fn)
 }
 
 IRAM_ATTR // (not only) called from ISR
-bool schedule_function (const std::function<void(void)>& fn)
+bool schedule_function(const std::function<void(void)>& fn)
 {
     if (!fn)
         return false;
@@ -98,18 +98,24 @@ bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
         return false;
 
     item->mFunc = fn;
-    item->mNext = nullptr;
     item->alarm = std::move(alarm);
 
     esp8266::InterruptLock lockAllInterruptsInThisScope;
 
-    rLast->mNext = item;
+    if (rLast)
+    {
+        rLast->mNext = item;
+    }
+    else
+    {
+        rFirst = item;
+    }
     rLast = item;
 
     return true;
 }
 
-void run_scheduled_functions ()
+void run_scheduled_functions()
 {
     esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
 
@@ -147,7 +153,7 @@ void run_scheduled_recurrent_functions()
     // its purpose is that it is never called from an interrupt
     // (always on cont stack).
 
-    auto current = rFirst.mNext;
+    auto current = rFirst;
     if (!current)
         return;
 
@@ -164,7 +170,7 @@ void run_scheduled_recurrent_functions()
         fence = true;
     }
 
-    auto prev = &rFirst;
+    recurrent_fn_t* prev = nullptr;
     // prevent scheduling of new functions during this run
     auto stop = rLast;
 
@@ -187,7 +193,14 @@ void run_scheduled_recurrent_functions()
                 rLast = prev;
 
             current = current->mNext;
-            prev->mNext = current;
+            if (prev)
+            {
+                prev->mNext = current;
+            }
+            else
+            {
+                rFirst = current;
+            }
 
             delete(to_ditch);
         }

From e12a369cb0b78af91895695ff41ae4d165bd8ee5 Mon Sep 17 00:00:00 2001
From: "Dirk O. Kaar" <dok@dok-net.net>
Date: Mon, 28 Oct 2019 10:00:33 +0100
Subject: [PATCH 15/15] Prefer const ref over call by value plus std::move().

---
 cores/esp8266/Schedule.cpp | 4 ++--
 cores/esp8266/Schedule.h   | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp
index 47a73c61e3..465521e518 100644
--- a/cores/esp8266/Schedule.cpp
+++ b/cores/esp8266/Schedule.cpp
@@ -86,7 +86,7 @@ bool schedule_function(const std::function<void(void)>& fn)
 }
 
 bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
-    uint32_t repeat_us, std::function<bool(void)> alarm)
+    uint32_t repeat_us, const std::function<bool(void)>& alarm)
 {
     assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
 
@@ -98,7 +98,7 @@ bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
         return false;
 
     item->mFunc = fn;
-    item->alarm = std::move(alarm);
+    item->alarm = alarm;
 
     esp8266::InterruptLock lockAllInterruptsInThisScope;
 
diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h
index 65c771f91f..a02241fa68 100644
--- a/cores/esp8266/Schedule.h
+++ b/cores/esp8266/Schedule.h
@@ -61,7 +61,7 @@ void run_scheduled_functions();
 // * If alarm is used, anytime during scheduling when it returns true,
 //   any remaining delay from repeat_us is disregarded, and fn is executed.
 bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
-    uint32_t repeat_us, std::function<bool(void)> alarm = nullptr);
+    uint32_t repeat_us, const std::function<bool(void)>& alarm = nullptr);
 
 // Test recurrence and run recurrent scheduled functions.
 // (internally called at every `yield()` and `loop()`)