1+ #pragma once
2+
3+ #include < algorithm>
4+ #include < array>
5+ #include < cstddef>
6+ #include < iterator>
7+ #include < utility>
8+
9+ namespace OpenVic {
10+ /* *
11+ * @brief A fixed-size circular buffer that stores the last N elements, supporting iteration.
12+ *
13+ * @tparam T The type of elements to store.
14+ * @tparam N The maximum capacity (must be > 0).
15+ */
16+ template <typename T, std::size_t N>
17+ class CircularBuffer {
18+ static_assert (N > 0 , " Capacity N must be greater than 0." );
19+
20+ private:
21+ std::array<T, N> storage;
22+ std::size_t head = 0 ; // Index of the next available slot for insertion
23+ std::size_t current_size = 0 ;
24+
25+ /* *
26+ * @brief Calculates the physical index in storage corresponding to the oldest element.
27+ * @return The physical index of the beginning of the buffer's content.
28+ */
29+ std::size_t calculate_start_index () const noexcept {
30+ // If the buffer is full, the oldest element is at 'head'.
31+ // If not full, the oldest element is at index 0.
32+ // The expression (head + N - current_size) % N correctly finds the start
33+ return (head + N - current_size) % N;
34+ }
35+
36+ /* *
37+ * @brief Encapsulates the logic for advancing the head pointer and updating the size.
38+ * * This logic is common to both push_back overloads.
39+ */
40+ void increment_head_and_size () noexcept {
41+ // Advance the head pointer, wrapping around the capacity N
42+ head = (head + 1 ) % N;
43+
44+ // Only increment the size if the buffer wasn't already full
45+ if (current_size < N) {
46+ current_size++;
47+ }
48+ }
49+
50+ public:
51+ CircularBuffer () = default ;
52+
53+ template <typename ValueType>
54+ class CircularBufferIterator {
55+ public:
56+ using iterator_category = std::forward_iterator_tag;
57+ using value_type = ValueType;
58+ using difference_type = std::ptrdiff_t ;
59+ using pointer = ValueType*;
60+ using reference = ValueType&;
61+
62+ private:
63+ ValueType* data_ptr;
64+ std::size_t logical_index;
65+ std::size_t physical_index;
66+ std::size_t capacity;
67+
68+ CircularBufferIterator (
69+ ValueType* new_data_ptr,
70+ std::size_t start_phys_index,
71+ std::size_t new_logical_index,
72+ std::size_t new_capacity
73+ ) :
74+ data_ptr (new_data_ptr),
75+ logical_index (new_logical_index),
76+ capacity (new_capacity)
77+ {
78+ physical_index = (start_phys_index + new_logical_index) % capacity;
79+ }
80+
81+ friend class CircularBuffer <T, N>;
82+
83+ public:
84+ CircularBufferIterator () :
85+ data_ptr (nullptr ),
86+ logical_index (0 ),
87+ physical_index (0 ),
88+ capacity (0 )
89+ {}
90+
91+ reference operator *() const {
92+ return data_ptr[physical_index];
93+ }
94+
95+ pointer operator ->() const {
96+ return &data_ptr[physical_index];
97+ }
98+
99+ CircularBufferIterator& operator ++() {
100+ if (logical_index < N) {
101+ logical_index++;
102+ physical_index = (physical_index + 1 ) % capacity;
103+ }
104+ return *this ;
105+ }
106+
107+ CircularBufferIterator operator ++(int ) {
108+ CircularBufferIterator temp = *this ;
109+ ++(*this );
110+ return temp;
111+ }
112+
113+ bool operator ==(const CircularBufferIterator& other) const {
114+ return logical_index == other.logical_index ;
115+ }
116+
117+ bool operator !=(const CircularBufferIterator& other) const {
118+ return !(*this == other);
119+ }
120+ };
121+
122+ // Define the iterator types for convenience
123+ using iterator = CircularBufferIterator<T>;
124+ using const_iterator = CircularBufferIterator<const T>;
125+
126+ /* *
127+ * @brief Returns an iterator to the beginning of the buffer (the oldest element).
128+ * @return An iterator pointing to the first element in insertion order.
129+ */
130+ iterator begin () {
131+ return iterator (
132+ storage.data (),
133+ calculate_start_index (),
134+ 0 ,
135+ N
136+ );
137+ }
138+
139+ /* *
140+ * @brief Returns a const iterator to the beginning of the buffer.
141+ */
142+ const_iterator begin () const {
143+ return const_iterator (
144+ storage.data (),
145+ calculate_start_index (),
146+ 0 ,
147+ N
148+ );
149+ }
150+
151+ /* *
152+ * @brief Returns an iterator to the end of the buffer (one past the newest element).
153+ * @return An iterator pointing one past the last element.
154+ */
155+ iterator end () {
156+ return iterator (
157+ storage.data (),
158+ 0 ,
159+ current_size,
160+ N
161+ );
162+ }
163+
164+ /* *
165+ * @brief Returns a const iterator to the end of the buffer.
166+ */
167+ const_iterator end () const {
168+ return const_iterator (
169+ storage.data (),
170+ 0 ,
171+ current_size,
172+ N
173+ );
174+ }
175+
176+ // Convenience for const iteration
177+ const_iterator cbegin () const { return begin (); }
178+ const_iterator cend () const { return end (); }
179+
180+ /* *
181+ * @brief Adds an element to the buffer. (Copy version)
182+ * @param value The value to be added.
183+ */
184+ void push_back (const T& value) {
185+ storage[head] = value;
186+ increment_head_and_size ();
187+ }
188+
189+ /* *
190+ * @brief Adds an element to the buffer. (Move version)
191+ * @param value The rvalue reference to the value to be added.
192+ */
193+ void push_back (T&& value) {
194+ storage[head] = std::move (value);
195+ increment_head_and_size ();
196+ }
197+
198+ /* *
199+ * @brief Fills the entire capacity of the buffer with a single value.
200+ * * After this call, size() will equal capacity(), and the head will be reset to 0.
201+ * @param value The value to copy into every slot of the buffer.
202+ */
203+ void fill (const T& value) {
204+ // 1. Use std::fill to efficiently assign the value to every element
205+ // in the underlying fixed array.
206+ std::fill (storage.begin (), storage.end (), value);
207+
208+ // 2. Update the internal state to reflect a full buffer.
209+ // The current_size is now equal to the full capacity N.
210+ current_size = N;
211+
212+ // 3. Reset the head index.
213+ // When the buffer is full and non-circularly indexed, the next insertion
214+ // should start overwriting at index 0 (the calculated start index).
215+ // Since current_size is N, calculate_start_index() would correctly return
216+ // (head + N - N) % N == head % N. Setting head to 0 simplifies the state.
217+ head = 0 ;
218+ }
219+
220+ constexpr std::size_t capacity () const noexcept {
221+ return N;
222+ }
223+
224+ std::size_t size () const noexcept {
225+ return current_size;
226+ }
227+
228+ bool empty () const noexcept {
229+ return current_size == 0 ;
230+ }
231+
232+ bool full () const noexcept {
233+ return current_size == N;
234+ }
235+ };
236+ }
0 commit comments