StormByte C++ Library: Buffer module 1.2.0
StormByte-Buffer is the buffer module of the StormByte C++ suite.
Loading...
Searching...
No Matches
hopper.txx
Go to the documentation of this file.
1/*
2 * Copyright (C) 2024-2026 David C. Manuelda (StormBytePP)
3 *
4 * This file is part of StormByte-Buffer.
5 *
6 * StormByte-Buffer is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License version 3
8 * or later, as published by the Free Software Foundation.
9 *
10 * StormByte-Buffer is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with StormByte-Buffer. If not, see
17 * <https://www.gnu.org/licenses/lgpl-3.0.html>.
18 */
19
20#pragma once
21
22#include <StormByte/type_traits.hxx>
23
24#include <atomic>
25#include <condition_variable>
26#include <cstddef>
27#include <memory>
28#include <mutex>
29#include <queue>
30#include <utility>
31
32namespace StormByte::Buffer {
33 /**
34 * @class Hopper<T>::Implementation
35 * @brief Internal implementation of Hopper queue details.
36 *
37 * Handles mutex-protected queue operations, atomic capacity settings,
38 * condition variable notifications, and EoF flags.
39 */
40 template<Type::MoveConstructible T>
41 class Hopper<T>::Implementation {
42 public:
43 /**
44 * @brief Constructs an unbounded Implementation instance.
45 */
46 Implementation() noexcept
47 : m_eof(false), m_wake(nullptr), m_cap(0), m_writers(1) {}
48
49 /**
50 * @brief Constructs a bounded Implementation instance.
51 * @param capacity Maximum items allowed.
52 */
53 explicit Implementation(std::size_t capacity) noexcept
54 : m_eof(false), m_wake(nullptr), m_cap(capacity), m_writers(1) {}
55
56 /**
57 * @brief Destructor. Marks EoF and wakes waiting producers.
58 */
59 ~Implementation() noexcept {
60 m_eof.store(true, std::memory_order_release);
61 m_space.notify_all();
62 }
63
64 /**
65 * @brief Gets capacity ceiling.
66 * @return Capacity value.
67 */
68 std::size_t Capacity() const noexcept {
69 return m_cap.load(std::memory_order_acquire);
70 }
71
72 /**
73 * @brief Sets capacity ceiling.
74 * @param capacity New capacity value.
75 */
76 void Capacity(std::size_t capacity) noexcept {
77 m_cap.store(capacity, std::memory_order_release);
78 m_space.notify_all();
79 }
80
81 /**
82 * @brief Gets item count in queue.
83 * @return Count of items.
84 */
85 std::size_t Size() const noexcept {
86 std::lock_guard<std::mutex> lock(m_mutex);
87 return m_items.size();
88 }
89
90 /**
91 * @brief Checks if bounded bucket is full.
92 * @return true if capacity > 0 and size >= capacity.
93 */
94 bool Full() const noexcept {
95 const std::size_t cap = m_cap.load(std::memory_order_acquire);
96 if (cap == 0)
97 return false;
98 return Size() >= cap;
99 }
100
101 /**
102 * @brief Enqueues an item, waiting if full.
103 * @param item Item to enqueue.
104 */
105 void Push(T item) noexcept {
106 if constexpr (Type::NullablePointer<T>) {
107 if (!item)
108 return;
109 }
110 {
111 std::unique_lock<std::mutex> lock(m_mutex);
112 m_space.wait(lock, [this]() {
113 const std::size_t cap = m_cap.load(std::memory_order_acquire);
114 return cap == 0
115 || m_items.size() < cap
116 || m_eof.load(std::memory_order_acquire);
117 });
118 if (m_eof.load(std::memory_order_acquire))
119 return;
120 m_items.push(std::move(item));
121 }
122 SignalConsumer();
123 }
124
125 /**
126 * @brief Signals end of production.
127 */
128 void Eof() noexcept {
129 m_eof.store(true, std::memory_order_release);
130 SignalConsumer();
131 m_space.notify_all();
132 }
133
134 /**
135 * @brief Registers an extra writer.
136 */
137 void AddWriter() noexcept {
138 m_writers.fetch_add(1, std::memory_order_acq_rel);
139 }
140
141 /**
142 * @brief Releases one writer. Last writer force-closes.
143 */
144 void CloseWriter() noexcept {
145 unsigned prev = m_writers.load(std::memory_order_acquire);
146 while (prev > 0) {
147 if (m_writers.compare_exchange_weak(prev, prev - 1,
148 std::memory_order_acq_rel, std::memory_order_acquire)) {
149 if (prev == 1)
150 Eof();
151 return;
152 }
153 }
154 }
155
156 /**
157 * @brief Pops next item from queue without waiting.
158 * @return Next item, or default T if empty.
159 */
160 T Pop() noexcept {
161 T item{};
162 {
163 std::lock_guard<std::mutex> lock(m_mutex);
164 if (m_items.empty())
165 return T{};
166 item = std::move(m_items.front());
167 m_items.pop();
168 }
169 m_space.notify_one();
170 return item;
171 }
172
173 /**
174 * @brief Checks if Eof was signaled.
175 * @return true if Eof set.
176 */
177 bool EoF() const noexcept {
178 return m_eof.load(std::memory_order_acquire);
179 }
180
181 /**
182 * @brief Checks if queue is empty.
183 * @return true if empty.
184 */
185 bool Empty() const noexcept {
186 std::lock_guard<std::mutex> lock(m_mutex);
187 return m_items.empty();
188 }
189
190 void Notify(std::condition_variable& wake) noexcept {
191 m_wake.store(&wake, std::memory_order_release);
192 }
193
194 void Unnotify() noexcept {
195 m_wake.store(nullptr, std::memory_order_release);
196 }
197
198 private:
199 /**
200 * @brief Notifies registered consumer condition variable if set.
201 */
202 void SignalConsumer() noexcept {
203 std::condition_variable* wake = m_wake.load(std::memory_order_acquire);
204 if (wake == nullptr)
205 return;
206 wake->notify_one();
207 }
208
209 mutable std::mutex m_mutex; ///< Guards queue access.
210 std::condition_variable m_space; ///< Producer wait condition when full.
211 std::queue<T> m_items; ///< Queue of stored items.
212 std::atomic<bool> m_eof; ///< End of production flag.
213 std::atomic<std::condition_variable*> m_wake; ///< Consumer condition variable.
214 std::atomic<std::size_t> m_cap; ///< Capacity ceiling (0 = unbounded).
215 std::atomic<unsigned> m_writers; ///< Live writers; last CloseWriter Eofs.
216 };
217
218 template<Type::MoveConstructible T>
219 Hopper<T>::Hopper() noexcept
220 : m_impl(std::make_unique<Implementation>()) {}
221
222 template<Type::MoveConstructible T>
223 Hopper<T>::Hopper(std::size_t capacity) noexcept
224 : m_impl(std::make_unique<Implementation>(capacity)) {}
225
226 template<Type::MoveConstructible T>
227 Hopper<T>::~Hopper() noexcept = default;
228
229 template<Type::MoveConstructible T>
230 std::size_t Hopper<T>::Capacity() const noexcept {
231 return m_impl->Capacity();
232 }
233
234 template<Type::MoveConstructible T>
235 void Hopper<T>::Capacity(std::size_t capacity) noexcept {
236 m_impl->Capacity(capacity);
237 }
238
239 template<Type::MoveConstructible T>
240 std::size_t Hopper<T>::Size() const noexcept {
241 return m_impl->Size();
242 }
243
244 template<Type::MoveConstructible T>
245 bool Hopper<T>::Full() const noexcept {
246 return m_impl->Full();
247 }
248
249 template<Type::MoveConstructible T>
250 void Hopper<T>::Push(T item) noexcept {
251 m_impl->Push(std::move(item));
252 }
253
254 template<Type::MoveConstructible T>
255 Hopper<T>& Hopper<T>::operator<<(T item) noexcept {
256 Push(std::move(item));
257 return *this;
258 }
259
260 template<Type::MoveConstructible T>
261 void Hopper<T>::Eof() noexcept {
262 m_impl->Eof();
263 }
264
265 template<Type::MoveConstructible T>
266 void Hopper<T>::AddWriter() noexcept {
267 m_impl->AddWriter();
268 }
269
270 template<Type::MoveConstructible T>
271 void Hopper<T>::CloseWriter() noexcept {
272 m_impl->CloseWriter();
273 }
274
275 template<Type::MoveConstructible T>
276 T Hopper<T>::Pop() noexcept {
277 return m_impl->Pop();
278 }
279
280 template<Type::MoveConstructible T>
281 Hopper<T>& Hopper<T>::operator>>(T& item) noexcept {
282 item = Pop();
283 return *this;
284 }
285
286 template<Type::MoveConstructible T>
287 bool Hopper<T>::EoF() const noexcept {
288 return m_impl->EoF();
289 }
290
291 template<Type::MoveConstructible T>
292 bool Hopper<T>::Empty() const noexcept {
293 return m_impl->Empty();
294 }
295
296 template<Type::MoveConstructible T>
297 void Hopper<T>::Notify(std::condition_variable& wake) noexcept {
298 m_impl->Notify(wake);
299 }
300
301 template<Type::MoveConstructible T>
302 void Hopper<T>::Unnotify() noexcept {
303 m_impl->Unnotify();
304 }
305}