StormByte C++ Library: Buffer module 1.2.0
StormByte-Buffer is the buffer module of the StormByte C++ suite.
Loading...
Searching...
No Matches
sink.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 <functional>
28#include <map>
29#include <memory>
30#include <mutex>
31#include <set>
32#include <utility>
33#include <vector>
34
35namespace StormByte::Buffer {
36 /**
37 * @class Sink<T>::Implementation
38 * @brief Internal implementation class for Sink.
39 *
40 * Manages the map of key-to-Hopper buckets, thread synchronization,
41 * wiring condition variables, and pop selection algorithms.
42 */
43 template<Type::MoveConstructible T>
44 class Sink<T>::Implementation {
45 public:
46 /**
47 * @brief Constructs the Sink Implementation instance.
48 */
49 Implementation() noexcept
50 : m_rr(0), m_consumer(nullptr), m_closed(false), m_drain(false) {}
51
52 /**
53 * @brief Destructor. Marks Sink closed and wakes any waiting threads on m_wired.
54 */
55 ~Implementation() noexcept {
56 m_closed.store(true, std::memory_order_release);
57 m_wired.notify_all();
58 }
59
60 /**
61 * @brief Enqueues an item into the hopper for key.
62 * @param key Bucket key identifier.
63 * @param item Item to push.
64 */
65 void Push(int key, T item) noexcept {
66 if constexpr (Type::NullablePointer<T>) {
67 if (!item)
68 return;
69 }
70 std::shared_ptr<Hopper<T>> hopper;
71 {
72 std::unique_lock<std::mutex> lock(m_mutex);
73 m_wired.wait(lock, [this, key] {
74 return m_closed.load(std::memory_order_acquire)
75 || m_drain.load(std::memory_order_acquire)
76 || m_buckets.find(key) != m_buckets.end();
77 });
78 if (m_buckets.find(key) == m_buckets.end())
79 return;
80 hopper = m_buckets[key];
81 }
82 if (!hopper)
83 return;
84 hopper->Push(std::move(item));
85 }
86
87 /**
88 * @brief Closes this Sink and returns hoppers it writes, for last-writer Eof.
89 * @param cv Set to the consumer condition variable, if any.
90 * @return Writer hoppers to CloseWriter (empty if already closed).
91 */
92 std::vector<std::shared_ptr<Hopper<T>>> Close(std::condition_variable*& cv) noexcept {
93 std::vector<std::shared_ptr<Hopper<T>>> writers;
94 {
95 std::lock_guard<std::mutex> lock(m_mutex);
96 const bool already = m_closed.exchange(true, std::memory_order_acq_rel);
97 if (!already) {
98 for (auto& hopper : m_order) {
99 if (m_writers.contains(hopper))
100 writers.push_back(hopper);
101 }
102 }
103 m_wired.notify_all();
104 cv = m_consumer.load(std::memory_order_acquire);
105 }
106 return writers;
107 }
108
109 /**
110 * @brief Shares all existing hoppers with consumer.
111 * @param consumer Consumer Sink implementation reference.
112 */
113 void Bind(Implementation& consumer) {
114 std::scoped_lock lock(m_mutex, consumer.m_mutex);
115 const bool closed = m_closed.load(std::memory_order_acquire)
116 || consumer.m_closed.load(std::memory_order_acquire);
117 std::condition_variable* cv = consumer.m_consumer.load(std::memory_order_acquire);
118 for (auto& [key, hopper] : m_buckets) {
119 if (closed)
120 hopper->Eof();
121 if (cv != nullptr)
122 hopper->Notify(*cv);
123 consumer.m_buckets[key] = hopper;
124 }
125 if (closed)
126 consumer.m_closed.store(true, std::memory_order_release);
127 consumer.RebuildOrder();
128 consumer.m_wired.notify_all();
129 m_wired.notify_all();
130 }
131
132 /**
133 * @brief Creates or shares the hopper for key with consumer.
134 * @param key Bucket key.
135 * @param consumer Consumer Sink implementation reference.
136 * @return Hopper when this Sink already held it (extra writer); empty otherwise.
137 */
138 std::shared_ptr<Hopper<T>> Bind(int key, Implementation& consumer) {
139 std::scoped_lock lock(m_mutex, consumer.m_mutex);
140 const bool closed = m_closed.load(std::memory_order_acquire)
141 || consumer.m_closed.load(std::memory_order_acquire);
142 std::condition_variable* cv = consumer.m_consumer.load(std::memory_order_acquire);
143 const bool existed = m_buckets.contains(key);
144 auto hopper = Ensure(key);
145 const bool already_writer = existed && consumer.m_writers.contains(hopper);
146 if (existed)
147 consumer.m_writers.insert(hopper);
148 if (closed)
149 hopper->Eof();
150 if (cv != nullptr)
151 hopper->Notify(*cv);
152 consumer.m_buckets[key] = hopper;
153 if (closed)
154 consumer.m_closed.store(true, std::memory_order_release);
155 consumer.RebuildOrder();
156 consumer.m_wired.notify_all();
157 m_wired.notify_all();
158 return (existed && !already_writer) ? hopper : nullptr;
159 }
160
161 /**
162 * @brief Sets Drain mode.
163 */
164 void Drain() noexcept {
165 m_drain.store(true, std::memory_order_release);
166 m_wired.notify_all();
167 }
168
169 /**
170 * @brief Checks if Drain was set.
171 * @return true if draining.
172 */
173 bool Draining() const noexcept {
174 return m_drain.load(std::memory_order_acquire);
175 }
176
177 /**
178 * @brief Registers condition variable for consumer notifications.
179 * @param consumer Condition variable reference.
180 */
181 void Notify(std::condition_variable& consumer) noexcept {
182 m_consumer.store(&consumer, std::memory_order_release);
183 const auto hoppers = Order();
184 for (auto& hopper : hoppers)
185 hopper->Notify(consumer);
186 }
187
188 /**
189 * @brief Drops the consumer condition variable on this Sink and its hoppers.
190 */
191 void Unnotify() noexcept {
192 m_consumer.store(nullptr, std::memory_order_release);
193 const auto hoppers = Order();
194 for (auto& hopper : hoppers) {
195 if (hopper)
196 hopper->Unnotify();
197 }
198 }
199
200 /**
201 * @brief Gets capacity of key hopper.
202 * @param key Bucket key.
203 * @return Capacity value.
204 */
205 std::size_t Capacity(int key) const noexcept {
206 const auto hopper = Bucket(key);
207 if (!hopper)
208 return 0;
209 return hopper->Capacity();
210 }
211
212 /**
213 * @brief Sets capacity of key hopper.
214 * @param key Bucket key.
215 * @param capacity New capacity.
216 */
217 void Capacity(int key, std::size_t capacity) noexcept {
218 std::lock_guard<std::mutex> lock(m_mutex);
219 auto found = m_buckets.find(key);
220 if (found != m_buckets.end() && found->second)
221 found->second->Capacity(capacity);
222 }
223
224 /**
225 * @brief Gets pending item count of key hopper.
226 * @param key Bucket key.
227 * @return Item count.
228 */
229 std::size_t Size(int key) const noexcept {
230 const auto hopper = Bucket(key);
231 if (!hopper)
232 return 0;
233 return hopper->Size();
234 }
235
236 /**
237 * @brief Checks if key hopper is full.
238 * @param key Bucket key.
239 * @return true if full.
240 */
241 bool Full(int key) const noexcept {
242 const auto hopper = Bucket(key);
243 if (!hopper)
244 return false;
245 return hopper->Full();
246 }
247
248 /**
249 * @brief Pops item using default selection.
250 * @return Popped item or default T.
251 */
252 T Pop() noexcept {
253 return Pop(typename Sink<T>::Select{});
254 }
255
256 /**
257 * @brief Pops item using specified selection function.
258 * @param select Bucket index chooser.
259 * @return Popped item or default T.
260 */
261 T Pop(const typename Sink<T>::Select& select) noexcept {
262 std::vector<std::shared_ptr<Hopper<T>>> hoppers;
263 {
264 std::unique_lock<std::mutex> lock(m_mutex);
265 m_wired.wait(lock, [this] {
266 return m_closed.load(std::memory_order_acquire) || !m_order.empty();
267 });
268 hoppers = m_order;
269 }
270 if (hoppers.empty())
271 return T{};
272 if (hoppers.size() == 1)
273 return hoppers.front()->Pop();
274
275 const std::size_t count = hoppers.size();
276 std::size_t start = 0;
277 if (select)
278 start = select(count) % count;
279 else
280 start = m_rr.fetch_add(1, std::memory_order_relaxed) % count;
281
282 for (std::size_t offset = 0; offset < count; ++offset) {
283 auto& hopper = hoppers[(start + offset) % count];
284 if (!hopper->Empty())
285 return hopper->Pop();
286 }
287 return T{};
288 }
289
290 /**
291 * @brief Checks if Sink is finished.
292 * @return true if closed and all hoppers drained.
293 */
294 bool EoF() const noexcept {
295 const auto hoppers = Order();
296 if (hoppers.empty())
297 return m_closed.load(std::memory_order_acquire);
298 for (const auto& hopper : hoppers) {
299 if (!hopper->EoF())
300 return false;
301 if (!hopper->Empty())
302 return false;
303 }
304 return true;
305 }
306
307 /**
308 * @brief Checks if Pop can return immediately.
309 * @return true if item is ready or EoF reached.
310 */
311 bool Ready() const noexcept {
312 const auto hoppers = Order();
313 if (hoppers.empty())
314 return m_closed.load(std::memory_order_acquire);
315 bool drained = true;
316 for (const auto& hopper : hoppers) {
317 if (!hopper->Empty())
318 return true;
319 if (!hopper->EoF())
320 drained = false;
321 }
322 return drained;
323 }
324
325 private:
326 /**
327 * @brief Ensures hopper for key exists. Caller holds m_mutex.
328 * @param key Bucket key.
329 * @return Shared hopper instance.
330 */
331 std::shared_ptr<Hopper<T>> Ensure(int key) {
332 auto found = m_buckets.find(key);
333 if (found != m_buckets.end())
334 return found->second;
335 auto hopper = std::make_shared<Hopper<T>>();
336 std::condition_variable* cv = m_consumer.load(std::memory_order_acquire);
337 if (cv != nullptr)
338 hopper->Notify(*cv);
339 m_buckets.emplace(key, hopper);
340 m_writers.insert(hopper);
341 RebuildOrder();
342 return hopper;
343 }
344
345 /**
346 * @brief Rebuilds order vector from m_buckets. Caller holds m_mutex.
347 */
348 void RebuildOrder() {
349 m_order.clear();
350 m_order.reserve(m_buckets.size());
351 for (auto& [key, hopper] : m_buckets)
352 m_order.push_back(hopper);
353 }
354
355 /**
356 * @brief Returns snapshot of current hoppers in order.
357 * @return Vector of hoppers.
358 */
359 std::vector<std::shared_ptr<Hopper<T>>> Order() const {
360 std::lock_guard<std::mutex> lock(m_mutex);
361 return m_order;
362 }
363
364 /**
365 * @brief Retrieves hopper for key.
366 * @param key Bucket key.
367 * @return Hopper pointer or nullptr.
368 */
369 std::shared_ptr<Hopper<T>> Bucket(int key) const {
370 std::lock_guard<std::mutex> lock(m_mutex);
371 auto found = m_buckets.find(key);
372 if (found == m_buckets.end())
373 return nullptr;
374 return found->second;
375 }
376
377 mutable std::mutex m_mutex; ///< Guards bucket map and order vector.
378 std::condition_variable m_wired; ///< Waits for bucket binding or closure.
379 std::map<int, std::shared_ptr<Hopper<T>>> m_buckets;///< Map of integer keys to Hopper buckets.
380 std::set<std::shared_ptr<Hopper<T>>> m_writers; ///< Hoppers this Sink writes (CloseWriter on Eof).
381 std::vector<std::shared_ptr<Hopper<T>>> m_order; ///< Order vector of hoppers for Pop.
382 std::atomic<std::size_t> m_rr; ///< Round-robin counter.
383 std::atomic<std::condition_variable*> m_consumer; ///< Registered consumer condition variable.
384 std::atomic<bool> m_closed; ///< Closed flag.
385 std::atomic<bool> m_drain; ///< Drain mode flag.
386 };
387
388 template<Type::MoveConstructible T>
389 Sink<T>::Sink() noexcept
390 : m_impl(std::make_unique<Implementation>()) {}
391
392 template<Type::MoveConstructible T>
393 Sink<T>::~Sink() noexcept = default;
394
395 template<Type::MoveConstructible T>
396 void Sink<T>::Push(int key, T item) noexcept {
397 m_impl->Push(key, std::move(item));
398 }
399
400 template<Type::MoveConstructible T>
401 void Sink<T>::Eof() noexcept {
402 std::condition_variable* cv = nullptr;
403 const auto writers = m_impl->Close(cv);
404 for (const auto& hopper : writers)
405 hopper->CloseWriter();
406 if (cv)
407 cv->notify_all();
408 }
409
410 template<Type::MoveConstructible T>
411 Sink<T>::Lane::Lane(Sink& from, int key) noexcept
412 : m_from(&from), m_key(key) {}
413
414 template<Type::MoveConstructible T>
415 typename Sink<T>::Lane Sink<T>::To(int key) noexcept {
416 return Lane(*this, key);
417 }
418
419 template<Type::MoveConstructible T>
420 Sink<T>& Sink<T>::Lane::operator>>(Sink& dest) noexcept {
421 if (auto extra = m_from->m_impl->Bind(m_key, *dest.m_impl))
422 extra->AddWriter();
423 return dest;
424 }
425
426 template<Type::MoveConstructible T>
427 Sink<T>& Sink<T>::operator>>(Sink& dest) noexcept {
428 m_impl->Bind(*dest.m_impl);
429 return dest;
430 }
431
432 template<Type::MoveConstructible T>
433 Sink<T>& Sink<T>::operator<<(Sink& src) noexcept {
434 src >> *this;
435 return *this;
436 }
437
438 template<Type::MoveConstructible T>
439 Sink<T>& Sink<T>::operator<<(Lane lane) noexcept {
440 lane >> *this;
441 return *this;
442 }
443
444 template<Type::MoveConstructible T>
445 void Sink<T>::Bind(Sink& consumer) {
446 *this >> consumer;
447 }
448
449 template<Type::MoveConstructible T>
450 void Sink<T>::Bind(int key, Sink& consumer) {
451 To(key) >> consumer;
452 }
453
454 template<Type::MoveConstructible T>
455 void Sink<T>::Drain() noexcept {
456 m_impl->Drain();
457 }
458
459 template<Type::MoveConstructible T>
460 bool Sink<T>::Draining() const noexcept {
461 return m_impl->Draining();
462 }
463
464 template<Type::MoveConstructible T>
465 void Sink<T>::Notify(std::condition_variable& consumer) noexcept {
466 m_impl->Notify(consumer);
467 }
468
469 template<Type::MoveConstructible T>
470 void Sink<T>::Unnotify() noexcept {
471 m_impl->Unnotify();
472 }
473
474 template<Type::MoveConstructible T>
475 std::size_t Sink<T>::Capacity(int key) const noexcept {
476 return m_impl->Capacity(key);
477 }
478
479 template<Type::MoveConstructible T>
480 void Sink<T>::Capacity(int key, std::size_t capacity) noexcept {
481 m_impl->Capacity(key, capacity);
482 }
483
484 template<Type::MoveConstructible T>
485 std::size_t Sink<T>::Size(int key) const noexcept {
486 return m_impl->Size(key);
487 }
488
489 template<Type::MoveConstructible T>
490 bool Sink<T>::Full(int key) const noexcept {
491 return m_impl->Full(key);
492 }
493
494 template<Type::MoveConstructible T>
495 T Sink<T>::Pop() noexcept {
496 return m_impl->Pop();
497 }
498
499 template<Type::MoveConstructible T>
500 T Sink<T>::Pop(const Select& select) noexcept {
501 return m_impl->Pop(select);
502 }
503
504 template<Type::MoveConstructible T>
505 bool Sink<T>::EoF() const noexcept {
506 return m_impl->EoF();
507 }
508
509 template<Type::MoveConstructible T>
510 bool Sink<T>::Ready() const noexcept {
511 return m_impl->Ready();
512 }
513}