/*
|
|
|
|
Part of the SYSTEM MODULE
|
|
|
|
Copyright (C) 2019-2021 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
|
|
|
|
*/
|
|
#pragma once
|
|
|
|
#include <Arduino.h>
|
|
#include <sys/pgmspace.h>
|
|
|
|
#include <chrono>
|
|
#include <memory>
|
|
|
|
#include "compat.h"
|
|
|
|
// missing in our original header
|
|
extern "C" int memcmp_P(const void*, const void*, size_t);
|
|
|
|
namespace espurna {
|
|
namespace duration {
|
|
|
|
// Only micros are 64bit, millis stored as 32bit to match what is actually returned & used by Core functions
|
|
using Microseconds = std::chrono::duration<uint64_t, std::micro>;
|
|
using Milliseconds = std::chrono::duration<uint32_t, std::milli>;
|
|
|
|
// Our own helper types, a lot of things are based off of the `millis()`
|
|
// (and it can be seamlessly used with any Core functions accepting u32 millisecond inputs)
|
|
using Seconds = std::chrono::duration<uint32_t, std::ratio<1> >;
|
|
using Minutes = std::chrono::duration<uint32_t, std::ratio<60> >;
|
|
using Hours = std::chrono::duration<uint32_t, std::ratio<Minutes::period::num * 60> >;
|
|
using Days = std::chrono::duration<uint32_t, std::ratio<Hours::period::num * 24> >;
|
|
|
|
} // namespace duration
|
|
|
|
// base class for loop / oneshot / generic callbacks that do not need arguments
|
|
// *not expected* to be used instead of std function at all times.
|
|
// main purpose of this special class is to circumvent the need for rtti in
|
|
// our gcc stl implementation and retrieve the 'target function' pointer
|
|
// (*should* be different in gcc 11 / 12 though, target() became constexpr)
|
|
struct Callback {
|
|
using Type = void (*)();
|
|
using WrapperType = std::function<void()>;
|
|
|
|
Callback() = default;
|
|
|
|
Callback(const Callback& other) :
|
|
_storage(nullptr),
|
|
_type(other._type)
|
|
{
|
|
copy(other);
|
|
}
|
|
|
|
Callback& operator=(const Callback& other) {
|
|
reset();
|
|
copy(other);
|
|
return *this;
|
|
}
|
|
|
|
Callback(const Callback&&) = delete;
|
|
Callback(Callback&& other) noexcept :
|
|
_storage(nullptr),
|
|
_type(other._type)
|
|
{
|
|
move(other);
|
|
}
|
|
|
|
Callback& operator=(Callback&& other) noexcept;
|
|
|
|
template <typename T>
|
|
using is_callback = std::is_same<std::remove_cvref<T>, Callback>;
|
|
|
|
template <typename T>
|
|
using is_type = std::is_same<T, Type>;
|
|
|
|
template <typename T>
|
|
using type_convertible = std::is_convertible<T, Type>;
|
|
|
|
template <typename T>
|
|
using wrapper_convertible = std::is_convertible<T, WrapperType>;
|
|
|
|
// when T *can* be converted into Callback::Type
|
|
// usually, function pointer *or* lambda without capture list
|
|
template <typename T,
|
|
typename = typename std::enable_if<
|
|
is_type<T>::value
|
|
|| type_convertible<T>::value>::type>
|
|
constexpr Callback(T callback) noexcept :
|
|
_storage(Type(callback)),
|
|
_type(StorageType::Simple)
|
|
{}
|
|
|
|
// anything else convertible into std function
|
|
template <typename T,
|
|
typename = typename std::enable_if<
|
|
!is_callback<T>::value>::type,
|
|
typename = typename std::enable_if<
|
|
wrapper_convertible<T>::value>::type,
|
|
typename = typename std::enable_if<
|
|
!type_convertible<T>::value>::type>
|
|
Callback(T callback) :
|
|
_storage(WrapperType(std::move(callback))),
|
|
_type(StorageType::Wrapper)
|
|
{
|
|
static_assert(!is_callback<T>::value, "");
|
|
}
|
|
|
|
~Callback() {
|
|
reset();
|
|
}
|
|
|
|
bool isEmpty() const {
|
|
return (_type == StorageType::Empty);
|
|
}
|
|
|
|
bool isSimple() const {
|
|
return (_type == StorageType::Simple);
|
|
}
|
|
|
|
bool isWrapped() const {
|
|
return (_type == StorageType::Wrapper);
|
|
}
|
|
|
|
bool operator==(Type callback) const {
|
|
return isSimple() && (_storage.simple == callback);
|
|
}
|
|
|
|
void reset();
|
|
void swap(Callback&) noexcept;
|
|
void operator()() const;
|
|
|
|
private:
|
|
union Storage {
|
|
WrapperType wrapper;
|
|
Type simple;
|
|
|
|
~Storage() {
|
|
}
|
|
|
|
explicit Storage(WrapperType callback) :
|
|
wrapper(std::move(callback))
|
|
{}
|
|
|
|
constexpr explicit Storage(Type callback) :
|
|
simple(callback)
|
|
{}
|
|
|
|
constexpr explicit Storage(std::nullptr_t) :
|
|
simple(nullptr)
|
|
{}
|
|
};
|
|
|
|
enum class StorageType {
|
|
Empty,
|
|
Simple,
|
|
Wrapper,
|
|
};
|
|
|
|
void copy(const Callback&);
|
|
void move(Callback&) noexcept;
|
|
|
|
Storage _storage { nullptr };
|
|
StorageType _type { StorageType::Empty };
|
|
};
|
|
|
|
// aka `std::source_location`
|
|
struct SourceLocation {
|
|
int line;
|
|
const char* file;
|
|
const char* func;
|
|
};
|
|
|
|
inline SourceLocation trim_source_location(SourceLocation value) {
|
|
for (auto* ptr = value.file; *ptr != '\0'; ++ptr) {
|
|
if ((*ptr == '/') || (*ptr == '\\')) {
|
|
value.file = ptr + 1;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
inline constexpr SourceLocation make_source_location(
|
|
int line = __builtin_LINE(),
|
|
const char* file = __builtin_FILE(),
|
|
const char* func = __builtin_FUNCTION())
|
|
{
|
|
return SourceLocation{
|
|
.line = line,
|
|
.file = file,
|
|
.func = func
|
|
};
|
|
}
|
|
|
|
// disallow re-locking, tracking external `bool`
|
|
struct ReentryLock {
|
|
ReentryLock() = delete;
|
|
|
|
ReentryLock(const ReentryLock&) = delete;
|
|
ReentryLock& operator=(const ReentryLock&) = delete;
|
|
|
|
ReentryLock(ReentryLock&&) = default;
|
|
ReentryLock& operator=(ReentryLock&&) = delete;
|
|
|
|
explicit ReentryLock(bool& handle) :
|
|
_initialized(!handle),
|
|
_handle(handle)
|
|
{
|
|
lock();
|
|
}
|
|
|
|
~ReentryLock() {
|
|
unlock();
|
|
}
|
|
|
|
explicit operator bool() const {
|
|
return initialized();
|
|
}
|
|
|
|
bool initialized() const {
|
|
return _initialized;
|
|
}
|
|
|
|
void lock() {
|
|
if (initialized()) {
|
|
_handle = true;
|
|
}
|
|
}
|
|
|
|
void unlock() {
|
|
if (initialized()) {
|
|
_handle = false;
|
|
}
|
|
}
|
|
private:
|
|
bool _initialized;
|
|
bool& _handle;
|
|
};
|
|
|
|
// common comparison would use >=0x40000000
|
|
// instead, slightly reduce the footprint by
|
|
// checking *only* for numbers below it
|
|
inline bool pointerInFlash(const void* ptr) {
|
|
static constexpr uintptr_t Mask { 1 << 30 };
|
|
return (reinterpret_cast<uintptr_t>(ptr) & Mask) > 0;
|
|
}
|
|
|
|
struct StringView {
|
|
constexpr StringView() noexcept :
|
|
_ptr(nullptr),
|
|
_len(0)
|
|
{}
|
|
|
|
~StringView() = default;
|
|
|
|
StringView(std::nullptr_t) = delete;
|
|
|
|
constexpr StringView(const StringView&) noexcept = default;
|
|
constexpr StringView(StringView&&) noexcept = default;
|
|
|
|
#if __cplusplus > 201103L
|
|
constexpr StringView& operator=(const StringView&) noexcept = default;
|
|
constexpr StringView& operator=(StringView&&) noexcept = default;
|
|
#else
|
|
StringView& operator=(const StringView&) noexcept = default;
|
|
StringView& operator=(StringView&&) noexcept = default;
|
|
#endif
|
|
|
|
constexpr StringView(const char* ptr, size_t len) noexcept :
|
|
_ptr(ptr),
|
|
_len(len)
|
|
{}
|
|
|
|
template <typename T, typename = typename std::enable_if<std::is_pointer<T>::value>::type>
|
|
constexpr StringView(T ptr) noexcept :
|
|
StringView(ptr, __builtin_strlen(ptr))
|
|
{}
|
|
|
|
template <size_t Size>
|
|
constexpr StringView(const char (&string)[Size]) noexcept :
|
|
StringView(&string[0], Size - 1)
|
|
{}
|
|
|
|
constexpr StringView(const char* begin, const char* end) noexcept :
|
|
StringView(begin, end - begin)
|
|
{}
|
|
|
|
explicit StringView(const __FlashStringHelper* ptr) noexcept :
|
|
_ptr(reinterpret_cast<const char*>(ptr)),
|
|
_len(strlen_P(_ptr))
|
|
{}
|
|
|
|
StringView(const String& string) noexcept :
|
|
StringView(string.c_str(), string.length())
|
|
{}
|
|
|
|
StringView& operator=(const String& string) noexcept {
|
|
_ptr = string.c_str();
|
|
_len = string.length();
|
|
return *this;
|
|
}
|
|
|
|
template <size_t Size>
|
|
constexpr StringView& operator=(const char (&string)[Size]) noexcept {
|
|
_ptr = &string[0];
|
|
_len = Size - 1;
|
|
return *this;
|
|
}
|
|
|
|
constexpr const char* begin() const noexcept {
|
|
return _ptr;
|
|
}
|
|
|
|
constexpr const char* end() const noexcept {
|
|
return _ptr + _len;
|
|
}
|
|
|
|
constexpr const char* c_str() const {
|
|
return _ptr;
|
|
}
|
|
|
|
constexpr const char* data() const {
|
|
return _ptr;
|
|
}
|
|
|
|
constexpr const char& operator[](size_t offset) const {
|
|
return *(_ptr + offset);
|
|
}
|
|
|
|
constexpr size_t length() const {
|
|
return _len;
|
|
}
|
|
|
|
String toString() const {
|
|
String out;
|
|
out.concat(_ptr, _len);
|
|
return out;
|
|
}
|
|
|
|
explicit operator String() const {
|
|
return toString();
|
|
}
|
|
|
|
bool equals(StringView) const;
|
|
bool equalsIgnoreCase(StringView) const;
|
|
|
|
bool startsWith(StringView) const;
|
|
bool endsWith(StringView) const;
|
|
|
|
private:
|
|
#if defined(HOST_MOCK)
|
|
constexpr static bool inFlash(const char*) {
|
|
return false;
|
|
}
|
|
#else
|
|
static bool inFlash(const char* ptr) {
|
|
return pointerInFlash(ptr);
|
|
}
|
|
#endif
|
|
|
|
const char* _ptr;
|
|
size_t _len;
|
|
};
|
|
|
|
inline bool operator==(StringView lhs, StringView rhs) {
|
|
return lhs.equals(rhs);
|
|
}
|
|
|
|
inline bool operator!=(StringView lhs, StringView rhs) {
|
|
return !lhs.equals(rhs);
|
|
}
|
|
|
|
inline String operator+(String&& lhs, StringView rhs) {
|
|
lhs.concat(rhs.c_str(), rhs.length());
|
|
return lhs;
|
|
}
|
|
|
|
inline String operator+=(String& lhs, StringView rhs) {
|
|
lhs.concat(rhs.c_str(), rhs.length());
|
|
return lhs;
|
|
}
|
|
|
|
inline String operator+(StringView lhs, const String& rhs) {
|
|
String out;
|
|
out += lhs.toString();
|
|
out += rhs;
|
|
return out;
|
|
}
|
|
|
|
#ifndef PROGMEM_STRING_ATTR
|
|
#define PROGMEM_STRING_ATTR __attribute__((section( "\".irom0.pstr." __FILE__ "." __STRINGIZE(__LINE__) "." __STRINGIZE(__COUNTER__) "\", \"aSM\", @progbits, 1 #")))
|
|
#endif
|
|
|
|
#ifndef PROGMEM_STRING
|
|
#define PROGMEM_STRING(NAME, X)\
|
|
alignas(4) static constexpr char NAME[] PROGMEM_STRING_ATTR = (X)
|
|
#endif
|
|
|
|
#ifndef STRING_VIEW
|
|
#define STRING_VIEW(X) ({\
|
|
alignas(4) static constexpr char __pstr__[] PROGMEM_STRING_ATTR = (X);\
|
|
::espurna::StringView{__pstr__};\
|
|
})
|
|
#endif
|
|
|
|
#ifndef STRING_VIEW_INLINE
|
|
#define STRING_VIEW_INLINE(NAME, X)\
|
|
alignas(4) static constexpr char __pstr__ ## NAME ## __ [] PROGMEM_STRING_ATTR = (X);\
|
|
constexpr auto NAME = ::espurna::StringView(__pstr__ ## NAME ## __)
|
|
#endif
|
|
|
|
#define STRING_VIEW_SETTING(X)\
|
|
((__builtin_strlen(X) > 0) ? STRING_VIEW(X) : StringView())
|
|
|
|
// ref. https://en.cppreference.com/w/cpp/types/type_identity
|
|
|
|
template <typename T>
|
|
struct TypeIdentity {
|
|
using type = T;
|
|
};
|
|
|
|
// ref.
|
|
// - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0122r7.html
|
|
// - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1976r2.html
|
|
// - https://github.com/microsoft/STL/issues/4
|
|
// - https://github.com/microsoft/GSL/blob/main/include/gsl/span
|
|
|
|
template <typename T>
|
|
struct SpanIterator {
|
|
#if __cplusplus > 201103L
|
|
#define SPAN_ITERATOR_CONSTEXPR constexpr
|
|
#else
|
|
#define SPAN_ITERATOR_CONSTEXPR
|
|
#endif
|
|
using iterator_category = std::random_access_iterator_tag;
|
|
|
|
using difference_type = std::ptrdiff_t;
|
|
using pointer = T*;
|
|
using reference = T&;
|
|
using value_type = typename std::remove_cv<T>::type;
|
|
|
|
SpanIterator() = delete;
|
|
constexpr SpanIterator(pointer begin, pointer end, pointer current) :
|
|
_begin(begin),
|
|
_end(end),
|
|
_current(current)
|
|
{}
|
|
|
|
constexpr reference operator*() const noexcept {
|
|
return *_current;
|
|
}
|
|
|
|
constexpr pointer operator->() const noexcept {
|
|
return _current;
|
|
}
|
|
|
|
constexpr SpanIterator& operator++() noexcept {
|
|
++_current;
|
|
return *this;
|
|
}
|
|
|
|
constexpr SpanIterator operator++(int) noexcept {
|
|
auto& self = *this;
|
|
SpanIterator tmp{self};
|
|
++self;
|
|
return self;
|
|
}
|
|
|
|
constexpr SpanIterator& operator--() noexcept {
|
|
--_current;
|
|
return *this;
|
|
}
|
|
|
|
constexpr SpanIterator operator--(int) noexcept {
|
|
auto& self = *this;
|
|
SpanIterator tmp{self};
|
|
--self;
|
|
return self;
|
|
}
|
|
|
|
constexpr SpanIterator& operator+=(const difference_type offset) noexcept {
|
|
_current += offset;
|
|
return *this;
|
|
}
|
|
|
|
constexpr SpanIterator operator+(const difference_type offset) noexcept {
|
|
SpanIterator out{*this};
|
|
out += offset;
|
|
return out;
|
|
}
|
|
|
|
constexpr SpanIterator& operator-=(const difference_type offset) noexcept {
|
|
_current -= offset;
|
|
return *this;
|
|
}
|
|
|
|
constexpr SpanIterator operator-(const difference_type offset) noexcept {
|
|
SpanIterator out{*this};
|
|
out -= offset;
|
|
return out;
|
|
}
|
|
|
|
constexpr difference_type operator-(const SpanIterator& other) const noexcept {
|
|
return _current - other._current;
|
|
}
|
|
|
|
constexpr reference operator[](const difference_type offset) const noexcept {
|
|
return *(*this + offset);
|
|
}
|
|
|
|
constexpr bool operator==(const SpanIterator& other) const noexcept {
|
|
return _current == other._current;
|
|
}
|
|
|
|
constexpr bool operator!=(const SpanIterator& other) const noexcept {
|
|
return _current != other._current;
|
|
}
|
|
|
|
constexpr bool operator<(const SpanIterator& other) const noexcept {
|
|
return _current < other._current;
|
|
}
|
|
|
|
constexpr bool operator>(const SpanIterator& other) const noexcept {
|
|
return _current > other._current;
|
|
}
|
|
|
|
constexpr bool operator<=(const SpanIterator& other) const noexcept {
|
|
return _current <= other._current;
|
|
}
|
|
|
|
constexpr bool operator>=(const SpanIterator& other) const noexcept {
|
|
return _current <= other._current;
|
|
}
|
|
|
|
private:
|
|
pointer _begin;
|
|
pointer _end;
|
|
pointer _current;
|
|
|
|
#undef SPAN_ITERATOR_CONSTEXPR
|
|
};
|
|
|
|
// storage helper. either store size in type info, or as a member
|
|
// using the same magic trick as most implementations
|
|
// - limits<size_t>::max() holds member inside of the struct
|
|
// - everything else is encoded in type
|
|
|
|
auto constexpr SpanDynamicExtent = std::numeric_limits<size_t>::max();
|
|
|
|
template <size_t Size>
|
|
struct __SpanBase {
|
|
constexpr __SpanBase() noexcept = default;
|
|
constexpr explicit __SpanBase(size_t) noexcept {
|
|
}
|
|
|
|
constexpr size_t size() const noexcept {
|
|
return Size;
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct __SpanBase<SpanDynamicExtent> {
|
|
constexpr __SpanBase() noexcept = default;
|
|
constexpr explicit __SpanBase(size_t size) noexcept :
|
|
_size(size)
|
|
{}
|
|
|
|
constexpr size_t size() const noexcept {
|
|
return _size;
|
|
}
|
|
private:
|
|
size_t _size{};
|
|
};
|
|
|
|
template <>
|
|
struct __SpanBase<0> {
|
|
constexpr __SpanBase() = delete;
|
|
constexpr explicit __SpanBase(size_t) noexcept = delete;
|
|
};
|
|
|
|
template <typename T, size_t Extent = SpanDynamicExtent>
|
|
struct Span : public __SpanBase<Extent> {
|
|
using element_type = T;
|
|
using value_type = typename std::remove_cv<T>::type;
|
|
using size_type = size_t;
|
|
using difference_type = std::ptrdiff_t;
|
|
using pointer = T*;
|
|
using const_pointer = const T*;
|
|
using reference = T&;
|
|
using const_reference = const T&;
|
|
using iterator = SpanIterator<T>;
|
|
|
|
static constexpr size_t extent = Extent;
|
|
|
|
constexpr Span() = default;
|
|
constexpr Span(const Span&) = default;
|
|
constexpr Span& operator=(const Span&) = default;
|
|
|
|
constexpr Span(Span&&) = default;
|
|
constexpr Span& operator=(Span&&) = default;
|
|
|
|
constexpr explicit Span(pointer data) noexcept :
|
|
__SpanBase<Extent>{},
|
|
_data(data)
|
|
{}
|
|
|
|
constexpr Span(pointer data, size_t size) noexcept :
|
|
__SpanBase<Extent>{size},
|
|
_data(data)
|
|
{}
|
|
|
|
constexpr Span(pointer first, pointer last) noexcept :
|
|
__SpanBase<Extent>{last - first},
|
|
_data(first)
|
|
{}
|
|
|
|
template <size_t Size>
|
|
constexpr Span(typename TypeIdentity<T>::type (&data)[Size]) noexcept :
|
|
Span(&data[0], Size)
|
|
{}
|
|
|
|
template <size_t Size>
|
|
constexpr Span(typename std::array<value_type, Size>& data) noexcept :
|
|
__SpanBase<Size>{},
|
|
_data(data.data())
|
|
{}
|
|
|
|
template <size_t Size>
|
|
constexpr Span(const typename std::array<value_type, Size>& data) noexcept :
|
|
__SpanBase<Size>{},
|
|
_data(data.data())
|
|
{}
|
|
|
|
constexpr reference operator[](size_t index) const {
|
|
return _data[index];
|
|
}
|
|
|
|
constexpr pointer data() const noexcept {
|
|
return _data;
|
|
}
|
|
|
|
constexpr iterator begin() const noexcept {
|
|
return {_data, _data + size(), &_data[0]};
|
|
}
|
|
|
|
constexpr iterator end() const noexcept {
|
|
return {_data, _data + size(), &_data[size()]};
|
|
}
|
|
|
|
constexpr size_type size() const {
|
|
return __SpanBase<Extent>::size();
|
|
}
|
|
|
|
constexpr Span<T, SpanDynamicExtent> subspan(size_type offset) const {
|
|
return {data() + offset, size() - offset};
|
|
}
|
|
|
|
constexpr reference front() const noexcept {
|
|
return _data[0];
|
|
}
|
|
|
|
constexpr reference back() const noexcept {
|
|
return _data[size() - 1];
|
|
}
|
|
|
|
private:
|
|
T* _data;
|
|
};
|
|
|
|
template <typename T, size_t Size>
|
|
inline constexpr Span<T, Size> make_span(typename TypeIdentity<T>::type (&data)[Size]) {
|
|
return Span<T, Size>(data);
|
|
}
|
|
|
|
template <typename T, size_t Size>
|
|
inline constexpr Span<T, Size> make_span(typename std::array<T, Size>& data) {
|
|
return Span<T, Size>(data);
|
|
}
|
|
|
|
template <typename T, size_t Size>
|
|
inline constexpr Span<T, Size> make_span(const typename std::array<T, Size>& data) {
|
|
return Span<T, Size>(data);
|
|
}
|
|
|
|
template <typename T>
|
|
inline constexpr Span<T> make_span(std::vector<T>& data) {
|
|
return Span<T>(data.data(), data.size());
|
|
}
|
|
|
|
template <typename T>
|
|
inline constexpr Span<T> make_span(const std::vector<T>& data) {
|
|
return Span<T>(data.data(), data.size());
|
|
}
|
|
|
|
} // namespace espurna
|