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:
parent
f5a882d03d
commit
05be1a09e6
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user