Comms CCF
This is a simple communication layer based on COBS, CBOR and FNV-1A
Loading...
Searching...
No Matches
circular_buffer.hpp
Go to the documentation of this file.
1
60
61#pragma once
62
63#include "types.hpp"
64
65#if defined(DEBUG_CIRC_BUF)
66#include DEBUG_CIRC_BUF
67#else
68#include "ndebug.hpp"
69#endif
70
71#include <stddef.h>
72#include <stdint.h>
73
74#include <array>
75#include <bit>
76#include <optional>
77#include <type_traits>
78#include <utility>
79
80template<typename Value, size_t Size, size_t MaxPacketSize>
82{
83public:
87 static_assert(std::popcount(Size) == 1, "Size needs to be a power of 2");
88
89 using value_type = Value;
90
91 static constexpr size_t sizeBytes = sizeof(SmallestTypeT<MaxPacketSize>);
92
93 constexpr size_t capacity() const { return Size; }
94 size_t size() const { return write - read; }
95 size_t readable() const { return notified - read; }
96 size_t unnotified() const { return std::max(sizeBytes, write - notified) - sizeBytes; }
97 bool empty() const { return size() == 0; }
98 bool full() const { return size() == capacity(); }
99 bool dropping() const { return dropped; }
100
104 template<typename Value_>
105 // Here to avoid an extra copy of the code with int for e.g. uint8_t
106 requires std::same_as<std::remove_cvref_t<Value>, std::remove_cvref_t<Value_>>
107 void push_back(Value_ && v)
108 {
109 if (unnotified() >= MaxPacketSize || size() >= capacity())
110 {
111 dropped = true;
112 }
113 if ((!dropped) && (notified == write))
114 {
115 write += sizeBytes;
116 }
117 if (unnotified() >= MaxPacketSize || size() >= capacity())
118 {
119 dropped = true;
120 }
121 if (!dropped)
122 {
123 buf[write++ % Size] = std::forward<Value_>(v);
124 }
125 }
126
129 {
130 if (read < notified)
131 {
132 ++read;
133 }
134 }
135
138 Value & front()
139 {
140 return buf[read % Size];
141 }
142
145 const Value & front() const
146 {
147 return buf[read % Size];
148 }
149
154 {
155 if (dropped)
156 {
157 dropped = false;
158 write = notified;
159 }
160 }
161
168 void notify()
169 {
170 // Since we are about to notify of a partial packet, we want to
171 // call `reset_dropped` to make sure it is treated as a full
172 // packet.
174 size_t size = unnotified();
175 for (size_t i = sizeBytes; i > 0; --i)
176 {
177 buf[notified++ % Size] = static_cast<uint8_t>(size);
178 size >>= 8;
179 }
180 notified = write;
181 }
182
183 class Frame;
184 class Iterator
185 {
186 public:
187 using value_type = Value;
188 using difference_type = ptrdiff_t;
189
190 Iterator(const CircularBuffer * parent_, size_t index_)
191 : parent(parent_), index(index_) {}
192 Iterator & operator++() { ++index; return *this; }
193 Iterator operator++(int) { const auto tmp = *this; ++*this; return tmp; }
194 const Value & operator*() const { return parent->buf[index % Size]; }
195 bool operator!=(const Iterator & other) const
196 {
197 return parent != other.parent || index != other.index;
198 }
199 bool operator==(const Iterator & other) const { return !(*this != other); }
200
201 private:
202 friend class Frame;
203 const CircularBuffer * parent;
204 size_t index;
205 };
206
207 class Frame
208 {
209 public:
210 Frame(CircularBuffer * parent_, size_t start, uint8_t len)
211 : parent(parent_),
212 begin_(parent, start),
213 end_(parent, start + len) {}
214 Frame(const Frame &) = delete;
215 Frame & operator=(const Frame &) = delete;
216 Frame(Frame && o) : begin_(o.begin_), end_(o.end_)
217 {
218 std::swap(parent, o.parent);
219 }
220 Frame & operator=(Frame && o)
221 {
222 parent = o.parent;
223 o.parent = nullptr;
224 begin_ = o.begin_;
225 end_ = o.end_;
226 return *this;
227 }
228
229 ~Frame()
230 {
231 if (parent)
232 {
233 parent->read = end_.index;
234 }
235 }
236
237 Iterator & begin() { return begin_; }
238 Iterator & end() { return end_; }
239
240 CircularBuffer * parent = nullptr;
241 Iterator begin_;
242 Iterator end_;
243 };
244
247 bool get_frame(std::optional<Frame> & frame)
248 {
249 // Ensure we drop the old one first, before reading from the queue
250 frame.reset();
251 if (dropped)
252 {
253 debugf(DEBUG "No packet as truncating" END LOGLEVEL_ARGS);
254 return false;
255 }
257 auto start = read;
258 const auto end = notified;
259 if (start == end)
260 {
261 // No next packet, compare with the next two `start != end`
262 return false;
263 }
264 size_t size = 0;
265 for (size_t i = sizeBytes; i > 0; --i)
266 {
267 if (start != end)
268 {
269 size <<= 8;
270 size |= buf[start++ % Size];
271 }
272 else
273 {
274 // A bad state, and we checked for truncating above
275 debugf(ERROR "Truncated next packet size" END);
276 }
277 }
278 if (start != end)
279 {
280 frame.emplace(this, start, size);
281 }
282 else
283 {
284 // Almost certainly an error, we checked for truncating above
285 debugf(ERROR "Zero length item" END LOGLEVEL_ARGS);
286 }
287 return frame.has_value();
288 }
289
291 void reset()
292 {
293 read = 0;
294 notified = 0;
295 write = 0;
296 dropped = false;
297 }
298
299private:
300 std::array<Value, Size> buf;
301 size_t read = 0;
302 size_t notified = 0;
303 size_t write = 0;
304 bool dropped = false;
305};
306
307// This is a header, undefine the debugf macro
308#include "debug_end.hpp"
Definition circular_buffer.hpp:185
Definition circular_buffer.hpp:82
void reset_dropped()
Definition circular_buffer.hpp:153
void notify()
Definition circular_buffer.hpp:168
bool get_frame(std::optional< Frame > &frame)
Definition circular_buffer.hpp:247
const Value & front() const
Definition circular_buffer.hpp:145
void push_back(Value_ &&v)
Definition circular_buffer.hpp:107
void reset()
Reset the queue to the initial, empty state.
Definition circular_buffer.hpp:291
void pop_front()
Drop the first element from the queue.
Definition circular_buffer.hpp:128
Value & front()
Definition circular_buffer.hpp:138
#define debugf(...)
Definition ndebug.hpp:23
#define END
Terminator of log message at the end.
Definition ndebug.hpp:50
#define LOGLEVEL_ARGS
Definition ndebug.hpp:28
#define DEBUG
Debug levels at the start of debugf.
Definition ndebug.hpp:33
Simple type utilities e.g. smallest type for given value.