Common System 0.2.0
Common interfaces and patterns for system integration
Loading...
Searching...
No Matches
circular_buffer.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
13#pragma once
14
15#include <array>
16#include <atomic>
17#include <cstddef>
18#include <mutex>
19#include <optional>
20#include <utility>
21
22namespace kcenon::common::utils {
23
24template<typename T, std::size_t Capacity>
25class CircularBuffer {
26 static_assert(Capacity > 0, "CircularBuffer capacity must be greater than zero");
27
28public:
29 CircularBuffer() = default;
30
31 bool push(const T& value, bool overwrite = false) {
32 std::lock_guard<std::mutex> lock(mutex_);
33 if (is_full_locked() && !overwrite) {
34 return false;
35 }
36 if (is_full_locked()) {
37 pop_locked();
38 }
39 buffer_[tail_] = value;
41 ++size_;
42 return true;
43 }
44
45 bool push(T&& value, bool overwrite = false) {
46 std::lock_guard<std::mutex> lock(mutex_);
47 if (is_full_locked() && !overwrite) {
48 return false;
49 }
50 if (is_full_locked()) {
51 pop_locked();
52 }
53 buffer_[tail_] = std::move(value);
55 ++size_;
56 return true;
57 }
58
59 [[nodiscard]] std::optional<T> pop() {
60 std::lock_guard<std::mutex> lock(mutex_);
61 return pop_locked();
62 }
63
64 [[nodiscard]] bool empty() const {
65 std::lock_guard<std::mutex> lock(mutex_);
66 return size_ == 0;
67 }
68
69 [[nodiscard]] bool full() const {
70 std::lock_guard<std::mutex> lock(mutex_);
71 return size_ == Capacity;
72 }
73
74 [[nodiscard]] std::size_t size() const {
75 std::lock_guard<std::mutex> lock(mutex_);
76 return size_;
77 }
78
79 [[nodiscard]] constexpr std::size_t capacity() const {
80 return Capacity;
81 }
82
83private:
84 void advance(std::size_t& index) noexcept {
85 index = (index + 1) % Capacity;
86 }
87
88 bool is_full_locked() const noexcept {
89 return size_ == Capacity;
90 }
91
92 std::optional<T> pop_locked() {
93 if (size_ == 0) {
94 return std::nullopt;
95 }
96 auto value = std::move(buffer_[head_]);
98 --size_;
99 return value;
100 }
101
102 mutable std::mutex mutex_;
103 std::array<T, Capacity> buffer_{};
104 std::size_t head_{0};
105 std::size_t tail_{0};
106 std::size_t size_{0};
107};
108
117template<typename T, std::size_t Capacity>
119 static_assert(Capacity > 0, "SPSCCircularBuffer capacity must be greater than zero");
120
121public:
123
128 bool push(const T& value) {
129 const auto tail = tail_.load(std::memory_order_relaxed);
130 const auto next_tail = advance(tail);
131 if (next_tail == head_.load(std::memory_order_acquire)) {
132 return false; // full
133 }
134 buffer_[tail] = value;
135 tail_.store(next_tail, std::memory_order_release);
136 return true;
137 }
138
139 bool push(T&& value) {
140 const auto tail = tail_.load(std::memory_order_relaxed);
141 const auto next_tail = advance(tail);
142 if (next_tail == head_.load(std::memory_order_acquire)) {
143 return false; // full
144 }
145 buffer_[tail] = std::move(value);
146 tail_.store(next_tail, std::memory_order_release);
147 return true;
148 }
149
154 [[nodiscard]] std::optional<T> pop() {
155 const auto head = head_.load(std::memory_order_relaxed);
156 if (head == tail_.load(std::memory_order_acquire)) {
157 return std::nullopt; // empty
158 }
159 auto value = std::move(buffer_[head]);
160 head_.store(advance(head), std::memory_order_release);
161 return value;
162 }
163
164 [[nodiscard]] bool empty() const noexcept {
165 return head_.load(std::memory_order_acquire)
166 == tail_.load(std::memory_order_acquire);
167 }
168
169 [[nodiscard]] bool full() const noexcept {
170 return advance(tail_.load(std::memory_order_acquire))
171 == head_.load(std::memory_order_acquire);
172 }
173
174 [[nodiscard]] std::size_t size() const noexcept {
175 const auto head = head_.load(std::memory_order_acquire);
176 const auto tail = tail_.load(std::memory_order_acquire);
177 if (tail >= head) {
178 return tail - head;
179 }
180 return (Capacity + 1) - head + tail;
181 }
182
183 [[nodiscard]] constexpr std::size_t capacity() const noexcept {
184 return Capacity;
185 }
186
187private:
188 static std::size_t advance(std::size_t index) noexcept {
189 return (index + 1) % (Capacity + 1);
190 }
191
192 // Internal buffer has Capacity+1 slots to distinguish full from empty
193 std::array<T, Capacity + 1> buffer_{};
194 alignas(64) std::atomic<std::size_t> head_{0};
195 alignas(64) std::atomic<std::size_t> tail_{0};
196};
197
198} // namespace kcenon::common::utils
constexpr std::size_t capacity() const
bool push(T &&value, bool overwrite=false)
bool push(const T &value, bool overwrite=false)
void advance(std::size_t &index) noexcept
Lock-free circular buffer for single-producer single-consumer (SPSC).
bool push(const T &value)
Push a value (producer thread only).
constexpr std::size_t capacity() const noexcept
std::optional< T > pop()
Pop a value (consumer thread only).
static std::size_t advance(std::size_t index) noexcept