move callback_list, parse_buffer, prepend_buffer, static_vector, timer_queue into util; util links pico_stdlib for timer_queue
This commit is contained in:
@@ -1,83 +0,0 @@
|
||||
#pragma once
|
||||
#include <utility>
|
||||
|
||||
// Fixed-capacity doubly-linked list with an embedded node array and intrusive
|
||||
// free list. Used for callback registries (frame callbacks, timers) needing
|
||||
// O(1) insert/remove with stable handles, without heap in IRQ-adjacent paths.
|
||||
template <typename T, int N>
|
||||
struct callback_list {
|
||||
struct node {
|
||||
T value;
|
||||
node* prev = nullptr;
|
||||
node* next = nullptr;
|
||||
};
|
||||
|
||||
node nodes[N];
|
||||
node* free_head = &nodes[0];
|
||||
node* head = nullptr;
|
||||
|
||||
callback_list() {
|
||||
for (int i = 0; i < N - 1; i++) nodes[i].next = &nodes[i + 1];
|
||||
nodes[N - 1].next = nullptr;
|
||||
}
|
||||
|
||||
bool empty() const { return head == nullptr; }
|
||||
|
||||
node* insert(T value) {
|
||||
if (!free_head) return nullptr;
|
||||
node* n = free_head;
|
||||
free_head = n->next;
|
||||
n->value = std::move(value);
|
||||
n->prev = nullptr;
|
||||
n->next = head;
|
||||
if (head) head->prev = n;
|
||||
head = n;
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename Less>
|
||||
node* insert_sorted(T value, Less&& less) {
|
||||
if (!free_head) return nullptr;
|
||||
node* n = free_head;
|
||||
free_head = n->next;
|
||||
n->value = std::move(value);
|
||||
if (!head || less(n->value, head->value)) {
|
||||
n->prev = nullptr;
|
||||
n->next = head;
|
||||
if (head) head->prev = n;
|
||||
head = n;
|
||||
return n;
|
||||
}
|
||||
node* cur = head;
|
||||
while (cur->next && !less(n->value, cur->next->value))
|
||||
cur = cur->next;
|
||||
n->prev = cur;
|
||||
n->next = cur->next;
|
||||
if (cur->next) cur->next->prev = n;
|
||||
cur->next = n;
|
||||
return n;
|
||||
}
|
||||
|
||||
void remove(node* n) {
|
||||
if (!n) return;
|
||||
if (n->prev) n->prev->next = n->next;
|
||||
else head = n->next;
|
||||
if (n->next) n->next->prev = n->prev;
|
||||
n->value = T{};
|
||||
n->next = free_head;
|
||||
n->prev = nullptr;
|
||||
free_head = n;
|
||||
}
|
||||
|
||||
node* front() { return head; }
|
||||
|
||||
template <typename Fn>
|
||||
void for_each(Fn&& fn) {
|
||||
node* cur = head;
|
||||
while (cur) {
|
||||
node* next = cur->next;
|
||||
fn(cur);
|
||||
cur = next;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
// Bounds-checked sequential reader for byte streams. consume<T>() returns a
|
||||
// typed pointer into the underlying buffer or nullptr on underflow. Used to
|
||||
// walk packet headers in order without UB and without copies.
|
||||
class parse_buffer {
|
||||
const uint8_t* m_data;
|
||||
size_t m_remaining;
|
||||
|
||||
public:
|
||||
parse_buffer(std::span<const uint8_t> data)
|
||||
: m_data(data.data()), m_remaining(data.size()) {}
|
||||
|
||||
template <typename T>
|
||||
const T* consume() {
|
||||
if (m_remaining < sizeof(T)) return nullptr;
|
||||
auto* p = reinterpret_cast<const T*>(m_data);
|
||||
m_data += sizeof(T);
|
||||
m_remaining -= sizeof(T);
|
||||
return p;
|
||||
}
|
||||
|
||||
bool skip(size_t len) {
|
||||
if (m_remaining < len) return false;
|
||||
m_data += len;
|
||||
m_remaining -= len;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::span<const uint8_t> remaining() const { return {m_data, m_remaining}; }
|
||||
size_t remaining_size() const { return m_remaining; }
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
|
||||
// Buffer that grows in both directions from a midpoint. Payload is appended at
|
||||
// the back; each protocol layer then prepends its header in reverse order
|
||||
// (UDP, then IPv4, then Ethernet) without moving payload bytes. Lets the
|
||||
// network stack build outbound frames bottom-up with no copies or scratch.
|
||||
template <size_t N>
|
||||
class prepend_buffer {
|
||||
uint8_t m_buf[N];
|
||||
size_t m_start = N / 2;
|
||||
size_t m_end = N / 2;
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
T* prepend() {
|
||||
m_start -= sizeof(T);
|
||||
return reinterpret_cast<T*>(m_buf + m_start);
|
||||
}
|
||||
|
||||
uint8_t* append(size_t len) {
|
||||
uint8_t* p = m_buf + m_end;
|
||||
m_end += len;
|
||||
return p;
|
||||
}
|
||||
|
||||
void append_copy(std::span<const uint8_t> data) {
|
||||
memcpy(append(data.size()), data.data(), data.size());
|
||||
}
|
||||
|
||||
uint8_t* payload_ptr() { return m_buf + m_end; }
|
||||
|
||||
uint8_t* data() { return m_buf + m_start; }
|
||||
const uint8_t* data() const { return m_buf + m_start; }
|
||||
size_t size() const { return m_end - m_start; }
|
||||
|
||||
std::span<const uint8_t> span() const { return {data(), size()}; }
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
// Fixed-capacity contiguous container: std::vector's interface with std::array's
|
||||
// storage. Used for response payloads sized at parse time but bounded at compile
|
||||
// time, where heap allocation is unavailable or undesirable.
|
||||
template <typename T, size_t Capacity>
|
||||
class static_vector {
|
||||
T m_data[Capacity];
|
||||
size_t m_size = 0;
|
||||
|
||||
public:
|
||||
void push_back(const T &v) {
|
||||
if (m_size < Capacity) m_data[m_size++] = v;
|
||||
}
|
||||
|
||||
void clear() { m_size = 0; }
|
||||
|
||||
size_t size() const { return m_size; }
|
||||
size_t capacity() const { return Capacity; }
|
||||
bool full() const { return m_size >= Capacity; }
|
||||
bool empty() const { return m_size == 0; }
|
||||
|
||||
T *data() { return m_data; }
|
||||
const T *data() const { return m_data; }
|
||||
|
||||
T &operator[](size_t i) { return m_data[i]; }
|
||||
const T &operator[](size_t i) const { return m_data[i]; }
|
||||
|
||||
T *begin() { return m_data; }
|
||||
T *end() { return m_data + m_size; }
|
||||
const T *begin() const { return m_data; }
|
||||
const T *end() const { return m_data + m_size; }
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
#include "pico/time.h"
|
||||
#include "callback_list.h"
|
||||
|
||||
struct timer_entry {
|
||||
absolute_time_t when;
|
||||
void (*fn)() = nullptr;
|
||||
};
|
||||
|
||||
using timer_handle = callback_list<timer_entry, 16>::node*;
|
||||
|
||||
// Deadline-sorted callback list with a single hardware alarm armed at the
|
||||
// head. The alarm IRQ sets a flag; run() drains expired entries on the main
|
||||
// loop, so callbacks execute in normal context rather than interrupt context.
|
||||
struct timer_queue {
|
||||
callback_list<timer_entry, 16> list;
|
||||
alarm_id_t alarm = -1;
|
||||
volatile bool irq_pending = false;
|
||||
|
||||
timer_handle schedule(absolute_time_t when, void (*fn)()) {
|
||||
auto* n = list.insert_sorted({when, fn},
|
||||
[](const timer_entry& a, const timer_entry& b) {
|
||||
return absolute_time_diff_us(b.when, a.when) < 0;
|
||||
});
|
||||
arm();
|
||||
return n;
|
||||
}
|
||||
|
||||
timer_handle schedule_ms(uint32_t ms, void (*fn)()) {
|
||||
return schedule(make_timeout_time_ms(ms), fn);
|
||||
}
|
||||
|
||||
bool cancel(timer_handle h) {
|
||||
if (!h) return false;
|
||||
list.remove(h);
|
||||
arm();
|
||||
return true;
|
||||
}
|
||||
|
||||
void run() {
|
||||
if (!irq_pending) return;
|
||||
irq_pending = false;
|
||||
while (auto* n = list.front()) {
|
||||
if (absolute_time_diff_us(get_absolute_time(), n->value.when) > 0) break;
|
||||
auto fn = n->value.fn;
|
||||
list.remove(n);
|
||||
fn();
|
||||
}
|
||||
arm();
|
||||
}
|
||||
|
||||
bool empty() const { return list.empty(); }
|
||||
|
||||
private:
|
||||
static int64_t alarm_cb(alarm_id_t, void* user_data) {
|
||||
static_cast<timer_queue*>(user_data)->irq_pending = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void arm() {
|
||||
if (alarm >= 0) cancel_alarm(alarm);
|
||||
alarm = -1;
|
||||
if (auto* n = list.front())
|
||||
alarm = add_alarm_at(n->value.when, alarm_cb, this, false);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user