1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

remove scheduled functions complexity overhead, change recurrent api (#6214)

* remove scheduled functions complexity overhead, change recurrent functions api
This commit is contained in:
david gauchard 2019-06-25 12:53:47 +02:00 committed by GitHub
parent f5a882d03d
commit 05be1a09e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 131 additions and 113 deletions

View File

@ -6,16 +6,11 @@
#include "interrupts.h" #include "interrupts.h"
#include "coredecls.h" #include "coredecls.h"
typedef std::function<bool(void)> mFuncT; typedef std::function<void(void)> mSchedFuncT;
struct scheduled_fn_t struct scheduled_fn_t
{ {
scheduled_fn_t* mNext = nullptr; scheduled_fn_t* mNext = nullptr;
mFuncT mFunc; mSchedFuncT mFunc;
esp8266::polledTimeout::periodicFastUs callNow;
schedule_e policy;
scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { }
}; };
static scheduled_fn_t* sFirst = nullptr; static scheduled_fn_t* sFirst = nullptr;
@ -23,8 +18,22 @@ static scheduled_fn_t* sLast = nullptr;
static scheduled_fn_t* sUnused = nullptr; static scheduled_fn_t* sUnused = nullptr;
static int sCount = 0; static int sCount = 0;
typedef std::function<bool(void)> mRecFuncT;
struct recurrent_fn_t
{
recurrent_fn_t* mNext = nullptr;
mRecFuncT mFunc;
esp8266::polledTimeout::periodicFastUs callNow;
recurrent_fn_t (esp8266::polledTimeout::periodicFastUs interval): callNow(interval) { }
};
static recurrent_fn_t* rFirst = nullptr; // fifo not needed
// 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 IRAM_ATTR // called from ISR
static scheduled_fn_t* get_fn_unsafe() static scheduled_fn_t* get_fn_unsafe ()
{ {
scheduled_fn_t* result = nullptr; scheduled_fn_t* result = nullptr;
// try to get an item from unused items list // try to get an item from unused items list
@ -33,18 +42,18 @@ static scheduled_fn_t* get_fn_unsafe()
result = sUnused; result = sUnused;
sUnused = sUnused->mNext; sUnused = sUnused->mNext;
result->mNext = nullptr; result->mNext = nullptr;
result->callNow.reset(esp8266::polledTimeout::periodicFastUs::alwaysExpired);
} }
// if no unused items, and count not too high, allocate a new one // if no unused items, and count not too high, allocate a new one
else if (sCount < SCHEDULED_FN_MAX_COUNT) else if (sCount < SCHEDULED_FN_MAX_COUNT)
{ {
result = new scheduled_fn_t; result = (scheduled_fn_t*)malloc(sizeof(scheduled_fn_t));
if (result)
++sCount; ++sCount;
} }
return result; 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->mFunc = nullptr; // special overload in c++ std lib
fn->mNext = sUnused; fn->mNext = sUnused;
@ -52,19 +61,14 @@ static void recycle_fn_unsafe(scheduled_fn_t* fn)
} }
IRAM_ATTR // (not only) called from ISR IRAM_ATTR // (not only) called from ISR
bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us, schedule_e policy) bool schedule_function (const std::function<void(void)>& fn)
{ {
assert(repeat_us < decltype(scheduled_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
esp8266::InterruptLock lockAllInterruptsInThisScope; esp8266::InterruptLock lockAllInterruptsInThisScope;
scheduled_fn_t* item = get_fn_unsafe(); scheduled_fn_t* item = get_fn_unsafe();
if (!item) if (!item)
return false; return false;
if (repeat_us)
item->callNow.reset(repeat_us);
item->policy = policy;
item->mFunc = fn; item->mFunc = fn;
if (sFirst) if (sFirst)
@ -76,25 +80,58 @@ bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us, sc
return true; return true;
} }
IRAM_ATTR // (not only) called from ISR bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us)
bool schedule_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us, schedule_e policy)
{ {
return schedule_function_us(std::function<bool(void)>(fn), repeat_us, policy); assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
esp8266::InterruptLock lockAllInterruptsInThisScope;
recurrent_fn_t* item = new recurrent_fn_t(repeat_us);
if (!item)
return false;
item->mFunc = fn;
if (rFirst)
{
item->mNext = rFirst;
rFirst = item;
}
else
rFirst = item;
return true;
} }
IRAM_ATTR // called from ISR void run_scheduled_functions ()
bool schedule_function(std::function<void(void)>&& fn, schedule_e policy)
{ {
return schedule_function_us([fn]() { fn(); return false; }, 0, policy); esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
while (sFirst)
{
sFirst->mFunc();
{
esp8266::InterruptLock lockAllInterruptsInThisScope;
auto to_recycle = sFirst;
sFirst = sFirst->mNext;
if (!sFirst)
sLast = nullptr;
recycle_fn_unsafe(to_recycle);
}
if (yieldNow)
{
// because scheduled function are allowed to last:
// this is yield() in cont stack:
esp_schedule();
cont_yield(g_pcont);
}
}
} }
IRAM_ATTR // called from ISR void run_scheduled_recurrent_functions ()
bool schedule_function(const std::function<void(void)>& fn, schedule_e policy)
{
return schedule_function(std::function<void(void)>(fn), policy);
}
void run_scheduled_functions(schedule_e policy)
{ {
// Note to the reader: // Note to the reader:
// There is no exposed API to remove a scheduled function: // There is no exposed API to remove a scheduled function:
@ -102,62 +139,51 @@ void run_scheduled_functions(schedule_e policy)
// its purpose is that it is never called from an interrupt // its purpose is that it is never called from an interrupt
// (always on cont stack). // (always on cont stack).
if (!rFirst)
return;
static bool fence = false; static bool fence = false;
{ {
esp8266::InterruptLock lockAllInterruptsInThisScope; // fence is like a mutex but as we are never called from ISR,
// locking is useless here. Leaving comment for reference.
//esp8266::InterruptLock lockAllInterruptsInThisScope;
if (fence) if (fence)
// prevent recursive calls from yield() // prevent recursive calls from yield()
// (even if they are not allowed)
return; return;
fence = true; fence = true;
} }
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms recurrent_fn_t* prev = nullptr;
scheduled_fn_t* lastRecurring = nullptr; recurrent_fn_t* current = rFirst;
scheduled_fn_t* nextCall = sFirst;
while (nextCall)
{
scheduled_fn_t* toCall = nextCall;
nextCall = nextCall->mNext;
// run scheduled function: while (current)
// - when its schedule policy allows it anytime
// - or if we are called at loop() time
// and
// - its time policy allows it
if ( ( toCall->policy == SCHEDULED_FUNCTION_WITHOUT_YIELDELAYCALLS
|| policy == SCHEDULED_FUNCTION_ONCE_PER_LOOP)
&& toCall->callNow)
{ {
if (toCall->mFunc()) if (current->callNow && !current->mFunc())
{ {
// function stays in list // remove function from stack
lastRecurring = toCall;
}
else
{
// function removed from list
esp8266::InterruptLock lockAllInterruptsInThisScope; esp8266::InterruptLock lockAllInterruptsInThisScope;
if (sFirst == toCall) auto to_ditch = current;
sFirst = sFirst->mNext;
else if (lastRecurring)
lastRecurring->mNext = toCall->mNext;
if (sLast == toCall) if (prev)
sLast = lastRecurring; {
current = current->mNext;
recycle_fn_unsafe(toCall); prev->mNext = current;
}
} }
else else
// function stays in list
lastRecurring = toCall;
if (policy == SCHEDULED_FUNCTION_ONCE_PER_LOOP && yieldNow)
{ {
// this is yield() in cont stack: rFirst = rFirst->mNext;
esp_schedule(); current = rFirst;
cont_yield(g_pcont); }
delete(to_ditch);
}
else
{
prev = current;
current = current->mNext;
} }
} }

View File

@ -3,56 +3,47 @@
#include <functional> #include <functional>
// This API is stabilizing
// Function signatures may change, internal queue will remain FIFO.
//
// * Add the given lambda to a fifo list of lambdas, which is run when
// - `loop` function returns,
// - or `yield` is called,
// - or `run_scheduled_functions` is called.
//
// * Use lambdas to pass arguments to a function, or call a class/static
// member function.
//
// * Please ensure variables or instances used from inside lambda will exist
// when lambda is later called
//
// * There is no mechanism for cancelling scheduled functions.
//
// * `yield` can be called from inside lambdas
//
// * Returns false if the number of scheduled functions exceeds
// SCHEDULED_FN_MAX_COUNT.
#define SCHEDULED_FN_MAX_COUNT 32 #define SCHEDULED_FN_MAX_COUNT 32
enum schedule_e // scheduled functions called once:
{ //
SCHEDULED_FUNCTION_ONCE_PER_LOOP, // * internal queue is FIFO.
SCHEDULED_FUNCTION_WITHOUT_YIELDELAYCALLS // * Add the given lambda to a fifo list of lambdas, which is run when
}; // `loop` function returns.
// * Use lambdas to pass arguments to a function, or call a class/static
// member function.
// * Please ensure variables or instances used from inside lambda will exist
// when lambda is later called.
// * There is no mechanism for cancelling scheduled functions.
// * `yield` can be called from inside lambdas.
// * Returns false if the number of scheduled functions exceeds
// SCHEDULED_FN_MAX_COUNT.
// * Run the lambda only once next time.
// * Run the lambda only once next time bool schedule_function (const std::function<void(void)>& fn);
bool schedule_function(std::function<void(void)>&& fn,
schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
bool schedule_function(const std::function<void(void)>& fn,
schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
// Run all scheduled functions.
// Use this function if your are not using `loop`, or `loop` does not return
// on a regular basis.
void run_scheduled_functions();
// recurrent scheduled function:
//
// * internal queue if not FIFO.
// * Run the lambda periodically about every <repeat_us> microseconds until // * Run the lambda periodically about every <repeat_us> microseconds until
// it returns false. // it returns false.
// * Note that it may be more than <repeat_us> microseconds between calls if // * Note that it may be more than <repeat_us> microseconds between calls if
// `yield` is not called frequently, and therefore should not be used for // `yield` is not called frequently, and therefore should not be used for
// timing critical operations. // timing critical operations.
bool schedule_function_us(std::function<bool(void)>&& fn, // * There is no mechanism for cancelling recurrent scheduled functions.
uint32_t repeat_us, // * long running operations or yield() or delay() are not wise in the lambda.
schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
bool schedule_function_us(const std::function<bool(void)>& fn,
uint32_t repeat_us,
schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
// Run all scheduled functions. bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us);
// Use this function if your are not using `loop`, or `loop` does not return
// on a regular basis.
void run_scheduled_functions(schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
#endif //ESP_SCHEDULE_H // Test recurrence and run recurrent scheduled functions.
// (internally called at every `yield()` and `loop()`)
void run_scheduled_recurrent_functions ();
#endif // ESP_SCHEDULE_H

View File

@ -87,7 +87,7 @@ void preloop_update_frequency() {
static inline void esp_yield_within_cont() __attribute__((always_inline)); static inline void esp_yield_within_cont() __attribute__((always_inline));
static void esp_yield_within_cont() { static void esp_yield_within_cont() {
cont_yield(g_pcont); cont_yield(g_pcont);
run_scheduled_functions(SCHEDULED_FUNCTION_WITHOUT_YIELDELAYCALLS); run_scheduled_recurrent_functions();
} }
extern "C" void esp_yield() { extern "C" void esp_yield() {
@ -129,7 +129,8 @@ static void loop_wrapper() {
setup_done = true; setup_done = true;
} }
loop(); loop();
run_scheduled_functions(SCHEDULED_FUNCTION_ONCE_PER_LOOP); run_scheduled_functions();
run_scheduled_recurrent_functions();
esp_schedule(); esp_schedule();
} }