diff --git a/cores/esp8266/Schedule.cpp b/cores/esp8266/Schedule.cpp new file mode 100644 index 000000000..5c3861200 --- /dev/null +++ b/cores/esp8266/Schedule.cpp @@ -0,0 +1,97 @@ +#include "Schedule.h" + +struct scheduled_fn_t +{ + scheduled_fn_t* mNext; + std::function mFunc; +}; + +static scheduled_fn_t* sFirst = 0; +static scheduled_fn_t* sLast = 0; + +static scheduled_fn_t* sFirstUnused = 0; +static scheduled_fn_t* sLastUnused = 0; + +static int sCount = 0; + +static void init_lists() +{ + if (sCount != 0) { + return; + } + while (sCount < SCHEDULED_FN_INITIAL_COUNT) { + scheduled_fn_t* it = new scheduled_fn_t; + if (sCount == 0) { + sFirstUnused = it; + } + else { + sLastUnused->mNext = it; + } + sLastUnused = it; + ++sCount; + } + sLastUnused->mNext = NULL; +} + +static scheduled_fn_t* get_fn() { + scheduled_fn_t* result = NULL; + // try to get an item from unused items list + if (sFirstUnused) { + result = sFirstUnused; + sFirstUnused = result->mNext; + if (sFirstUnused == NULL) { + sLastUnused = NULL; + } + } + // 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; + result->mNext = NULL; + ++sCount; + } + return result; +} + +static void recycle_fn(scheduled_fn_t* fn) +{ + if (!sLastUnused) { + sFirstUnused = fn; + } + else { + sLastUnused->mNext = fn; + } + fn->mNext = NULL; + sLastUnused = fn; +} + +bool schedule_function(std::function fn) +{ + scheduled_fn_t* item = get_fn(); + if (!item) { + return false; + } + item->mFunc = fn; + item->mNext = NULL; + if (!sFirst) { + sFirst = item; + } + else { + sLast->mNext = item; + } + sLast = item; + return true; +} + +void run_scheduled_functions() +{ + while (sFirst) { + scheduled_fn_t* item = sFirst; + sFirst = item->mNext; + if (sFirst == NULL) { + sLast = NULL; + } + item->mFunc(); + item->mFunc = std::function(); + recycle_fn(item); + } +} diff --git a/cores/esp8266/Schedule.h b/cores/esp8266/Schedule.h new file mode 100644 index 000000000..339997294 --- /dev/null +++ b/cores/esp8266/Schedule.h @@ -0,0 +1,27 @@ +#ifndef ESP_SCHEDULE_H +#define ESP_SCHEDULE_H + +#include + +#define SCHEDULED_FN_MAX_COUNT 32 +#define SCHEDULED_FN_INITIAL_COUNT 4 + +// Warning +// This API is not considered stable. +// Function signatures will change. +// You have been warned. + +// Run given function next time `loop` function returns, +// or `run_scheduled_functions` is called. +// Use std::bind to pass arguments to a function, or call a class member function. +// Note: there is no mechanism for cancelling scheduled functions. +// Keep that in mind when binding functions to objects which may have short lifetime. +// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT. +bool schedule_function(std::function fn); + +// 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(); + +#endif //ESP_SCHEDULE_H diff --git a/cores/esp8266/core_esp8266_main.cpp b/cores/esp8266/core_esp8266_main.cpp index 634eb7902..13bb645e2 100644 --- a/cores/esp8266/core_esp8266_main.cpp +++ b/cores/esp8266/core_esp8266_main.cpp @@ -23,6 +23,7 @@ //This may be used to change user task stack size: //#define CONT_STACKSIZE 4096 #include +#include "Schedule.h" extern "C" { #include "ets_sys.h" #include "os_type.h" @@ -119,6 +120,7 @@ static void loop_wrapper() { setup_done = true; } loop(); + run_scheduled_functions(); esp_schedule(); } diff --git a/tests/device/test_schedule/test_schedule.ino b/tests/device/test_schedule/test_schedule.ino new file mode 100644 index 000000000..812d3339b --- /dev/null +++ b/tests/device/test_schedule/test_schedule.ino @@ -0,0 +1,77 @@ +#include +#include +#include + +BS_ENV_DECLARE(); + +void setup() +{ + Serial.begin(115200); + BS_RUN(Serial); +} + + +TEST_CASE("scheduled functions are executed", "[schedule]") +{ + bool executed = false; + CHECK(schedule_function([&](){ + executed = true; + })); + run_scheduled_functions(); + CHECK(executed); +} + +TEST_CASE("scheduled functions are executed in correct order", "[schedule]") +{ + int counter = 0; + auto fn = [&](int id) { + CHECK(id == counter); + ++counter; + }; + for (int i = 0; i < 8; ++i) { + schedule_function(std::bind(fn, i)); + } + run_scheduled_functions(); +} + +TEST_CASE("functions are only executed once", "[schedule]") +{ + int counter = 0; + auto fn = [&](){ + ++counter; + }; + schedule_function(fn); + schedule_function(fn); + schedule_function(fn); + run_scheduled_functions(); + CHECK(counter == 3); + counter = 0; + run_scheduled_functions(); + CHECK(counter == 0); +} + +TEST_CASE("can schedule SCHEDULED_FN_MAX_COUNT functions", "[schedule]") +{ + int counter = 0; + auto fn = [&](int id) { + CHECK(id == counter); + CHECK(id < SCHEDULED_FN_MAX_COUNT); + ++counter; + }; + int i; + for (i = 0; i < SCHEDULED_FN_MAX_COUNT; ++i) { + CHECK(schedule_function(std::bind(fn, i))); + } + CHECK(!schedule_function(std::bind(fn, i))); + run_scheduled_functions(); + CHECK(counter == SCHEDULED_FN_MAX_COUNT); + counter = 0; + for (i = 0; i < SCHEDULED_FN_MAX_COUNT; ++i) { + CHECK(schedule_function(std::bind(fn, i))); + } + CHECK(!schedule_function(std::bind(fn, i))); + run_scheduled_functions(); + CHECK(counter == SCHEDULED_FN_MAX_COUNT); +} + +void loop(){}