2 * Copyright (C) 2024-2026 David C. Manuelda (StormBytePP)
4 * This file is part of StormByte-Buffer.
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.
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.
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>.
22#include <StormByte/type_traits.hxx>
25#include <condition_variable>
35namespace StormByte::Buffer {
37 * @class Sink<T>::Implementation
38 * @brief Internal implementation class for Sink.
40 * Manages the map of key-to-Hopper buckets, thread synchronization,
41 * wiring condition variables, and pop selection algorithms.
43 template<Type::MoveConstructible T>
44 class Sink<T>::Implementation {
47 * @brief Constructs the Sink Implementation instance.
49 Implementation() noexcept
50 : m_rr(0), m_consumer(nullptr), m_closed(false), m_drain(false) {}
53 * @brief Destructor. Marks Sink closed and wakes any waiting threads on m_wired.
55 ~Implementation() noexcept {
56 m_closed.store(true, std::memory_order_release);
61 * @brief Enqueues an item into the hopper for key.
62 * @param key Bucket key identifier.
63 * @param item Item to push.
65 void Push(int key, T item) noexcept {
66 if constexpr (Type::NullablePointer<T>) {
70 std::shared_ptr<Hopper<T>> hopper;
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();
78 if (m_buckets.find(key) == m_buckets.end())
80 hopper = m_buckets[key];
84 hopper->Push(std::move(item));
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).
92 std::vector<std::shared_ptr<Hopper<T>>> Close(std::condition_variable*& cv) noexcept {
93 std::vector<std::shared_ptr<Hopper<T>>> writers;
95 std::lock_guard<std::mutex> lock(m_mutex);
96 const bool already = m_closed.exchange(true, std::memory_order_acq_rel);
98 for (auto& hopper : m_order) {
99 if (m_writers.contains(hopper))
100 writers.push_back(hopper);
103 m_wired.notify_all();
104 cv = m_consumer.load(std::memory_order_acquire);
110 * @brief Shares all existing hoppers with consumer.
111 * @param consumer Consumer Sink implementation reference.
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) {
123 consumer.m_buckets[key] = hopper;
126 consumer.m_closed.store(true, std::memory_order_release);
127 consumer.RebuildOrder();
128 consumer.m_wired.notify_all();
129 m_wired.notify_all();
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.
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);
147 consumer.m_writers.insert(hopper);
152 consumer.m_buckets[key] = hopper;
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;
162 * @brief Sets Drain mode.
164 void Drain() noexcept {
165 m_drain.store(true, std::memory_order_release);
166 m_wired.notify_all();
170 * @brief Checks if Drain was set.
171 * @return true if draining.
173 bool Draining() const noexcept {
174 return m_drain.load(std::memory_order_acquire);
178 * @brief Registers condition variable for consumer notifications.
179 * @param consumer Condition variable reference.
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);
189 * @brief Drops the consumer condition variable on this Sink and its hoppers.
191 void Unnotify() noexcept {
192 m_consumer.store(nullptr, std::memory_order_release);
193 const auto hoppers = Order();
194 for (auto& hopper : hoppers) {
201 * @brief Gets capacity of key hopper.
202 * @param key Bucket key.
203 * @return Capacity value.
205 std::size_t Capacity(int key) const noexcept {
206 const auto hopper = Bucket(key);
209 return hopper->Capacity();
213 * @brief Sets capacity of key hopper.
214 * @param key Bucket key.
215 * @param capacity New capacity.
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);
225 * @brief Gets pending item count of key hopper.
226 * @param key Bucket key.
227 * @return Item count.
229 std::size_t Size(int key) const noexcept {
230 const auto hopper = Bucket(key);
233 return hopper->Size();
237 * @brief Checks if key hopper is full.
238 * @param key Bucket key.
239 * @return true if full.
241 bool Full(int key) const noexcept {
242 const auto hopper = Bucket(key);
245 return hopper->Full();
249 * @brief Pops item using default selection.
250 * @return Popped item or default T.
253 return Pop(typename Sink<T>::Select{});
257 * @brief Pops item using specified selection function.
258 * @param select Bucket index chooser.
259 * @return Popped item or default T.
261 T Pop(const typename Sink<T>::Select& select) noexcept {
262 std::vector<std::shared_ptr<Hopper<T>>> hoppers;
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();
272 if (hoppers.size() == 1)
273 return hoppers.front()->Pop();
275 const std::size_t count = hoppers.size();
276 std::size_t start = 0;
278 start = select(count) % count;
280 start = m_rr.fetch_add(1, std::memory_order_relaxed) % count;
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();
291 * @brief Checks if Sink is finished.
292 * @return true if closed and all hoppers drained.
294 bool EoF() const noexcept {
295 const auto hoppers = Order();
297 return m_closed.load(std::memory_order_acquire);
298 for (const auto& hopper : hoppers) {
301 if (!hopper->Empty())
308 * @brief Checks if Pop can return immediately.
309 * @return true if item is ready or EoF reached.
311 bool Ready() const noexcept {
312 const auto hoppers = Order();
314 return m_closed.load(std::memory_order_acquire);
316 for (const auto& hopper : hoppers) {
317 if (!hopper->Empty())
327 * @brief Ensures hopper for key exists. Caller holds m_mutex.
328 * @param key Bucket key.
329 * @return Shared hopper instance.
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);
339 m_buckets.emplace(key, hopper);
340 m_writers.insert(hopper);
346 * @brief Rebuilds order vector from m_buckets. Caller holds m_mutex.
348 void RebuildOrder() {
350 m_order.reserve(m_buckets.size());
351 for (auto& [key, hopper] : m_buckets)
352 m_order.push_back(hopper);
356 * @brief Returns snapshot of current hoppers in order.
357 * @return Vector of hoppers.
359 std::vector<std::shared_ptr<Hopper<T>>> Order() const {
360 std::lock_guard<std::mutex> lock(m_mutex);
365 * @brief Retrieves hopper for key.
366 * @param key Bucket key.
367 * @return Hopper pointer or nullptr.
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())
374 return found->second;
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.
388 template<Type::MoveConstructible T>
389 Sink<T>::Sink() noexcept
390 : m_impl(std::make_unique<Implementation>()) {}
392 template<Type::MoveConstructible T>
393 Sink<T>::~Sink() noexcept = default;
395 template<Type::MoveConstructible T>
396 void Sink<T>::Push(int key, T item) noexcept {
397 m_impl->Push(key, std::move(item));
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();
410 template<Type::MoveConstructible T>
411 Sink<T>::Lane::Lane(Sink& from, int key) noexcept
412 : m_from(&from), m_key(key) {}
414 template<Type::MoveConstructible T>
415 typename Sink<T>::Lane Sink<T>::To(int key) noexcept {
416 return Lane(*this, key);
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))
426 template<Type::MoveConstructible T>
427 Sink<T>& Sink<T>::operator>>(Sink& dest) noexcept {
428 m_impl->Bind(*dest.m_impl);
432 template<Type::MoveConstructible T>
433 Sink<T>& Sink<T>::operator<<(Sink& src) noexcept {
438 template<Type::MoveConstructible T>
439 Sink<T>& Sink<T>::operator<<(Lane lane) noexcept {
444 template<Type::MoveConstructible T>
445 void Sink<T>::Bind(Sink& consumer) {
449 template<Type::MoveConstructible T>
450 void Sink<T>::Bind(int key, Sink& consumer) {
454 template<Type::MoveConstructible T>
455 void Sink<T>::Drain() noexcept {
459 template<Type::MoveConstructible T>
460 bool Sink<T>::Draining() const noexcept {
461 return m_impl->Draining();
464 template<Type::MoveConstructible T>
465 void Sink<T>::Notify(std::condition_variable& consumer) noexcept {
466 m_impl->Notify(consumer);
469 template<Type::MoveConstructible T>
470 void Sink<T>::Unnotify() noexcept {
474 template<Type::MoveConstructible T>
475 std::size_t Sink<T>::Capacity(int key) const noexcept {
476 return m_impl->Capacity(key);
479 template<Type::MoveConstructible T>
480 void Sink<T>::Capacity(int key, std::size_t capacity) noexcept {
481 m_impl->Capacity(key, capacity);
484 template<Type::MoveConstructible T>
485 std::size_t Sink<T>::Size(int key) const noexcept {
486 return m_impl->Size(key);
489 template<Type::MoveConstructible T>
490 bool Sink<T>::Full(int key) const noexcept {
491 return m_impl->Full(key);
494 template<Type::MoveConstructible T>
495 T Sink<T>::Pop() noexcept {
496 return m_impl->Pop();
499 template<Type::MoveConstructible T>
500 T Sink<T>::Pop(const Select& select) noexcept {
501 return m_impl->Pop(select);
504 template<Type::MoveConstructible T>
505 bool Sink<T>::EoF() const noexcept {
506 return m_impl->EoF();
509 template<Type::MoveConstructible T>
510 bool Sink<T>::Ready() const noexcept {
511 return m_impl->Ready();