MT Core (C++)
Core library for replacing C++ standard in project usage
Loading...
Searching...
No Matches
ring_buffer.hpp
Go to the documentation of this file.
1/*
2
3Copyright 2025 Matthew Tolman
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17*/
18
19#ifndef MTCORE_RING_BUFFER_HPP
20#define MTCORE_RING_BUFFER_HPP
21
22#include "iter.hpp"
23#include "optional.hpp"
24#include "result.hpp"
25
27namespace mtcore {
41 template<typename T>
42 struct RingBuffer {
43 private:
44 Slice<T> elements = {};
45 size_t head = 0;
46 size_t tail = 0;
47 size_t count = 0;
48
49 public:
50 using Elem = T;
51
56 decltype(auto) ptr_iter() noexcept {
57 ensure(elements.head, "NOT INITIALIZED");
58 return iter::ptr(*this);
59 }
60
64 decltype(auto) ptr_iter() const noexcept {
65 ensure(elements.head, "NOT INITIALIZED");
66 return iter::const_ptr(*this);
67 }
68
72 decltype(auto) iter() noexcept {
73 ensure(elements.head, "NOT INITIALIZED");
74 return iter::val(*this);
75 }
76
83 Result<void, AllocationError> init(Allocator &alloc, size_t initCapacity = 10) noexcept {
84 ensure(!head && !tail && !count, "ALREADY INITIALIZED");
85 ensure(elements.head == nullptr && elements.size() == 0, "ALREADY INITIALIZED!");
86 initCapacity = initCapacity ? initCapacity : 10;
87 auto allocRes = alloc.create_many<T>(initCapacity);
88 if (allocRes.is_error()) {
89 return allocRes.error();
90 }
91
92 elements = *allocRes;
93 return success();
94 }
95
104 Result<void, AllocationError> init(Allocator &alloc, std::initializer_list<T> init, size_t capacity = 0) noexcept {
105 ensure(!head && !tail && !count, "ALREADY INITIALIZED");
106 ensure(elements.head == nullptr && elements.size() == 0, "ALREADY INITIALIZED!");
107 if (capacity < init.size()) {
108 capacity = init.size();
109 }
110 capacity = capacity ? capacity : 10;
111 auto allocRes = alloc.create_many<T>(init.size());
112 if (allocRes.is_error()) {
113 return allocRes.error();
114 }
115
116 elements = *allocRes;
117 for (const auto &elem: init) {
118 (void) push_back(elem);
119 }
120
121 return success();
122 }
123
128 void deinit(Allocator &alloc) {
129 if (elements.head) {
130 alloc.destroy_many(elements);
131 elements.head = nullptr;
132 }
133 elements.len = 0;
134 head = 0;
135 tail = 0;
136 count = 0;
137 }
138
140 const T &operator[](size_t i) const noexcept {
141 ensure(elements.head, "NOT INITIALIZED");
142 return at(i);
143 }
144
145 T &operator[](size_t i) noexcept {
146 ensure(elements.head, "NOT INITIALIZED");
147 return at(i);
148 }
149
151 [[nodiscard]] size_t size() const noexcept { return count; }
153 [[nodiscard]] size_t capacity() const noexcept { return elements.size(); }
154
156 [[nodiscard]] const T &at(size_t i) const noexcept {
157 ensure(elements.head, "NOT INITIALIZED");
158 ensure(i < count, "OUT OF BOUNDS ACCESS");
159 auto index = i + head;
160 if (index >= elements.size()) {
161 index -= elements.size();
162 }
163 return elements[index];
164 }
165
167 T &at(size_t i) noexcept {
168 ensure(elements.head, "NOT INITIALIZED");
169 ensure(i < count, "OUT OF BOUNDS ACCESS");
170 auto index = i + head;
171 if (index >= elements.size()) {
172 index -= elements.size();
173 }
174 return elements[index];
175 }
176
179 ensure(elements.head, "NOT INITIALIZED");
180 mtdefer { ensure(size() <= capacity(), "SIZE MISMATCH"); };
181 if (auto err = grow_if_needed(alloc); err.is_error()) {
182 return err;
183 }
184 if (push_back(elem).is_error()) {
186 }
187 return success();
188 }
189
192 ensure(elements.head, "NOT INITIALIZED");
193 mtdefer { ensure(size() <= capacity(), "SIZE MISMATCH"); };
194 if (auto err = grow_if_needed(alloc); err.is_error()) {
195 return err;
196 }
197 if (push_front(elem).is_error()) {
199 }
200 return success();
201 }
202
205 ensure(elements.head, "NOT INITIALIZED");
206 mtdefer { ensure(size() <= capacity(), "SIZE MISMATCH"); };
207 if (count >= elements.size()) {
209 }
210
211 elements[tail++] = elem;
212 count++;
213 if (tail == elements.size()) {
214 tail = 0;
215 }
216 return success();
217 }
218
220 const auto newSize = std::max(static_cast<size_t>(1), count);
221 auto newBufferRes = alloc.create_many<T>(newSize);
222 if (newBufferRes.is_error()) {
223 return newBufferRes.error();
224 }
225 auto newBuffer = *newBufferRes;
226
227 for (size_t i = 0, curHead = head; i < newBuffer.size() && i < count; ++i) {
228 mtdefer {
229 curHead += 1;
230 if (curHead == elements.size()) {
231 curHead = 0;
232 }
233 };
234 std::swap(newBuffer[i], elements[curHead]);
235 }
236 head = 0;
237 tail = elements.size();
238 count = tail - head;
239 alloc.destroy_many(elements);
240 elements = newBuffer;
241 head = 0;
242 return success();
243 }
244
247 ensure(elements.head, "NOT INITIALIZED");
248 mtdefer { ensure(size() <= capacity(), "SIZE MISMATCH"); };
249 if (count >= elements.size()) {
251 }
252
253 auto new_head = head;
254 if (new_head == 0) {
255 new_head = elements.size() - 1;
256 }
257 else {
258 new_head--;
259 }
260 elements[new_head] = elem;
261 count++;
262 head = new_head;
263 return success();
264 }
265
268 ensure(elements.head, "NOT INITIALIZED");
269 mtdefer { ensure(size() <= capacity(), "SIZE MISMATCH"); };
270 if (count == 0) {
271 return nullopt;
272 }
273
274 auto &res = elements[head++];
275
276 count--;
277 if (head == elements.size()) {
278 head = 0;
279 }
280 return Optional{std::move(res)};
281 }
282
285 ensure(elements.head, "NOT INITIALIZED");
286 mtdefer { ensure(size() <= capacity(), "SIZE MISMATCH"); };
287 if (count == 0) {
288 return nullopt;
289 }
290
291 auto new_tail = tail;
292 if (new_tail == 0) {
293 new_tail = elements.size() - 1;
294 }
295 else {
296 new_tail--;
297 }
298
299 auto res = elements[tail];
300 --count;
301 tail = new_tail;
302 return res;
303 }
304
305 private:
306 Result<void, AllocationError> grow_if_needed(Allocator &alloc) {
307 ensure(elements.head, "NOT INITIALIZED");
308 mtdefer { ensure(size() <= capacity(), "SIZE MISMATCH"); };
309 if (count < elements.size()) {
310 return success();
311 }
312
313 size_t newSize = elements.len + ((elements.len + 1) / 2);
314 ensure(newSize > size(), "FAILED TO GROW RING BUFFER");
315 auto newElemAllocRes = alloc.create_many<T>(newSize);
316 if (newElemAllocRes.is_error()) {
317 return newElemAllocRes.error();
318 }
319
320 auto newElems = *newElemAllocRes;
321 for (size_t i = 0, curHead = head; i < elements.size(); ++i) {
322 mtdefer {
323 curHead += 1;
324 if (curHead == elements.size()) {
325 curHead = 0;
326 }
327 };
328 std::swap(newElems[i], elements[curHead]);
329 }
330 head = 0;
331 tail = elements.size();
332 count = tail - head;
333 alloc.destroy_many(elements);
334 elements = newElems;
335 return success();
336 }
337 };
338
339 static_assert(is_mutable_addressable<RingBuffer<int>>, "RING BUFFER IS NOT MUTABLE ADDRESSABLE");
340
355 template<typename T, size_t Capacity = 10>
357 private:
358 std::array<T, Capacity> elements;
359 size_t head = 0;
360 size_t tail = 0;
361 size_t count = 0;
362
363 public:
364 using Elem = T;
365
370 [[nodiscard]] decltype(auto) ptr_iter() noexcept { return iter::ptr(*this); }
371
376 [[nodiscard]] decltype(auto) ptr_iter() const noexcept { return iter::const_ptr(*this); }
377
382 [[nodiscard]] decltype(auto) iter() noexcept { return iter::val(*this); }
383
387 void init() {};
388
392 void init(std::initializer_list<T> list) {
393 ensure(list.size() < Capacity, "TOO MANY ELEMENTS PROVIDED");
394 for (const auto &e: list) {
395 (void) push_back(e);
396 }
397 }
398
404 [[nodiscard]] const T &operator[](size_t i) const noexcept { return at(i); }
405
411 [[nodiscard]] T &operator[](size_t i) noexcept { return at(i); }
412
416 [[nodiscard]] size_t size() const noexcept { return count; }
417
423 [[nodiscard]] const T &at(size_t i) const noexcept {
424 ensure(i < count, "OUT OF BOUNDS ACCESS");
425 auto index = i + head;
426 if (index >= elements.size()) {
427 index -= elements.size();
428 }
429 return elements[index];
430 }
431
437 [[nodiscard]] T &at(size_t i) noexcept {
438 ensure(i < count, "OUT OF BOUNDS ACCESS");
439 auto index = i + head;
440 if (index >= elements.size()) {
441 index -= elements.size();
442 }
443 return elements[index];
444 }
445
452 if (count >= elements.size()) {
454 }
455
456 elements[tail++] = elem;
457 count++;
458 if (tail == elements.size()) {
459 tail = 0;
460 }
461 return success();
462 }
463
470 if (count >= elements.size()) {
472 }
473
474 auto new_head = head;
475 if (new_head == 0) {
476 new_head = elements.size() - 1;
477 }
478 else {
479 new_head--;
480 }
481 elements[new_head] = elem;
482 count++;
483 head = new_head;
484 return success();
485 }
486
491 [[nodiscard]] Optional<T> pop_front() {
492 if (count == 0) {
493 return nullopt;
494 }
495
496 auto res = std::move(elements[head++]);
497
498 count--;
499 if (head == elements.size()) {
500 head = 0;
501 }
502 return res;
503 }
504
509 [[nodiscard]] Optional<T> pop_back() {
510 if (count == 0) {
511 return nullopt;
512 }
513
514 auto new_tail = tail;
515 if (new_tail == 0) {
516 new_tail = elements.size() - 1;
517 }
518 else {
519 new_tail--;
520 }
521
522 auto res = elements[tail];
523 --count;
524 tail = new_tail;
525 return res;
526 }
527 };
528
529 static_assert(is_mutable_addressable<RingBuffer<int>>, "RING BUFFER IS NOT MUTABLE ADDRESSABLE");
530
531} // namespace mtcore
532
533#endif // MTCORE_RING_BUFFER_HPP
ValIter< T > val(const T &r)
Generic value iterator that uses the operator[] and incrementing indexes to iterate over a collection...
Definition iter.hpp:114
constexpr auto nullopt
Placeholder value for an empty Optional.
Definition optional.hpp:409
ConstPtrIter< T > const_ptr(const T &r)
Generic constant pointer iterator that uses the operator[] and incrementing indexes to iterate over a...
Definition iter.hpp:128
PtrIter< T > ptr(T &r)
Generic pointer iterator that uses the operator[] and incrementing indexes to iterate over a collecti...
Definition iter.hpp:101
#define ensure(check,...)
Ensures that a check holds true, aborts the program if not true Will print error if the condition is ...
#define mtdefer
Defer statement that will mtdefer execution until the scope is left, at which point the code will run...
Success< void > success()
Creates a successful void Result object.
Definition result.hpp:398
Error< Underlying > error(Underlying err)
Creates an error.
Definition result.hpp:425
constexpr bool is_mutable_addressable
Boolean check for if something has the trait Addressable.
Core library for C++ with Zig-related functionality.
Represents a memory allocator Exact behavior depends on the underlying VTable used Should use the a_*...
void destroy_many(Slice< T > s)
Destroys objects allocated by this allocator by calling the destructors and freeing memory.
Result< Slice< T >, AllocationError > create_many(size_t count=1)
Allocates some block of memory with an allocator with a known type Will call default constructor on t...
Represents a ring buffer with static memory allocation Can be used as a FIFO or LIFO queue Allows mem...
void init(std::initializer_list< T > list)
Initializes a new fixed ring buffer with specific elements.
size_t size() const noexcept
Gets current size of the buffer.
void init()
Initializes a new fixed ring buffer.
Optional< T > pop_front()
Removes an element from the front.
Optional< T > pop_back()
Removes an element from the back.
Result< void, CollectionAddNoAllocationError > push_front(const T &elem)
Adds an element to the front of the buffer.
Result< void, CollectionAddNoAllocationError > push_back(const T &elem)
Adds an element to the back of the buffer.
decltype(auto) iter() noexcept
Gets an iterator that returns copies of values.
T & at(size_t i) noexcept
Gets an element at a specific index.
decltype(auto) ptr_iter() const noexcept
Gets a constant pointer iterator to values.
decltype(auto) ptr_iter() noexcept
Gets a pointer iterator to values.
const T & operator[](size_t i) const noexcept
Gets an element at a specific index.
const T & at(size_t i) const noexcept
Gets an element at a specific index.
T & operator[](size_t i) noexcept
Gets an element at a specific index.
Represents a value that may or may not exist (an "Optional" value) Similar concept to std::optional,...
Definition optional.hpp:235
Represents a Result that may have an error (error code) or a success value A type of "void" means the...
Definition result.hpp:170
Represents a ring buffer with dynamically allocated memory Can be used as a FIFO or LIFO queue Allows...
decltype(auto) ptr_iter() noexcept
Gets iterator over pointers to elements.
Result< void, AllocationError > init(Allocator &alloc, std::initializer_list< T > init, size_t capacity=0) noexcept
Initializes an ring buffer with specified elements.
const T & operator[](size_t i) const noexcept
Access element.
Result< void, CollectionAddNoAllocationError > push_front(const T &elem)
Add element to front of buffer.
const T & at(size_t i) const noexcept
Access element.
decltype(auto) iter() noexcept
Gets iterator over values.
size_t size() const noexcept
Get size.
size_t capacity() const noexcept
Get capacity.
Optional< T > pop_front()
Removes and returns element from front.
decltype(auto) ptr_iter() const noexcept
Gets iterator over const pointers to elements.
T & at(size_t i) noexcept
Access element.
Result< void, AllocationError > push_back(Allocator &alloc, const T &elem)
Add element to back of buffer.
Optional< T > pop_back()
Removes and returns element from back.
Result< void, CollectionAddNoAllocationError > push_back(const T &elem)
Add element to back of buffer.
T & operator[](size_t i) noexcept
Access element.
Result< void, AllocationError > push_front(Allocator &alloc, const T &elem)
Add element to front of buffer.
Result< void, AllocationError > shrink_to_fit(Allocator &alloc)
void deinit(Allocator &alloc)
Cleans up any memory held by ring buffer.
Result< void, AllocationError > init(Allocator &alloc, size_t initCapacity=10) noexcept
Initializes an empty ArrayList with an initial capacity.
A Slice which is just a pointer + length Accessing elements through the array operator will do bounds...
constexpr size_t size() const noexcept
Gets the size of a Slice.