Skip to content

Commit

Permalink
remove scheduled functions complexity overhead, change recurrent api (
Browse files Browse the repository at this point in the history
…#6214)

* remove scheduled functions complexity overhead, change recurrent functions api
  • Loading branch information
d-a-v authored Jun 25, 2019
1 parent f5a882d commit 05be1a0
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 104 deletions.
162 changes: 94 additions & 68 deletions cores/esp8266/Schedule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,34 @@
#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

// 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
Expand All @@ -33,38 +42,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;
++sCount;
result = (scheduled_fn_t*)malloc(sizeof(scheduled_fn_t));
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)

esp8266::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,88 +80,110 @@ 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);
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
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();

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

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;
{
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)
// 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)
recurrent_fn_t* prev = nullptr;
recurrent_fn_t* current = rFirst;

while (current)
{
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 (current->callNow && !current->mFunc())
{
if (toCall->mFunc())
// remove function from stack
esp8266::InterruptLock lockAllInterruptsInThisScope;

auto to_ditch = current;

if (prev)
{
// function stays in list
lastRecurring = toCall;
current = current->mNext;
prev->mNext = current;
}
else
{
// function removed from list
esp8266::InterruptLock lockAllInterruptsInThisScope;

if (sFirst == toCall)
sFirst = sFirst->mNext;
else if (lastRecurring)
lastRecurring->mNext = toCall->mNext;

if (sLast == toCall)
sLast = lastRecurring;

recycle_fn_unsafe(toCall);
rFirst = rFirst->mNext;
current = rFirst;
}

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

Expand Down
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

0 comments on commit 05be1a0

Please sign in to comment.