Skip to content

Commit 05be1a0

Browse files
authored
remove scheduled functions complexity overhead, change recurrent api (esp8266#6214)
* remove scheduled functions complexity overhead, change recurrent functions api
1 parent f5a882d commit 05be1a0

File tree

3 files changed

+122
-104
lines changed

3 files changed

+122
-104
lines changed

cores/esp8266/Schedule.cpp

+94-68
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,34 @@
66
#include "interrupts.h"
77
#include "coredecls.h"
88

9-
typedef std::function<bool(void)> mFuncT;
10-
9+
typedef std::function<void(void)> mSchedFuncT;
1110
struct scheduled_fn_t
1211
{
1312
scheduled_fn_t* mNext = nullptr;
14-
mFuncT mFunc;
15-
esp8266::polledTimeout::periodicFastUs callNow;
16-
schedule_e policy;
17-
18-
scheduled_fn_t() : callNow(esp8266::polledTimeout::periodicFastUs::alwaysExpired) { }
13+
mSchedFuncT mFunc;
1914
};
2015

2116
static scheduled_fn_t* sFirst = nullptr;
2217
static scheduled_fn_t* sLast = nullptr;
2318
static scheduled_fn_t* sUnused = nullptr;
2419
static int sCount = 0;
2520

21+
typedef std::function<bool(void)> mRecFuncT;
22+
struct recurrent_fn_t
23+
{
24+
recurrent_fn_t* mNext = nullptr;
25+
mRecFuncT mFunc;
26+
esp8266::polledTimeout::periodicFastUs callNow;
27+
recurrent_fn_t (esp8266::polledTimeout::periodicFastUs interval): callNow(interval) { }
28+
};
29+
30+
static recurrent_fn_t* rFirst = nullptr; // fifo not needed
31+
32+
// Returns a pointer to an unused sched_fn_t,
33+
// or if none are available allocates a new one,
34+
// or nullptr if limit is reached
2635
IRAM_ATTR // called from ISR
27-
static scheduled_fn_t* get_fn_unsafe()
36+
static scheduled_fn_t* get_fn_unsafe ()
2837
{
2938
scheduled_fn_t* result = nullptr;
3039
// try to get an item from unused items list
@@ -33,38 +42,33 @@ static scheduled_fn_t* get_fn_unsafe()
3342
result = sUnused;
3443
sUnused = sUnused->mNext;
3544
result->mNext = nullptr;
36-
result->callNow.reset(esp8266::polledTimeout::periodicFastUs::alwaysExpired);
3745
}
3846
// if no unused items, and count not too high, allocate a new one
3947
else if (sCount < SCHEDULED_FN_MAX_COUNT)
4048
{
41-
result = new scheduled_fn_t;
42-
++sCount;
49+
result = (scheduled_fn_t*)malloc(sizeof(scheduled_fn_t));
50+
if (result)
51+
++sCount;
4352
}
4453
return result;
4554
}
4655

47-
static void recycle_fn_unsafe(scheduled_fn_t* fn)
56+
static void recycle_fn_unsafe (scheduled_fn_t* fn)
4857
{
4958
fn->mFunc = nullptr; // special overload in c++ std lib
5059
fn->mNext = sUnused;
5160
sUnused = fn;
5261
}
5362

