#pragma once #include #include #include // Fixed-capacity FIFO with wrap-around. push_overwrite drops the oldest entry // when full -- used by the debug log so dlog never blocks or fails. Iteration // walks logical positions head..tail, hiding the modular indexing. template struct ring_buffer { std::array data = {}; uint16_t head = 0; uint16_t tail = 0; uint16_t used() const { return tail - head; } uint16_t free() const { return N - used(); } bool empty() const { return head == tail; } bool push(std::span src) { if (src.size() > free()) return false; for (auto& v : src) data[(tail++) % N] = v; return true; } bool push(const T& v) { if (free() == 0) return false; data[(tail++) % N] = v; return true; } void push_overwrite(const T& v) { if (free() == 0) head++; data[(tail++) % N] = v; } uint16_t peek(std::span dst) const { uint16_t len = dst.size() < used() ? dst.size() : used(); for (uint16_t i = 0; i < len; i++) dst[i] = data[(head + i) % N]; return len; } void consume(uint16_t len) { head += len; if (head >= N) { head -= N; tail -= N; } } std::span read_contiguous() const { uint16_t offset = head % N; uint16_t contig = N - offset; uint16_t pending = used(); uint16_t len = pending < contig ? pending : contig; return {data.data() + offset, len}; } struct iterator { const ring_buffer* rb; uint16_t index; const T& operator*() const { return rb->data[(rb->head + index) % N]; } iterator& operator++() { index++; return *this; } bool operator!=(const iterator& o) const { return index != o.index; } }; iterator begin() const { return {this, 0}; } iterator end() const { return {this, used()}; } };