Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove scheduled functions complexity overhead, change recurrent api #6214

Merged
merged 12 commits into from
Jun 25, 2019
154 changes: 82 additions & 72 deletions cores/esp8266/Schedule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,31 @@
#include "interrupts.h"
#include "coredecls.h"

typedef std::function<bool(void)> mFuncT;

typedef std::function<void(void)> mSchedFuncT;
struct scheduled_fn_t
{
scheduled_fn_t* mNext = nullptr;
mFuncT mFunc;
esp8266::polledTimeout::periodicFastUs callNow;
schedule_e policy;

scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { }
mSchedFuncT mFunc;
};

static scheduled_fn_t* sFirst = nullptr;
static scheduled_fn_t* sLast = nullptr;
static scheduled_fn_t* sUnused = nullptr;
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

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
d-a-v marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -33,38 +39,33 @@ static scheduled_fn_t* get_fn_unsafe()
result = sUnused;
sUnused = sUnused->mNext;
result->mNext = nullptr;
result->callNow.reset(esp8266::polledTimeout::periodicFastUs::alwaysExpired);
}
// if no unused items, and count not too high, allocate a new one
else if (sCount < SCHEDULED_FN_MAX_COUNT)
{
result = new scheduled_fn_t;
d-a-v marked this conversation as resolved.
Show resolved Hide resolved
++sCount;
if (result)
++sCount;
}
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;
sUnused = fn;
}

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)

InterruptLock lockAllInterruptsInThisScope;

scheduled_fn_t* item = get_fn_unsafe();
if (!item)
return false;

if (repeat_us)
item->callNow.reset(repeat_us);
item->policy = policy;
item->mFunc = fn;

if (sFirst)
Expand All @@ -76,90 +77,99 @@ bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us, sc
return true;
}

IRAM_ATTR // (not only) called from ISR
bool schedule_function_us(const std::function<bool(void)>& fn, uint32_t repeat_us, schedule_e policy)
bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us)
{
return schedule_function_us(std::function<bool(void)>(fn), repeat_us, policy);
}
assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)

IRAM_ATTR // called from ISR
bool schedule_function(std::function<void(void)>&& fn, schedule_e policy)
{
return schedule_function_us([fn]() { fn(); return false; }, 0, policy);
InterruptLock lockAllInterruptsInThisScope;

recurrent_fn_t* item = new recurrent_fn_t(repeat_us);
devyte marked this conversation as resolved.
Show resolved Hide resolved
if (!item)
return false;

item->mFunc = fn;

if (rFirst)
{
item->mNext = rFirst;
rFirst = item;
}
else
rFirst = item;

return true;
}

IRAM_ATTR // called from ISR
bool schedule_function(const std::function<void(void)>& fn, schedule_e policy)
void run_scheduled_functions ()
{
return schedule_function(std::function<void(void)>(fn), policy);
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms

while (sFirst)
{
sFirst->mFunc();

{
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);
}
}
}

void run_scheduled_functions(schedule_e policy)
void run_scheduled_recurrent_functions ()
{
// 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)
return;

static bool fence = false;
{
InterruptLock lockAllInterruptsInThisScope;
d-a-v marked this conversation as resolved.
Show resolved Hide resolved
if (fence)
// prevent recursive calls from yield()
// (even if they are not allowed)
return;
fence = true;
}

esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
scheduled_fn_t* lastRecurring = nullptr;
scheduled_fn_t* nextCall = sFirst;
while (nextCall)
{
scheduled_fn_t* toCall = nextCall;
nextCall = nextCall->mNext;

// run scheduled function:
// - 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())
{
// function stays in list
lastRecurring = toCall;
}
else
{
// function removed from list
InterruptLock lockAllInterruptsInThisScope;

if (sFirst == toCall)
sFirst = sFirst->mNext;
else if (lastRecurring)
lastRecurring->mNext = toCall->mNext;
recurrent_fn_t* prev = nullptr;
recurrent_fn_t* current = rFirst;

if (sLast == toCall)
sLast = lastRecurring;
while (current)
if (current->callNow && !current->mFunc())
d-a-v marked this conversation as resolved.
Show resolved Hide resolved
{
// remove function from stack
InterruptLock lockAllInterruptsInThisScope;

recycle_fn_unsafe(toCall);
}
auto to_ditch = current;
if (prev)
prev->mNext = current = current->mNext;
d-a-v marked this conversation as resolved.
Show resolved Hide resolved
else
current = rFirst = rFirst->mNext;
d-a-v marked this conversation as resolved.
Show resolved Hide resolved
delete(to_ditch);
}
else
// function stays in list
lastRecurring = toCall;

if (policy == SCHEDULED_FUNCTION_ONCE_PER_LOOP && yieldNow)
{
// this is yield() in cont stack:
esp_schedule();
cont_yield(g_pcont);
prev = current;
current = current->mNext;
}
}

fence = false;
}
59 changes: 25 additions & 34 deletions cores/esp8266/Schedule.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,47 @@

#include <functional>

// This API is stabilizing
// Function signatures may change, internal queue will remain FIFO.
#define SCHEDULED_FN_MAX_COUNT 32

// scheduled functions called once:
//
// * internal queue is 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.
//
// `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
//
// when lambda is later called.
// * There is no mechanism for cancelling scheduled functions.
//
// * `yield` can be called from inside lambdas
//
// * `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.

#define SCHEDULED_FN_MAX_COUNT 32
bool schedule_function (const std::function<void(void)>& fn);

enum schedule_e
{
SCHEDULED_FUNCTION_ONCE_PER_LOOP,
SCHEDULED_FUNCTION_WITHOUT_YIELDELAYCALLS
};
// Run all scheduled functions.
// Use this function if your are not using `loop`, or `loop` does not return
// on a regular basis.

// * Run the lambda only once next time
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);
void run_scheduled_functions();

// recurrent scheduled function:
//
// * internal queue if not FIFO.
// * Run the lambda periodically about every <repeat_us> microseconds until
// it returns false.
// * 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
// timing critical operations.
bool schedule_function_us(std::function<bool(void)>&& fn,
uint32_t repeat_us,
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);
// * There is no mechanism for cancelling recurrent scheduled functions.
// * long running operations or yield() or delay() are not wise in the lambda.

// 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(schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us);

// Test recurrence and run recurrent scheduled functions.
// (internally called at every `yield()` and `loop()`)

void run_scheduled_recurrent_functions ();

#endif //ESP_SCHEDULE_H
#endif // ESP_SCHEDULE_H
5 changes: 3 additions & 2 deletions cores/esp8266/core_esp8266_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void preloop_update_frequency() {
static inline void esp_yield_within_cont() __attribute__((always_inline));
static void esp_yield_within_cont() {
cont_yield(g_pcont);
run_scheduled_functions(SCHEDULED_FUNCTION_WITHOUT_YIELDELAYCALLS);
run_scheduled_recurrent_functions();
}

extern "C" void esp_yield() {
Expand Down Expand Up @@ -129,7 +129,8 @@ static void loop_wrapper() {
setup_done = true;
}
loop();
run_scheduled_functions(SCHEDULED_FUNCTION_ONCE_PER_LOOP);
run_scheduled_functions();
run_scheduled_recurrent_functions();
esp_schedule();
}

Expand Down