5463
IRAM_ATTR // (not only) called from ISR
55-
bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us, schedule_e policy)
64+
bool schedule_function (const std::function<void(void)>& fn)
5665
{
57-
assert(repeat_us < decltype(scheduled_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
58-
5966
esp8266::InterruptLock lockAllInterruptsInThisScope;
6067

6168
scheduled_fn_t* item = get_fn_unsafe();
6269
if (!item)
6370
return false;
6471

65-
if (repeat_us)
66-
item->callNow.reset(repeat_us);
67-
item->policy = policy;
6872
item->mFunc = fn;
6973

7074
if (sFirst)
@@ -76,88 +80,110 @@ bool schedule_function_us(std::function<bool(void)>&& fn, uint32_t repeat_us, sc
7680
return true;
7781
}
7882

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

85-
IRAM_ATTR // called from ISR
86-
bool schedule_function(std::function<void(void)>&& fn, schedule_e policy)
87-
{
88-
return schedule_function_us([fn]() { fn(); return false; }, 0, policy);
87+
esp8266::InterruptLock lockAllInterruptsInThisScope;
88+
89+
recurrent_fn_t* item = new recurrent_fn_t(repeat_us);
90+
if (!item)
91+
return false;
92+
93+
item->mFunc = fn;
94+
95+
if (rFirst)
96+
{
97+
item->mNext = rFirst;
98+
rFirst = item;
99+
}
100+
else
101+
rFirst = item;
102+
103+
return true;
89104
}
90105

91-
IRAM_ATTR // called from ISR
92-
bool schedule_function(const std::function<void(void)>& fn, schedule_e policy)
106+
void run_scheduled_functions ()
93107
{
94-
return schedule_function(std::function<void(void)>(fn), policy);
108+
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
109+
110+
while (sFirst)
111+
{
112+
sFirst->mFunc();
113+
114+
{
115+
esp8266::InterruptLock lockAllInterruptsInThisScope;
116+
117+
auto to_recycle = sFirst;
118+
sFirst = sFirst->mNext;
119+
if (!sFirst)
120+
sLast = nullptr;
121+
recycle_fn_unsafe(to_recycle);
122+
}
123+
124+
if (yieldNow)
125+
{
126+
// because scheduled function are allowed to last:
127+
// this is yield() in cont stack:
128+
esp_schedule();
129+
cont_yield(g_pcont);
130+
}
131+
}
95132
}
96133

97-
void run_scheduled_functions(schedule_e policy)
134+
void run_scheduled_recurrent_functions ()
98135
{
99136
// Note to the reader:
100137
// There is no exposed API to remove a scheduled function:
101138
// Scheduled functions are removed only from this function, and
102139
// its purpose is that it is never called from an interrupt
103140
// (always on cont stack).
104141

142+
if (!rFirst)
143+
return;
144+
105145
static bool fence = false;
106146
{
107-
esp8266::InterruptLock lockAllInterruptsInThisScope;
147+
// fence is like a mutex but as we are never called from ISR,
148+
// locking is useless here. Leaving comment for reference.
149+
//esp8266::InterruptLock lockAllInterruptsInThisScope;
150+
108151
if (fence)
109152
// prevent recursive calls from yield()
153+
// (even if they are not allowed)
110154
return;
111155
fence = true;
112156
}
113157

114-
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
115-
scheduled_fn_t* lastRecurring = nullptr;
116-
scheduled_fn_t* nextCall = sFirst;
117-
while (nextCall)
158+
recurrent_fn_t* prev = nullptr;
159+
recurrent_fn_t* current = rFirst;
160+
161+
while (current)
118162
{
119-
scheduled_fn_t* toCall = nextCall;
120-
nextCall = nextCall->mNext;
121-
122-
// run scheduled function:
123-
// - when its schedule policy allows it anytime
124-
// - or if we are called at loop() time
125-
// and
126-
// - its time policy allows it
127-
if ( ( toCall->policy == SCHEDULED_FUNCTION_WITHOUT_YIELDELAYCALLS
128-
|| policy == SCHEDULED_FUNCTION_ONCE_PER_LOOP)
129-
&& toCall->callNow)
163+
if (current->callNow && !current->mFunc())
130164
{
131-
if (toCall->mFunc())
165+
// remove function from stack
166+
esp8266::InterruptLock lockAllInterruptsInThisScope;
167+
168+
auto to_ditch = current;
169+
170+
if (prev)
132171
{
133-
// function stays in list
134-
lastRecurring = toCall;
172+
current = current->mNext;
173+
prev->mNext = current;
135174
}
136175
else
137176
{
138-
// function removed from list
139-
esp8266::InterruptLock lockAllInterruptsInThisScope;
140-
141-
if (sFirst == toCall)
142-
sFirst = sFirst->mNext;
143-
else if (lastRecurring)
144-
lastRecurring->mNext = toCall->mNext;
145-
146-
if (sLast == toCall)
147-
sLast = lastRecurring;
148-
149-
recycle_fn_unsafe(toCall);
177+
rFirst = rFirst->mNext;
178+
current = rFirst;
150179
}
180+
181+
delete(to_ditch);
151182
}
152183
else
153-
// function stays in list
154-
lastRecurring = toCall;
155-
156-
if (policy == SCHEDULED_FUNCTION_ONCE_PER_LOOP && yieldNow)
157184
{
158-
// this is yield() in cont stack:
159-
esp_schedule();
160-
cont_yield(g_pcont);
185+
prev = current;
186+
current = current->mNext;
161187
}
162188
}
163189

cores/esp8266/Schedule.h

+25-34
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,47 @@
33

44
#include <functional>
55

6-
// This API is stabilizing
7-
// Function signatures may change, internal queue will remain FIFO.
6+
#define SCHEDULED_FN_MAX_COUNT 32
7+
8+
// scheduled functions called once:
89
//
10+
// * internal queue is FIFO.
911
// * Add the given lambda to a fifo list of lambdas, which is run when
10-
// - `loop` function returns,
11-
// - or `yield` is called,
12-
// - or `run_scheduled_functions` is called.
13-
//
12+
// `loop` function returns.
1413
// * Use lambdas to pass arguments to a function, or call a class/static
1514
// member function.
16-
//
1715
// * Please ensure variables or instances used from inside lambda will exist
18-
// when lambda is later called
19-
//
16+
// when lambda is later called.
2017
// * There is no mechanism for cancelling scheduled functions.
21-
//
22-
// * `yield` can be called from inside lambdas
23-
//
18+
// * `yield` can be called from inside lambdas.
2419
// * Returns false if the number of scheduled functions exceeds
2520
// SCHEDULED_FN_MAX_COUNT.
21+
// * Run the lambda only once next time.
2622

27-
#define SCHEDULED_FN_MAX_COUNT 32
23+
bool schedule_function (const std::function<void(void)>& fn);
2824

29-
enum schedule_e
30-
{
31-
SCHEDULED_FUNCTION_ONCE_PER_LOOP,
32-
SCHEDULED_FUNCTION_WITHOUT_YIELDELAYCALLS
33-
};
25+
// Run all scheduled functions.
26+
// Use this function if your are not using `loop`, or `loop` does not return
27+
// on a regular basis.
3428

35-
// * Run the lambda only once next time
36-
bool schedule_function(std::function<void(void)>&& fn,
37-
schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
38-
bool schedule_function(const std::function<void(void)>& fn,
39-
schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
29+
void run_scheduled_functions();
4030

31+
// recurrent scheduled function:
32+
//
33+
// * internal queue if not FIFO.
4134
// * Run the lambda periodically about every <repeat_us> microseconds until
4235
// it returns false.
4336
// * Note that it may be more than <repeat_us> microseconds between calls if
4437
// `yield` is not called frequently, and therefore should not be used for
4538
// timing critical operations.
46-
bool schedule_function_us(std::function<bool(void)>&& fn,
47-
uint32_t repeat_us,
48-
schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
49-
bool schedule_function_us(const std::function<bool(void)>& fn,
50-
uint32_t repeat_us,
51-
schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
39+
// * There is no mechanism for cancelling recurrent scheduled functions.
40+
// * long running operations or yield() or delay() are not wise in the lambda.
5241

53-
// Run all scheduled functions.
54-
// Use this function if your are not using `loop`, or `loop` does not return
55-
// on a regular basis.
56-
void run_scheduled_functions(schedule_e policy = SCHEDULED_FUNCTION_ONCE_PER_LOOP);
42+
bool schedule_recurrent_function_us (const std::function<bool(void)>& fn, uint32_t repeat_us);
43+
44+
// Test recurrence and run recurrent scheduled functions.
45+
// (internally called at every `yield()` and `loop()`)
46+
47+
void run_scheduled_recurrent_functions ();
5748

58-
#endif //ESP_SCHEDULE_H
49+
#endif // ESP_SCHEDULE_H

cores/esp8266/core_esp8266_main.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ void preloop_update_frequency() {
8787
static inline void esp_yield_within_cont() __attribute__((always_inline));
8888
static void esp_yield_within_cont() {
8989
cont_yield(g_pcont);
90-
run_scheduled_functions(SCHEDULED_FUNCTION_WITHOUT_YIELDELAYCALLS);
90+
run_scheduled_recurrent_functions();
9191
}
9292

9393
extern "C" void esp_yield() {
@@ -129,7 +129,8 @@ static void loop_wrapper() {
129129
setup_done = true;
130130
}
131131
loop();
132-
run_scheduled_functions(SCHEDULED_FUNCTION_ONCE_PER_LOOP);
132+
run_scheduled_functions();
133+
run_scheduled_recurrent_functions();
133134
esp_schedule();
134135
}
135136

0 commit comments

Comments
 (0)