diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp index ed39736e55..465521e518 100644 --- a/cores/esp8266/Schedule.cpp +++ b/cores/esp8266/Schedule.cpp @@ -24,16 +24,18 @@ struct recurrent_fn_t recurrent_fn_t* mNext = nullptr; mRecFuncT mFunc; esp8266::polledTimeout::periodicFastUs callNow; - recurrent_fn_t (esp8266::polledTimeout::periodicFastUs interval): callNow(interval) { } + std::function alarm = nullptr; + recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { } }; -static recurrent_fn_t* rFirst = nullptr; // fifo not needed +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 @@ -52,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; @@ -60,8 +62,11 @@ static void recycle_fn_unsafe (scheduled_fn_t* fn) } IRAM_ATTR // (not only) called from ISR -bool schedule_function (const std::function& fn) +bool schedule_function(const std::function& fn) { + if (!fn) + return false; + esp8266::InterruptLock lockAllInterruptsInThisScope; scheduled_fn_t* item = get_fn_unsafe(); @@ -80,27 +85,37 @@ bool schedule_function (const std::function& fn) return true; } -bool schedule_recurrent_function_us (const std::function& fn, uint32_t repeat_us) +bool schedule_recurrent_function_us(const std::function& fn, + uint32_t repeat_us, const std::function& alarm) { assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s) - esp8266::InterruptLock lockAllInterruptsInThisScope; + if (!fn) + return false; recurrent_fn_t* item = new (std::nothrow) recurrent_fn_t(repeat_us); if (!item) return false; item->mFunc = fn; + item->alarm = alarm; - if (rFirst) - item->mNext = rFirst; + esp8266::InterruptLock lockAllInterruptsInThisScope; - rFirst = 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 @@ -128,15 +143,18 @@ 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 // its purpose is that it is never called from an interrupt // (always on cont stack). - if (!rFirst) + auto current = rFirst; + if (!current) return; static bool fence = false; @@ -153,26 +171,35 @@ void run_scheduled_recurrent_functions () } recurrent_fn_t* prev = nullptr; - recurrent_fn_t* current = rFirst; + // prevent scheduling of new functions during this run + auto stop = rLast; - while (current) + bool done; + do { - if (current->callNow && !current->mFunc()) + done = current == stop; + const bool wakeup = current->alarm && current->alarm(); + bool callNow = current->callNow; + + if ((wakeup || callNow) && !current->mFunc()) { // remove function from stack esp8266::InterruptLock lockAllInterruptsInThisScope; auto to_ditch = current; + // removing rLast + if (rLast == current) + rLast = prev; + + current = current->mNext; if (prev) { - current = current->mNext; prev->mNext = current; } else { - rFirst = rFirst->mNext; - current = rFirst; + rFirst = current; } delete(to_ditch); @@ -182,7 +209,15 @@ void run_scheduled_recurrent_functions () prev = current; current = current->mNext; } - } + + if (yieldNow) + { + // because scheduled functions might last too long for watchdog etc, + // this is yield() in cont stack: + esp_schedule(); + cont_yield(g_pcont); + } + } while (current && !done); fence = false; } diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h index 50fc88fc8b..a02241fa68 100644 --- a/cores/esp8266/Schedule.h +++ b/cores/esp8266/Schedule.h @@ -10,7 +10,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()`. @@ -58,14 +58,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. - -bool schedule_recurrent_function_us (const std::function& fn, uint32_t repeat_us); +// * 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& fn, + uint32_t repeat_us, const std::function& alarm = 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