Redesign (simplify) top-level avclan interface to use expected/nullable unique_ptr

- Peripheral::read/send now return `expected<unique_ptr<Frame>, Error>`
for a unified success/error interface.
- Peripheral::route returns `expected<unique_ptr<Frame>, Error>` to
distinguish intent: (intentional) non-response vs unable to respond
- Other functions with optional message semantics (handle,poll,react)
return a unique_ptr whose ownership-state indicates response intent
(i.e. send new message)

Bundled necessary changes:
- Switch Frame allocation from global to local (enabled via member
  function new/delete)
- Add new header-library tl::expected to shim avr-libstdcpp

Unrelated: Defensive `continue` added after `route`, to reduce Bus
activity check latency

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Allen Hill
2026-07-17 14:24:03 -07:00
parent 200a225add
commit 5208e083f3
17 changed files with 536 additions and 187 deletions
+47 -24
View File
@@ -8,33 +8,44 @@
#include <cstdint>
#include <limits>
#include <memory>
#include <type_traits>
namespace detail {
template <class T, auto N> struct Deleter;
}
template <class T, std::integral auto N, bool Owning = false>
requires((N & (N - 1)) == 0 && N <= std::numeric_limits<uint8_t>::max())
template <class T, std::integral auto N, bool Owning = false,
class Deleter = std::default_delete<T>>
requires((N & (N - 1)) == 0 && N <= std::numeric_limits<uint8_t>::max() &&
// push() drops the passed-in deleter and pop() fabricates a fresh
// one, so a deleter must either derive its state from the queue
// itself (the pool Deleter) or carry no state at all
(std::is_same_v<Deleter, detail::Deleter<T, N>> ||
(std::is_empty_v<Deleter> &&
std::is_nothrow_default_constructible_v<Deleter>)))
class Queue {
using Deleter = detail::template Deleter<T, N>;
friend Deleter;
friend detail::Deleter<T, N>;
public:
// Only full and copy-convert-from-full construction is allowed
Queue() = delete;
// Only empty construction is allowed for non-Owning, non-pool deleters
constexpr Queue()
requires(!Owning && !std::is_same_v<Deleter, detail::Deleter<T, N>>)
: owner{nullptr} {}
Queue(const Queue &) = delete;
// Moving is unsupported due to being self-referential
Queue(Queue &&) = delete;
Queue &operator=(Queue &&) = delete;
// Construct from pre-defined storage
constexpr Queue(T (&items)[N])
requires(Owning)
requires(Owning) && std::same_as<Deleter, detail::Deleter<T, N>>
: owner{this}, write{N} {
for (uint8_t i = 0; i < N; ++i)
buf[i] = &items[i];
}
constexpr Queue(Queue<T, N, true> &queue)
// Construct non-Owning empty from Owning queue
constexpr Queue(Queue<T, N, true, Deleter> &queue)
requires(!Owning)
: owner{&queue} {}
@@ -46,14 +57,22 @@ public:
uint8_t push(std::unique_ptr<T, Deleter> x)
requires(!Owning)
{
// structurally unnecessary; empty construction from same size parent
// guarantees
// !isFull assert(!isFull());
if constexpr (!std::is_same_v<Deleter, detail::Deleter<T, N>>) {
// structurally unnecessary for detail::Deleter, where empty construction
// is only from same size parent
if (isFull())
return 1;
}
if (!x || !x.get_deleter().is_owned_by(owner))
if (!x)
return 1;
reclaim(x.release());
if constexpr (std::is_same_v<Deleter, detail::Deleter<T, N>>) {
if (!x.get_deleter().is_owned_by(owner))
return 1;
}
claim(x.release());
return 0;
}
@@ -69,35 +88,39 @@ public:
if (isEmpty())
return nullptr;
if constexpr (Owning)
return {buf[mask(read++)], {this}};
if constexpr (!std::is_same_v<Deleter, detail::Deleter<T, N>>)
return {buf[mask(read++)], Deleter{}};
else if constexpr (Owning)
return {buf[mask(read++)], Deleter{this}};
else
return {buf[mask(read++)], {owner}};
return {buf[mask(read++)], Deleter{owner}};
}
private:
uint8_t mask(uint8_t pos) const { return pos & (N - 1); }
void reclaim(T *x) { buf[mask(write++)] = x; }
void claim(T *x) { buf[mask(write++)] = x; }
std::array<T *, N> buf = {};
Queue<T, N, true> *const owner;
Queue<T, N, true, Deleter> *const owner;
uint8_t read = 0;
uint8_t write = 0;
};
template <class T, auto N> Queue(Queue<T, N, true> &) -> Queue<T, N, false>;
template <class T, auto N> Queue(T (&items)[N]) -> Queue<T, N, true>;
template <class T, auto N, class D>
Queue(Queue<T, N, true, D> &) -> Queue<T, N, false, D>;
template <class T, auto N>
Queue(T (&items)[N]) -> Queue<T, N, true, detail::Deleter<T, N>>;
namespace detail {
template <class T, auto N> struct Deleter {
Deleter() = default;
constexpr Deleter(Queue<T, N, true> *owner) : owner{owner} {}
void operator()(T *x) const { owner->reclaim(x); }
constexpr bool is_owned_by(const Queue<T, N, true> *parent) const {
constexpr Deleter(Queue<T, N, true, Deleter> *owner) : owner{owner} {}
void operator()(T *x) const { owner->claim(x); }
constexpr bool is_owned_by(const Queue<T, N, true, Deleter> *parent) const {
return owner == parent;
}
private:
Queue<T, N, true> *const owner = nullptr;
Queue<T, N, true, Deleter> *const owner = nullptr;
};
} // namespace detail