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#include <utility>
22
23namespace easynav
24{
25
32template<typename T>
34{
35public:
37 explicit CircularBuffer(std::size_t capacity)
38 : buffer_(capacity),
39 capacity_(capacity),
40 head_(0),
41 tail_(0),
42 size_(0)
43 {
44 }
45
51 : buffer_(other.capacity_),
52 capacity_(other.capacity_),
53 head_(0),
54 tail_(0),
55 size_(0)
56 {
57 std::lock_guard<std::mutex> lock(other.mutex_);
58 buffer_ = other.buffer_;
59 head_ = other.head_;
60 tail_ = other.tail_;
61 size_ = other.size_;
62 }
63
66 {
67 if (this == &other) {
68 return *this;
69 }
70
71 // Lock both mutexes in a deadlock-safe way.
72 std::scoped_lock<std::mutex, std::mutex> lock(mutex_, other.mutex_);
73
74 capacity_ = other.capacity_;
75 buffer_ = other.buffer_;
76 head_ = other.head_;
77 tail_ = other.tail_;
78 size_ = other.size_;
79 return *this;
80 }
81
86
88 std::size_t capacity() const noexcept
89 {
90 return capacity_;
91 }
92
94 std::size_t size() const noexcept
95 {
96 std::lock_guard<std::mutex> lock(mutex_);
97 return size_;
98 }
99
101 bool empty() const noexcept
102 {
103 std::lock_guard<std::mutex> lock(mutex_);
104 return size_ == 0;
105 }
106
108 bool full() const noexcept
109 {
110 std::lock_guard<std::mutex> lock(mutex_);
111 return size_ == capacity_;
112 }
113
115 void clear() noexcept
116 {
117 std::lock_guard<std::mutex> lock(mutex_);
118 head_ = 0;
119 tail_ = 0;
120 size_ = 0;
121 }
122
124 void push(const T & value)
125 {
126 std::lock_guard<std::mutex> lock(mutex_);
127 buffer_[head_] = value;
128 advance_head_();
129 }
130
132 void push(T && value)
133 {
134 std::lock_guard<std::mutex> lock(mutex_);
135 buffer_[head_] = std::move(value);
136 advance_head_();
137 }
138
143 bool pop(T & out)
144 {
145 std::lock_guard<std::mutex> lock(mutex_);
146 if (size_ == 0) {
147 return false;
148 }
149
150 out = buffer_[tail_];
151 tail_ = (tail_ + 1) % capacity_;
152 --size_;
153 return true;
154 }
155
160 bool latest(T & out) const
161 {
162 std::lock_guard<std::mutex> lock(mutex_);
163 if (size_ == 0) {
164 return false;
165 }
166
167 const std::size_t newest_index =
168 (head_ + capacity_ - 1) % capacity_;
169 out = buffer_[newest_index];
170 return true;
171 }
172
173 // DEBUG ONLY: raw access to slots
175 {
177 const T & value;
178 };
179
180 DebugSlotView raw_slot(std::size_t idx) const
181 {
182 if (!buffer_[idx].engaged) {
183 // dummy static empty
184 static T dummy{};
185 static const DebugSlotView empty{false, dummy};
186 return empty;
187 }
188 return DebugSlotView{true, buffer_[idx].storage};
189 }
190
191private:
193 void advance_head_()
194 {
195 head_ = (head_ + 1) % capacity_;
196 if (size_ < capacity_) {
197 ++size_;
198 } else {
199 // Buffer is full, we overwrite the oldest element.
200 tail_ = (tail_ + 1) % capacity_;
201 }
202 }
203
204 mutable std::mutex mutex_;
205 std::vector<T> buffer_;
206 std::size_t capacity_;
207 std::size_t head_; // next position to write
208 std::size_t tail_; // next position to read
209 std::size_t size_; // number of valid elements
210};
211
212} // namespace easynav
213
214#endif // EASYNAV_COMMON__CIRCULARBUFFER_HPP_
void clear() noexcept
Remove all elements, keeping the allocated storage.
Definition CircularBuffer.hpp:115
void push(T &&value)
Push a new element (move). Overwrites the oldest if the buffer is full.
Definition CircularBuffer.hpp:132
bool empty() const noexcept
True if the buffer is empty.
Definition CircularBuffer.hpp:101
std::size_t size() const noexcept
Current number of valid elements.
Definition CircularBuffer.hpp:94
CircularBuffer(std::size_t capacity)
Construct a buffer with the given capacity.
Definition CircularBuffer.hpp:37
void push(const T &value)
Push a new element (copy). Overwrites the oldest if the buffer is full.
Definition CircularBuffer.hpp:124
DebugSlotView raw_slot(std::size_t idx) const
Definition CircularBuffer.hpp:180
bool latest(T &out) const
Get a copy of the newest element without removing it.
Definition CircularBuffer.hpp:160
bool pop(T &out)
Pop the oldest element.
Definition CircularBuffer.hpp:143
CircularBuffer & operator=(CircularBuffer &&)=delete
CircularBuffer & operator=(const CircularBuffer &other)
Copy assignment.
Definition CircularBuffer.hpp:65
std::size_t capacity() const noexcept
Maximum number of elements that can be stored.
Definition CircularBuffer.hpp:88
bool full() const noexcept
True if the buffer is full.
Definition CircularBuffer.hpp:108
CircularBuffer(CircularBuffer &&)=delete
Move operations are optional.
CircularBuffer(const CircularBuffer &other)
Copy constructor.
Definition CircularBuffer.hpp:50
Definition CircularBuffer.hpp:24
Definition CircularBuffer.hpp:175
const T & value
Definition CircularBuffer.hpp:177
bool has_value
Definition CircularBuffer.hpp:176