Easy Navigation
Loading...
Searching...
No Matches
CircularBuffer.hpp
Go to the documentation of this file.
1// Copyright 2025 Intelligent Robotics Lab
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef EASYNAV_COMMON__CIRCULARBUFFER_HPP_
16#define EASYNAV_COMMON__CIRCULARBUFFER_HPP_
17
18#include <vector>
19#include <mutex>
20#include <cstddef>
21
22namespace easynav
23{
24
31template<typename T>
33{
34public:
36 explicit CircularBuffer(std::size_t capacity)
37 : buffer_(capacity),
38 capacity_(capacity),
39 head_(0),
40 tail_(0),
41 size_(0)
42 {
43 }
44
50 : buffer_(other.capacity_),
51 capacity_(other.capacity_),
52 head_(0),
53 tail_(0),
54 size_(0)
55 {
56 std::lock_guard<std::mutex> lock(other.mutex_);
57 buffer_ = other.buffer_;
58 head_ = other.head_;
59 tail_ = other.tail_;
60 size_ = other.size_;
61 }
62
65 {
66 if (this == &other) {
67 return *this;
68 }
69
70 // Lock both mutexes in a deadlock-safe way.
71 std::scoped_lock<std::mutex, std::mutex> lock(mutex_, other.mutex_);
72
73 capacity_ = other.capacity_;
74 buffer_ = other.buffer_;
75 head_ = other.head_;
76 tail_ = other.tail_;
77 size_ = other.size_;
78 return *this;
79 }
80
85
87 std::size_t capacity() const noexcept
88 {
89 return capacity_;
90 }
91
93 std::size_t size() const noexcept
94 {
95 std::lock_guard<std::mutex> lock(mutex_);
96 return size_;
97 }
98
100 bool empty() const noexcept
101 {
102 std::lock_guard<std::mutex> lock(mutex_);
103 return size_ == 0;
104 }
105
107 bool full() const noexcept
108 {
109 std::lock_guard<std::mutex> lock(mutex_);
110 return size_ == capacity_;
111 }
112
114 void clear() noexcept
115 {
116 std::lock_guard<std::mutex> lock(mutex_);
117 head_ = 0;
118 tail_ = 0;
119 size_ = 0;
120 }
121
123 void push(const T & value)
124 {
125 std::lock_guard<std::mutex> lock(mutex_);
126 buffer_[head_] = value;
127 advance_head_();
128 }
129
131 void push(T && value)
132 {
133 std::lock_guard<std::mutex> lock(mutex_);
134 buffer_[head_] = std::move(value);
135 advance_head_();
136 }
137
142 bool pop(T & out)
143 {
144 std::lock_guard<std::mutex> lock(mutex_);
145 if (size_ == 0) {
146 return false;
147 }
148
149 out = buffer_[tail_];
150 tail_ = (tail_ + 1) % capacity_;
151 --size_;
152 return true;
153 }
154
159 bool latest(T & out) const
160 {
161 std::lock_guard<std::mutex> lock(mutex_);
162 if (size_ == 0) {
163 return false;
164 }
165
166 const std::size_t newest_index =
167 (head_ + capacity_ - 1) % capacity_;
168 out = buffer_[newest_index];
169 return true;
170 }
171
173 const T & latest_ref() const
174 {
175 std::lock_guard<std::mutex> lock(mutex_);
176
177 const std::size_t newest_index =
178 (head_ + capacity_ - 1) % capacity_;
179 return buffer_[newest_index];
180 }
181
182 // DEBUG ONLY: raw access to slots
184 {
186 const T & value;
187 };
188
189 DebugSlotView raw_slot(std::size_t idx) const
190 {
191 if (!buffer_[idx].engaged) {
192 // dummy static empty
193 static T dummy{};
194 static const DebugSlotView empty{false, dummy};
195 return empty;
196 }
197 return DebugSlotView{true, buffer_[idx].storage};
198 }
199
200private:
202 void advance_head_()
203 {
204 head_ = (head_ + 1) % capacity_;
205 if (size_ < capacity_) {
206 ++size_;
207 } else {
208 // Buffer is full, we overwrite the oldest element.
209 tail_ = (tail_ + 1) % capacity_;
210 }
211 }
212
213 mutable std::mutex mutex_;
214 std::vector<T> buffer_;
215 std::size_t capacity_;
216 std::size_t head_; // next position to write
217 std::size_t tail_; // next position to read
218 std::size_t size_; // number of valid elements
219};
220
221} // namespace easynav
222
223#endif // EASYNAV_COMMON__CIRCULARBUFFER_HPP_
void clear() noexcept
Remove all elements, keeping the allocated storage.
Definition CircularBuffer.hpp:114
void push(T &&value)
Push a new element (move). Overwrites the oldest if the buffer is full.
Definition CircularBuffer.hpp:131
const T & latest_ref() const
Get a const reference to the newest element without removing it.
Definition CircularBuffer.hpp:173
bool empty() const noexcept
True if the buffer is empty.
Definition CircularBuffer.hpp:100
std::size_t size() const noexcept
Current number of valid elements.
Definition CircularBuffer.hpp:93
CircularBuffer(std::size_t capacity)
Construct a buffer with the given capacity.
Definition CircularBuffer.hpp:36
void push(const T &value)
Push a new element (copy). Overwrites the oldest if the buffer is full.
Definition CircularBuffer.hpp:123
DebugSlotView raw_slot(std::size_t idx) const
Definition CircularBuffer.hpp:189
bool latest(T &out) const
Get a copy of the newest element without removing it.
Definition CircularBuffer.hpp:159
bool pop(T &out)
Pop the oldest element.
Definition CircularBuffer.hpp:142
CircularBuffer & operator=(CircularBuffer &&)=delete
CircularBuffer & operator=(const CircularBuffer &other)
Copy assignment.
Definition CircularBuffer.hpp:64
std::size_t capacity() const noexcept
Maximum number of elements that can be stored.
Definition CircularBuffer.hpp:87
bool full() const noexcept
True if the buffer is full.
Definition CircularBuffer.hpp:107
CircularBuffer(CircularBuffer &&)=delete
Move operations are optional.
CircularBuffer(const CircularBuffer &other)
Copy constructor.
Definition CircularBuffer.hpp:49
Definition CircularBuffer.hpp:23
Definition CircularBuffer.hpp:184
const T & value
Definition CircularBuffer.hpp:186
bool has_value
Definition CircularBuffer.hpp:185