1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-04-21 10:26:06 +03:00

Longer delays for Ticker and some internal updates (#8625)

Adds max duration check. In case it is over SDK limit, enable 'repeat'ing timer with a duration proportional to the original one and count until it executes N times, only then run the callback.
Code with durations less than that executes as usual. Original proposal was to not create anything or create some kind of error state... which seems counter-productive to not help out with this pretty solvable use-case.

Additional updates, while refactoring the class
- Stronger types for internal time management using `std::chrono::duration`. Works the same, `std::chrono::duration` handles seconds <-> milliseconds conversion, and we don't have to remember the time type in each method. (...and even allow `once()` and `attach` as overloads instead of the current `_ms`-suffix, in a future update)
- `::detach()` when timer finishes. Fixes (unintentional?) side-effect that we remain `::active()`. Plus, this destroys any lambda-bound variables that will persist with the Ticker object. And, since we can't re-arm with the existing function (`Ticker::attach_ms(uint32_t just_the_time)` and etc.)
- `std::variant` aka union for internal callback storage (kind-of similar to #6918). Instead of having two separate code paths, **always** attach our static function and dispatch using type info. Also helps with the issue described above, since it will call `std::function` dtor when ptr + arg is attached instead of doing nothing.
- smarter copy and move, detaching existing timer on assignment and detaching the moved-in timer object in both ctor and assignment. Copying or moving a running timer no longer blindly copies `_timer` pointer, allowing to disarm the original one. Since we are a simple wrapper around `os_timer_t`, just do the simpler thing (and not re-schedule the callback, try to store original times, etc. polledTimeout already does it and is copyable)
This commit is contained in:
Max Prokhorov 2022-11-01 20:15:14 +03:00 committed by GitHub
parent 04494f0729
commit 27c0591756
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 230 additions and 85 deletions

View File

@ -146,7 +146,9 @@ void updateSensor(sensorType &sensor) {
SSEBroadcastState(sensor.name, sensor.value, newVal); // only broadcast if state is different SSEBroadcastState(sensor.name, sensor.value, newVal); // only broadcast if state is different
} }
sensor.value = newVal; sensor.value = newVal;
sensor.update.once(rand() % 20 + 10, std::bind(updateSensor, sensor)); // randomly update sensor sensor.update.once(rand() % 20 + 10, [&]() {
updateSensor(sensor);
}); // randomly update sensor
} }
void handleSubscribe() { void handleSubscribe() {

View File

@ -13,9 +13,11 @@ public:
ExampleClass(int pin, int duration) ExampleClass(int pin, int duration)
: _pin(pin), _duration(duration) { : _pin(pin), _duration(duration) {
pinMode(_pin, OUTPUT); pinMode(_pin, OUTPUT);
_myTicker.attach_ms(_duration, std::bind(&ExampleClass::classBlink, this)); _myTicker.attach_ms(_duration,
[this]() {
classBlink();
});
} }
~ExampleClass(){};
int _pin, _duration; int _pin, _duration;
Ticker _myTicker; Ticker _myTicker;
@ -53,7 +55,7 @@ void setup() {
scheduledTicker.attach_ms_scheduled(100, scheduledBlink); scheduledTicker.attach_ms_scheduled(100, scheduledBlink);
pinMode(LED4, OUTPUT); pinMode(LED4, OUTPUT);
parameterTicker.attach_ms(100, std::bind(parameterBlink, LED4)); parameterTicker.attach_ms(100, parameterBlink, LED4);
pinMode(LED5, OUTPUT); pinMode(LED5, OUTPUT);
lambdaTicker.attach_ms(100, []() { lambdaTicker.attach_ms(100, []() {

View File

@ -23,49 +23,113 @@
#include "eagle_soc.h" #include "eagle_soc.h"
#include "osapi.h" #include "osapi.h"
#include <Arduino.h>
#include "Ticker.h" #include "Ticker.h"
Ticker::Ticker() // ETSTimer is part of the instance, and we don't have any state besides
: _timer(nullptr) {} // the things required for the callback. Allow copies and moves, but
// disable any member copies and default-init + detach() instead.
Ticker::~Ticker() Ticker::~Ticker()
{ {
detach(); detach();
} }
void Ticker::_attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg) Ticker::Ticker(const Ticker&)
{ {
if (_timer) }
{
Ticker& Ticker::operator=(const Ticker&)
{
detach();
return *this;
}
Ticker::Ticker(Ticker&& other) noexcept
{
other.detach();
}
Ticker& Ticker::operator=(Ticker&& other) noexcept
{
other.detach();
detach();
return *this;
}
void Ticker::_attach(Ticker::Milliseconds milliseconds, bool repeat)
{
if (_timer) {
os_timer_disarm(_timer); os_timer_disarm(_timer);
} } else {
else _timer = &_timer_internal;
{
_timer = &_etsTimer;
} }
os_timer_setfn(_timer, callback, arg); os_timer_setfn(_timer,
os_timer_arm(_timer, milliseconds, repeat); [](void* ptr) {
reinterpret_cast<Ticker*>(ptr)->_static_callback();
}, this);
_repeat = repeat;
// whenever duration excedes this limit, make timer repeatable N times
// in case it is really repeatable, it will reset itself and continue as usual
size_t total = 0;
if (milliseconds > DurationMax) {
total = 1;
while (milliseconds > DurationMax) {
total *= 2;
milliseconds /= 2;
}
_tick.reset(new callback_tick_t{
.total = total,
.count = 0,
});
repeat = true;
}
os_timer_arm(_timer, milliseconds.count(), repeat);
} }
void Ticker::detach() void Ticker::detach()
{ {
if (!_timer) if (_timer) {
return; os_timer_disarm(_timer);
_timer = nullptr;
os_timer_disarm(_timer); _tick.reset(nullptr);
_timer = nullptr; _callback = std::monostate{};
_callback_function = nullptr; }
} }
bool Ticker::active() const bool Ticker::active() const
{ {
return _timer; return _timer != nullptr;
} }
void Ticker::_static_callback(void* arg) void Ticker::_static_callback()
{ {
Ticker* _this = reinterpret_cast<Ticker*>(arg); if (_tick) {
if (_this && _this->_callback_function) ++_tick->count;
_this->_callback_function(); if (_tick->count < _tick->total) {
return;
}
}
std::visit([](auto&& callback) {
using T = std::decay_t<decltype(callback)>;
if constexpr (std::is_same_v<T, callback_ptr_t>) {
callback.func(callback.arg);
} else if constexpr (std::is_same_v<T, callback_function_t>) {
callback();
}
}, _callback);
if (_repeat) {
if (_tick) {
_tick->count = 0;
}
return;
}
detach();
} }

View File

@ -19,140 +19,217 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#ifndef TICKER_H #pragma once
#define TICKER_H
#include <chrono>
#include <functional> #include <functional>
#include <memory>
#include <variant>
#include <Arduino.h>
#include <Schedule.h> #include <Schedule.h>
#include <ets_sys.h> #include <ets_sys.h>
class Ticker class Ticker
{ {
public: public:
Ticker(); // Our helper type to support any callable object
// In case of a lambda with bound variable(s), it will be destroyed
// either when the timer expires or detach() is called
using callback_function_t = std::function<void()>;
// Native SDK type, simple function with void* argument
using callback_with_arg_t = void(*)(void*);
// Helper type to allow type coercion on function argument
// Only works with a function pointer. Argument *must not* be larger than the size of the `void*`
template <typename T>
using remove_cvref_t = typename std::remove_cv_t<
typename std::remove_reference_t<T>>;
template <typename T, typename Y = remove_cvref_t<T>>
using callback_with_typed_arg_t = void(*)(Y);
Ticker() = default;
~Ticker(); ~Ticker();
typedef void (*callback_with_arg_t)(void*); Ticker(const Ticker&);
typedef std::function<void(void)> callback_function_t; Ticker& operator=(const Ticker&);
Ticker(Ticker&&) noexcept;
Ticker& operator=(Ticker&&) noexcept;
// callback will be called at following loop() after ticker fires // callback will be called at following loop() after ticker fires
void attach_scheduled(float seconds, callback_function_t callback) void attach_scheduled(float seconds, callback_function_t callback)
{ {
_callback_function = [callback]() { schedule_function(callback); }; _callback = [callback]() {
_attach_ms(1000UL * seconds, true); schedule_function(callback);
};
_attach(Seconds(seconds), true);
} }
// callback will be called in SYS ctx when ticker fires // callback will be called in SYS ctx when ticker fires
void attach(float seconds, callback_function_t callback) void attach(float seconds, callback_function_t callback)
{ {
_callback_function = std::move(callback); _callback = std::move(callback);
_attach_ms(1000UL * seconds, true); _attach(Seconds(seconds), true);
} }
// callback will be called at following loop() after ticker fires // callback will be called at following loop() after ticker fires
void attach_ms_scheduled(uint32_t milliseconds, callback_function_t callback) void attach_ms_scheduled(uint32_t milliseconds, callback_function_t callback)
{ {
_callback_function = [callback]() { schedule_function(callback); }; _callback = [callback]() {
_attach_ms(milliseconds, true); schedule_function(callback);
};
_attach(Milliseconds(milliseconds), true);
} }
// callback will be called at following yield() after ticker fires // callback will be called at following yield() after ticker fires
void attach_ms_scheduled_accurate(uint32_t milliseconds, callback_function_t callback) void attach_ms_scheduled_accurate(uint32_t milliseconds, callback_function_t callback)
{ {
_callback_function = [callback]() { schedule_recurrent_function_us([callback]() { callback(); return false; }, 0); }; _callback = [callback]() {
_attach_ms(milliseconds, true); schedule_recurrent_function_us([callback]() {
callback();
return false;
}, 0);
};
_attach(Milliseconds(milliseconds), true);
} }
// callback will be called in SYS ctx when ticker fires // callback will be called in SYS ctx when ticker fires
void attach_ms(uint32_t milliseconds, callback_function_t callback) void attach_ms(uint32_t milliseconds, callback_function_t callback)
{ {
_callback_function = std::move(callback); _callback = std::move(callback);
_attach_ms(milliseconds, true); _attach(Milliseconds(milliseconds), true);
} }
// callback will be called in SYS ctx when ticker fires // callback will still be called in SYS ctx when ticker fires
template<typename TArg> template <typename Func, typename Arg>
void attach(float seconds, void (*callback)(TArg), TArg arg) void attach(float seconds, Func func, Arg arg)
{ {
#pragma GCC diagnostic push _callback = make_callback_ptr(func, arg);
#pragma GCC diagnostic ignored "-Wcast-function-type" _attach(Seconds(seconds), true);
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_ms(1000UL * seconds, true, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void*>(arg));
#pragma GCC diagnostic pop
} }
// callback will be called in SYS ctx when ticker fires // callback will still be called in SYS ctx when ticker fires
template<typename TArg> template <typename Func, typename Arg>
void attach_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg) void attach_ms(uint32_t milliseconds, Func func, Arg arg)
{ {
#pragma GCC diagnostic push _callback = make_callback_ptr(func, arg);
#pragma GCC diagnostic ignored "-Wcast-function-type" _attach(Milliseconds(milliseconds), true);
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)");
_attach_ms(milliseconds, true, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void*>(arg));
#pragma GCC diagnostic pop
} }
// callback will be called at following loop() after ticker fires // callback will be called at following loop() after ticker fires
void once_scheduled(float seconds, callback_function_t callback) void once_scheduled(float seconds, callback_function_t callback)
{ {
_callback_function = [callback]() { schedule_function(callback); }; _callback = [callback]() { schedule_function(callback); };
_attach_ms(1000UL * seconds, false); _attach(Seconds(seconds), false);
} }
// callback will be called in SYS ctx when ticker fires // callback will be called in SYS ctx when ticker fires
void once(float seconds, callback_function_t callback) void once(float seconds, callback_function_t callback)
{ {
_callback_function = std::move(callback); _callback = std::move(callback);
_attach_ms(1000UL * seconds, false); _attach(Seconds(seconds), false);
} }
// callback will be called at following loop() after ticker fires // callback will be called at following loop() after ticker fires
void once_ms_scheduled(uint32_t milliseconds, callback_function_t callback) void once_ms_scheduled(uint32_t milliseconds, callback_function_t callback)
{ {
_callback_function = [callback]() { schedule_function(callback); }; _callback = [callback]() { schedule_function(callback); };
_attach_ms(milliseconds, false); _attach(Milliseconds(milliseconds), false);
} }
// callback will be called in SYS ctx when ticker fires // callback will be called in SYS ctx when ticker fires
void once_ms(uint32_t milliseconds, callback_function_t callback) void once_ms(uint32_t milliseconds, callback_function_t callback)
{ {
_callback_function = std::move(callback); _callback = std::move(callback);
_attach_ms(milliseconds, false); _attach(Milliseconds(milliseconds), false);
} }
// callback will be called in SYS ctx when ticker fires // callback will be called in SYS ctx when ticker fires
template<typename TArg> template <typename Func, typename Arg>
void once(float seconds, void (*callback)(TArg), TArg arg) void once(float seconds, Func func, Arg arg)
{ {
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); _callback = make_callback_ptr(func, arg);
_attach_ms(1000UL * seconds, false, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void*>(arg)); _attach(Seconds(seconds), false);
} }
// callback will be called in SYS ctx when ticker fires // callback will be called in SYS ctx when ticker fires
template<typename TArg> template <typename Func, typename Arg>
void once_ms(uint32_t milliseconds, void (*callback)(TArg), TArg arg) void once_ms(uint32_t milliseconds, Func func, Arg arg)
{ {
static_assert(sizeof(TArg) <= sizeof(void*), "attach() callback argument size must be <= sizeof(void*)"); _callback = make_callback_ptr(func, arg);
_attach_ms(milliseconds, false, reinterpret_cast<callback_with_arg_t>(callback), reinterpret_cast<void*>(arg)); _attach(Milliseconds(milliseconds), false);
} }
// if active(), disables currently running timer
void detach(); void detach();
bool active() const; bool active() const;
protected: explicit operator bool() const {
static void _static_callback(void* arg); return active();
void _attach_ms(uint32_t milliseconds, bool repeat, callback_with_arg_t callback, void* arg);
void _attach_ms(uint32_t milliseconds, bool repeat)
{
_attach_ms(milliseconds, repeat, _static_callback, this);
} }
ETSTimer* _timer; protected:
callback_function_t _callback_function = nullptr; // internals use this as duration
using Milliseconds = std::chrono::duration<uint32_t, std::ratio<1, 1000>>;
// we allow a floating point as input as well
// float -> u32 has some precision issues, though
using Seconds = std::chrono::duration<float, std::ratio<1>>;
// NONOS SDK timer object duration cannot be longer than 6870947 (0x68D7A3)
// when that's the case, we split execution into multiple 'ticks'
static constexpr auto DurationMax = Milliseconds(6870947);
struct callback_tick_t
{
uint32_t total = 0;
uint32_t count = 0;
};
void _static_callback();
void _attach(Milliseconds milliseconds, bool repeat);
void _attach(Seconds seconds, bool repeat)
{
_attach(std::chrono::duration_cast<Milliseconds>(seconds), repeat);
}
std::unique_ptr<callback_tick_t> _tick;
bool _repeat = false;
ETSTimer* _timer = nullptr;
private: private:
ETSTimer _etsTimer; struct callback_ptr_t
{
callback_with_arg_t func;
void* arg;
};
// original implementation inluded type coersion of integer values that would fit into uintptr_t
// to avoid writing these in our every method, use a generic type that automatically converts it
// (XXX it is a weird hack, though, consider removing this in the future and prever void* instead)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
template <typename T, typename Y = remove_cvref_t<T>>
static callback_ptr_t make_callback_ptr(callback_with_typed_arg_t<Y> func, T arg) {
static_assert(sizeof(Y) <= sizeof(void*), "");
return callback_ptr_t{
.func = reinterpret_cast<callback_with_arg_t>(func),
.arg = reinterpret_cast<void*>(arg),
};
}
#pragma GCC diagnostic pop
using callback_data_t = std::variant<
std::monostate,
callback_ptr_t,
callback_function_t>;
callback_data_t _callback;
ETSTimer _timer_internal{};
}; };
#endif //TICKER_H