9#include "arg_wrappers.hpp"
13#include "introspect.hpp"
14#include "processor.hpp"
15#include "vector_queue.hpp"
20#include <condition_variable>
37#if (defined(__APPLE__) && defined(__arm64__)) || \
38 (defined(__powerpc64__) || defined(__ppc64__) || defined(__PPC64__))
39inline constexpr std::size_t destructive_interference_size = 128;
41inline constexpr std::size_t destructive_interference_size = 64;
52class buffer_accessor {
53 std::function<void()> halt_fn;
54 std::function<void()> pump_fn;
58 template <
typename HaltFunc,
typename PumpFunc>
59 explicit buffer_accessor(HaltFunc halt_func, PumpFunc pump_func)
60 : halt_fn(halt_func), pump_fn(pump_func) {}
84 void halt() noexcept { halt_fn(); }
114template <
typename Event,
bool LatencyLimited,
typename Downstream>
115 requires processor<Downstream, Event>
117 static_assert(std::move_constructible<Event>,
118 "buffer requires Event to be move-constructible (events are "
119 "stored in an internal queue)");
121 using clock_type = std::chrono::steady_clock;
122 using queue_type = vector_queue<Event>;
124 std::size_t threshold;
125 clock_type::duration max_latency;
128 std::condition_variable has_data_condition;
129 queue_type shared_queue;
130 clock_type::time_point oldest_enqueued_time;
131 bool upstream_flushed =
false;
132 bool upstream_halted =
false;
133 bool downstream_threw =
false;
137#pragma warning(disable : 4324)
150 alignas(destructive_interference_size) queue_type emit_queue;
156 Downstream downstream;
160 access_tracker<buffer_accessor> trk;
162 void halt() noexcept {
164 auto const lock = std::lock_guard(mutex);
165 upstream_halted =
true;
167 has_data_condition.notify_one();
172 auto lock = std::unique_lock(mutex);
174 throw std::logic_error(
175 "buffer may not be pumped a second time");
180 if constexpr (LatencyLimited) {
181 has_data_condition.wait(lock, [&] {
182 return not shared_queue.empty() || upstream_flushed ||
186 auto const deadline = oldest_enqueued_time + max_latency;
187 has_data_condition.wait_until(lock, deadline, [&] {
188 return shared_queue.size() >= threshold ||
189 upstream_flushed || upstream_halted;
192 has_data_condition.wait(lock, [&] {
193 return shared_queue.size() >= threshold ||
194 upstream_flushed || upstream_halted;
198 if (not upstream_flushed && upstream_halted)
199 throw source_halted();
200 if (shared_queue.empty() && upstream_flushed) {
202 return downstream.flush();
205 emit_queue.swap(shared_queue);
207 while (!emit_queue.empty()) {
208 downstream.handle(std::move(emit_queue.front()));
213 }
catch (source_halted
const &) {
216 auto const lock = std::lock_guard(mutex);
217 downstream_threw =
true;
223 template <
typename Rep,
typename Period>
224 explicit buffer(arg::threshold<std::size_t> threshold,
225 std::chrono::duration<Rep, Period> latency_limit,
226 access_tracker<buffer_accessor> &&tracker,
227 Downstream downstream)
228 : threshold(threshold.value >= 0 ? threshold.value : 1),
230 std::chrono::duration_cast<clock_type::duration>(latency_limit)),
231 downstream(std::move(downstream)), trk(std::move(tracker)) {
233 if (max_latency > std::chrono::hours(24)) {
234 throw std::invalid_argument(
235 "buffer latency limit must not be greater than 24 h");
238 trk.register_accessor_factory([](
auto &tracker) {
240 return buffer_accessor([self] { self->halt(); },
241 [self] { self->pump(); });
246 explicit buffer(arg::threshold<std::size_t> threshold,
247 access_tracker<buffer_accessor> &&tracker,
248 Downstream downstream)
249 : buffer(threshold, std::chrono::hours(24), std::move(tracker),
250 std::move(downstream)) {}
257 buffer(buffer
const &) =
delete;
258 auto operator=(buffer
const &) =
delete;
260 buffer(buffer &&other) noexcept
261 : threshold(other.threshold), max_latency(other.max_latency),
262 shared_queue(std::move(other.shared_queue)),
263 oldest_enqueued_time(other.oldest_enqueued_time),
264 upstream_flushed(other.upstream_flushed),
265 upstream_halted(other.upstream_halted),
266 downstream_threw(other.downstream_threw),
267 emit_queue(std::move(other.emit_queue)),
268 downstream(std::move(other.downstream)), pumped(other.pumped),
269 trk(std::move(other.trk)) {}
271 auto operator=(buffer &&) =
delete;
273 [[nodiscard]]
auto introspect_node() const -> processor_info {
274 return processor_info(
this,
"buffer");
277 [[nodiscard]]
auto introspect_graph() const -> processor_graph {
278 return downstream.introspect_graph().push_entry_point(
this);
281 template <
typename E>
282 requires std::convertible_to<std::remove_cvref_t<E>, Event>
283 void handle(E &&event) {
284 bool should_notify{};
286 auto const lock = std::lock_guard(mutex);
287 if (downstream_threw)
288 throw end_of_processing(
289 "ending upstream of buffer upon end of downstream processing");
291 shared_queue.push(std::forward<E>(event));
292 should_notify = shared_queue.size() == threshold;
293 if constexpr (LatencyLimited) {
294 if (shared_queue.size() == 1) {
295 oldest_enqueued_time = clock_type::now();
296 should_notify =
true;
301 has_data_condition.notify_one();
306 auto const lock = std::lock_guard(mutex);
307 if (downstream_threw)
308 throw end_of_processing(
309 "ending upstream of buffer upon end of downstream processing");
310 upstream_flushed =
true;
312 has_data_condition.notify_one();
365template <
typename Event,
typename Downstream>
368 return internal::buffer<Event, false, Downstream>(
369 threshold, std::move(tracker), std::move(downstream));
428template <
typename Event,
typename Rep,
typename Period,
typename Downstream>
430 std::chrono::duration<Rep, Period> latency_limit,
432 Downstream downstream) {
433 return internal::buffer<Event, true, Downstream>(
434 threshold, latency_limit, std::move(tracker), std::move(downstream));
Tracker that mediates access to objects via a tcspc::context.
Definition context.hpp:39
void pump()
Pump buffered events downstream.
Definition buffer.hpp:109
void halt() noexcept
Halt pumping of the buffer.
Definition buffer.hpp:84
#define LIBTCSPC_OBJECT_FROM_TRACKER(obj_type, tracker_field_name, tracker)
Recover the object address from a tcspc::access_tracker embedded in the object.
Definition context.hpp:255
auto buffer(arg::threshold< std::size_t > threshold, access_tracker< buffer_accessor > &&tracker, Downstream downstream)
Create a processor that buffers events and emits them on a different thread.
Definition buffer.hpp:366
auto real_time_buffer(arg::threshold< std::size_t > threshold, std::chrono::duration< Rep, Period > latency_limit, access_tracker< buffer_accessor > &&tracker, Downstream downstream)
Create a processor that buffers events and emits them on a different thread, with limited latency.
Definition buffer.hpp:429
libtcspc namespace.
Definition acquire.hpp:30
Function argument wrapper for threshold parameter.
Definition arg_wrappers.hpp:397