Files
espurna/code/espurna/led_pattern.ipp
Maxim Prokhorov 10fcecdbf6 led(build): -Wunused-function false positive
techically true, but impl has to be there
note that class itself is namespace { ... } - scoped too
2025-11-28 22:57:43 +03:00

684 lines
16 KiB
C++

/*
Part of the LED MODULE
*/
#pragma once
#include "led_internal.h"
#include "settings_convert.h"
namespace espurna {
namespace led {
namespace {
// Multi-purpose container for either empty, single or multiple delays
// Mainly useful to get rid of vector alloc when using predefined patterns
struct Delays {
using ConstPointer = Delay*;
using Pointer = Delay*;
using Container = std::vector<Delay>;
struct Storage {
struct Empty {
};
struct Single {
explicit Single(Delay delay) noexcept :
delay(delay)
{}
Single() = default;
~Single() = default;
Delay delay;
};
struct Multiple {
explicit Multiple(const Container& container) :
container(container)
{}
explicit Multiple(Container&& container) noexcept :
container(std::move(container))
{}
Multiple(const Multiple&) = default;
Multiple& operator=(const Multiple&) = default;
Multiple(Multiple&&) noexcept = default;
Multiple& operator=(Multiple&&) = default;
Multiple() = default;
~Multiple() = default;
Container container;
};
enum class Type {
Empty,
Single,
Multiple,
};
explicit Storage(const Single& single) :
_impl(single),
_type(Type::Single)
{}
explicit Storage(Single&& single) :
_impl(std::move(single)),
_type(Type::Single)
{}
explicit Storage(const Multiple& multiple) :
_impl(multiple),
_type(Type::Multiple)
{}
explicit Storage(Multiple&& multiple) :
_impl(std::move(multiple)),
_type(Type::Multiple)
{}
Storage() = default;
~Storage();
Storage(const Storage&);
[[gnu::unused]]
Storage& operator=(const Storage&);
Storage(Storage&&) noexcept;
[[gnu::unused]]
Storage& operator=(Storage&&) noexcept;
bool operator==(const Storage&) const noexcept;
bool operator!=(const Storage& other) const noexcept {
return !(*this == other);
}
Type type() const {
return _type;
}
template <typename T>
void visit(T&& visitor) {
switch (_type) {
case Type::Empty:
visitor();
break;
case Type::Single:
visitor(_impl.single);
break;
case Type::Multiple:
visitor(_impl.multiple);
break;
}
}
template <typename T>
void visit(T&& visitor) const {
return const_cast<Storage*>(this)->visit(std::forward<T>(visitor));
}
template <typename R, typename T>
R visit(T&& visitor, R) {
switch (_type) {
case Type::Empty:
return visitor();
case Type::Single:
return visitor(_impl.single);
case Type::Multiple:
return visitor(_impl.multiple);
}
return R{};
}
template <typename R, typename T>
R visit(T&& visitor, R _) const {
return const_cast<Storage*>(this)->visit(std::forward<T>(visitor), _);
}
struct Destructor {
void operator()() const noexcept;
void operator()(Storage::Single&) const noexcept;
void operator()(Storage::Multiple&) const noexcept;
};
struct Copy {
explicit Copy(Storage&);
void operator()() const noexcept;
void operator()(Storage::Single&) const noexcept;
void operator()(Storage::Multiple&) const noexcept;
private:
Storage& _storage;
};
struct Move {
explicit Move(Storage&);
void operator()() const noexcept;
void operator()(Storage::Single&) const noexcept;
void operator()(Storage::Multiple&) const noexcept;
private:
Storage& _storage;
};
struct Equal {
explicit Equal(const Storage&);
bool operator()() const noexcept;
bool operator()(Storage::Single&) const noexcept;
bool operator()(Storage::Multiple&) const noexcept;
private:
const Storage& _other;
};
union Impl {
Impl();
~Impl();
explicit Impl(Single single) noexcept :
single(single)
{}
explicit Impl(const Multiple& multiple) :
multiple(multiple)
{}
explicit Impl(Multiple&& multiple) noexcept :
multiple(std::move(multiple))
{}
char empty;
Single single;
Multiple multiple;
};
private:
Impl _impl;
Type _type{};
};
Delays() = default;
~Delays() = default;
Delays(const Delays&) = default;
Delays& operator=(const Delays&) = default;
Delays(Delays&&) = default;
Delays& operator=(Delays&&) = default;
Delays(Delay delay) :
_storage(Storage::Single(delay))
{}
Delays(Container&& multiple) noexcept :
_storage(Storage::Multiple(std::move(multiple)))
{}
size_t size() const noexcept;
void add(Delay);
ConstPointer begin() const noexcept;
ConstPointer end() const noexcept;
Pointer begin() noexcept;
Pointer end() noexcept;
bool operator==(const Delays&) const noexcept;
bool operator!=(const Delays& other) const noexcept {
return !(*this == other);
}
private:
Storage _storage;
};
void Delays::Storage::Destructor::operator()() const noexcept {
}
void Delays::Storage::Destructor::operator()(Storage::Single&) const noexcept {
}
void Delays::Storage::Destructor::operator()(Storage::Multiple& multiple) const noexcept {
multiple.container.~Container();
}
Delays::Storage::~Storage() {
visit(Destructor());
}
Delays::Storage::Impl::Impl() :
empty{}
{}
Delays::Storage::Impl::~Impl() {
}
Delays::Storage::Copy::Copy(Storage& storage) :
_storage(storage)
{}
void Delays::Storage::Copy::operator()() const noexcept {
}
void Delays::Storage::Copy::operator()(Single& single) const noexcept {
_storage._impl.single = single;
}
void Delays::Storage::Copy::operator()(Multiple& multiple) const noexcept {
::new (&_storage._impl.multiple) Storage::Multiple(std::cref(multiple));
}
Delays::Storage::Storage(const Storage& other) :
_type(other._type)
{
other.visit(Copy(*this));
}
Delays::Storage& Delays::Storage::operator=(const Storage& other) {
visit(Destructor());
_type = other._type;
other.visit(Copy(*this));
return *this;
}
Delays::Storage::Move::Move(Storage& storage) :
_storage(storage)
{}
void Delays::Storage::Move::operator()() const noexcept {
}
void Delays::Storage::Move::operator()(Single& single) const noexcept {
_storage._impl.single = single;
}
void Delays::Storage::Move::operator()(Multiple& multiple) const noexcept {
::new (&_storage._impl.multiple) Storage::Multiple(std::move(multiple));
}
Delays::Storage::Storage(Storage&& other) noexcept :
_type(other._type)
{
other.visit(Move(*this));
other.visit(Destructor());
other._type = Type::Empty;
}
Delays::Storage::Equal::Equal(const Storage& other) :
_other(other)
{}
bool Delays::Storage::Equal::operator()() const noexcept {
return _other._type == Type::Empty;
}
bool Delays::Storage::Equal::operator()(Single& single) const noexcept {
return _other._type == Type::Single
&& single.delay == _other._impl.single.delay;
}
bool Delays::Storage::Equal::operator()(Multiple& multiple) const noexcept {
return _other._type == Type::Multiple
&& multiple.container == _other._impl.multiple.container;
}
bool Delays::Storage::operator==(const Storage& other) const noexcept {
return visit(Equal{other}, false);
}
bool Delays::operator==(const Delays& other) const noexcept {
return _storage == other._storage;
}
Delays::Storage& Delays::Storage::operator=(Storage&& other) noexcept {
visit(Destructor());
_type = other._type;
other.visit(Move(*this));
other.visit(Destructor());
other._type = Type::Empty;
return *this;
}
struct Size {
size_t operator()() const noexcept {
return 0;
}
size_t operator()(Delays::Storage::Single&) const noexcept {
return 1;
}
size_t operator()(Delays::Storage::Multiple& multiple) const noexcept {
return multiple.container.size();
}
};
size_t Delays::size() const noexcept {
return _storage.visit(Size{}, 1);
}
struct Add {
Add(Delays::Storage& storage, const Delay& delay):
_storage(storage),
_delay(delay)
{}
void operator()() const noexcept {
_storage = Delays::Storage(Delays::Storage::Single(_delay));
}
void operator()(Delays::Storage::Single& single) const noexcept {
Delays::Storage::Multiple multiple;
multiple.container.reserve(2);
multiple.container.push_back(single.delay);
multiple.container.push_back(_delay);
_storage = Delays::Storage(std::move(multiple));
}
void operator()(Delays::Storage::Multiple& multiple) const noexcept {
multiple.container.push_back(_delay);
}
private:
Delays::Storage& _storage;
const Delay& _delay;
};
void Delays::add(Delay delay) {
_storage.visit(Add{_storage, delay});
}
struct BeginPointer {
Delays::Pointer operator()() const noexcept {
return nullptr;
}
Delays::Pointer operator()(Delays::Storage::Single& single) const noexcept {
return std::addressof(single.delay);
}
Delays::Pointer operator()(Delays::Storage::Multiple& multiple) const noexcept {
return std::addressof(*multiple.container.begin());
}
};
Delays::Pointer Delays::begin() noexcept {
return _storage.visit(BeginPointer{}, Delays::Pointer{});
}
Delays::ConstPointer Delays::begin() const noexcept {
return const_cast<Delays*>(this)->begin();
}
struct EndPointer {
Delays::Pointer operator()() const noexcept {
return nullptr;
}
Delays::Pointer operator()(Delays::Storage::Single& single) const noexcept {
return std::addressof(single.delay) + 1;
}
Delays::Pointer operator()(Delays::Storage::Multiple& multiple) const noexcept {
return std::addressof(*multiple.container.end());
}
};
Delays::Pointer Delays::end() noexcept {
return _storage.visit(EndPointer{}, Pointer{});
}
Delays::ConstPointer Delays::end() const noexcept {
return const_cast<Delays*>(this)->end();
}
// Sequence of pending 'delays', by default it's in the order they are specified in the underlying vector.
// Notice that there are no checks that '_current' is dereferencable, it's up to the consumer to check via 'operator bool()' first
// Also notice that consumer must correctly implement copy & move, since '_current' and '_end' pointers do not own the target 'delays'
struct Sequence {
Sequence() = default;
Sequence(const Sequence&) = default;
Sequence(Sequence&&) = default;
explicit Sequence(const Delays& delays) {
start_with(delays);
}
Sequence& operator=(const Sequence&) = default;
Sequence& operator=(Sequence&&) = default;
template <typename Status, typename Cycle>
bool run(Status&& status, Cycle&& cycle) {
if (!ok()) {
return false;
}
const auto current_status = status();
const auto current_duration =
current_status
? on() : off();
if (!cycle(current_status, current_duration)) {
return false;
}
if (repeat()) {
return true;
}
if (next()) {
// '0,0' at the end restarts from the beginning
if (on() == Duration::zero()
&& off() == Duration::zero())
{
start();
}
} else {
// ...which is completely stopped otherwise
stop();
return false;
}
return true;
}
void start() {
_current = _begin;
if (_current != _end) {
_repeats = (*_current).repeats;
}
}
void stop() {
_current = _end;
_repeats = 0;
}
void start_with(const Delays& delays) {
_current = delays.begin();
_begin = delays.begin();
_end = delays.end();
_repeats = (_current != _end)
? (*_current).repeats
: 0;
}
void stop_with(const Delays& delays) {
_current = delays.end();
_begin = delays.begin();
_end = delays.end();
_repeats = 0;
}
bool ok() const {
return _current != _end;
}
explicit operator bool() const {
return ok();
}
size_t repeats() const {
return _repeats;
}
Duration on() const {
return (*_current).on;
}
Duration off() const {
return (*_current).off;
}
bool repeat() {
bool out;
if (_repeats) {
--_repeats;
out = _repeats != 0;
} else {
out = _repeats == 0;
}
return out;
}
bool next() {
if (_current != _end) {
++_current;
if (_current != _end) {
_repeats = (*_current).repeats;
return true;
}
}
return false;
}
private:
const Delay* _current{};
const Delay* _begin{};
const Delay* _end{};
size_t _repeats{};
};
struct Pattern {
Pattern() = default;
Pattern(const Pattern&) = default;
Pattern& operator=(const Pattern&) = default;
Pattern(Pattern&&) noexcept = default;
Pattern& operator=(Pattern&&) noexcept = default;
bool operator==(const Pattern& other) const noexcept {
return _delays == other._delays;
}
bool operator!=(const Pattern& other) const noexcept {
return !(*this == other);
}
explicit Pattern(Delays&& delays) :
_delays(std::move(delays))
{}
explicit Pattern(Delay delay) :
Pattern(Delays(delay))
{}
bool ok() const {
return _delays.size() > 0;
}
explicit operator bool() const {
return ok();
}
String toString() const {
String out;
bool first = true;
for (auto it = _delays.begin(); it != _delays.end(); ++it) {
if (!first) {
out += ' ';
}
first = false;
const auto repeat_delay = (*it).repeats > 0;
const auto repeat_pattern =
((*it).on == Duration::zero())
&& ((*it).off == Duration::zero());
if (!repeat_delay && repeat_pattern) {
out += 'R';
break;
}
out += as_millis((*it).on);
out += ',';
out += as_millis((*it).off);
if (!repeat_delay) {
break;
}
out += ',';
out += String((*it).repeats);
}
return out;
}
void add(Delay delay) {
_delays.add(delay);
}
void add(Duration on, Duration off, size_t repeats) {
_delays.add(
Delay{
.on = on,
.off = off,
.repeats = repeats,
});
}
size_t size() const {
return _delays.size();
}
Sequence make_sequence() const {
return Sequence(_delays);
}
const Delays& delays() const {
return _delays;
}
private:
static String as_millis(Duration value) {
const auto ms = std::chrono::duration_cast<duration::Milliseconds>(value);
return ::espurna::settings::internal::serialize(ms);
}
Delays _delays;
};
} // namespace
} // namespace led
} // namespace espurna