libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
batch_unbatch.hpp
1/*
2 * This file is part of libtcspc
3 * Copyright 2019-2026 Board of Regents of the University of Wisconsin System
4 * SPDX-License-Identifier: MIT
5 */
6
7#pragma once
8
9#include "arg_wrappers.hpp"
10#include "bucket.hpp"
11#include "common.hpp"
12#include "introspect.hpp"
13#include "processor.hpp"
14
15#include <cstddef>
16#include <iterator>
17#include <memory>
18#include <stdexcept>
19#include <type_traits>
20#include <utility>
21
22namespace tcspc {
23
24namespace internal {
25
26template <typename Event, typename Downstream>
27 requires processor<Downstream, bucket<Event>>
28class batch {
29 static_assert(
30 std::is_default_constructible_v<Event>,
31 "batch requires Event to be default-constructible (buckets are "
32 "pre-filled with default-constructed events)");
33
34 std::shared_ptr<bucket_source<Event>> bsource;
35 std::size_t bsize;
36
37 bucket<Event> cur_bucket;
38 std::size_t n_filled = 0;
39
40 Downstream downstream;
41
42 public:
43 explicit batch(std::shared_ptr<bucket_source<Event>> buffer_provider,
44 arg::batch_size<std::size_t> batch_size,
45 Downstream downstream)
46 : bsource(std::move(buffer_provider)), bsize(batch_size.value),
47 downstream(std::move(downstream)) {
48 if (bsize == 0)
49 throw std::invalid_argument(
50 "batch processor batch_size must not be zero");
51 }
52
53 [[nodiscard]] auto introspect_node() const -> processor_info {
54 return processor_info(this, "batch");
55 }
56
57 [[nodiscard]] auto introspect_graph() const -> processor_graph {
58 return downstream.introspect_graph().push_entry_point(this);
59 }
60
61 template <typename E>
62 requires std::convertible_to<std::remove_cvref_t<E>, Event>
63 void handle(E &&event) {
64 if (cur_bucket.empty())
65 cur_bucket = bsource->bucket_of_size(bsize);
66
67 cur_bucket[n_filled] = std::forward<E>(event);
68 ++n_filled;
69
70 if (n_filled == bsize) {
71 downstream.handle(std::move(cur_bucket));
72 cur_bucket = {};
73 n_filled = 0;
74 }
75 }
76
77 void flush() {
78 if (n_filled > 0) {
79 cur_bucket.shrink(0, n_filled);
80 downstream.handle(std::move(cur_bucket));
81 }
82 downstream.flush();
83 }
84};
85
86template <typename ContainerEvent>
87 requires std::is_same_v<typename std::iterator_traits<
88 decltype(std::declval<ContainerEvent>()
89 .begin())>::value_type,
90 typename std::iterator_traits<
91 decltype(std::declval<ContainerEvent>()
92 .end())>::value_type>
93using unbatch_element_t = std::iterator_traits<
94 decltype(std::declval<ContainerEvent>().end())>::value_type;
95
96template <typename ContainerEvent, typename Downstream>
97 requires processor<Downstream, unbatch_element_t<ContainerEvent>>
98class unbatch {
99 Downstream downstream;
100
101 public:
102 explicit unbatch(Downstream downstream)
103 : downstream(std::move(downstream)) {}
104
105 [[nodiscard]] auto introspect_node() const -> processor_info {
106 return processor_info(this, "unbatch");
107 }
108
109 [[nodiscard]] auto introspect_graph() const -> processor_graph {
110 return downstream.introspect_graph().push_entry_point(this);
111 }
112
113 // Should we mark this LIBTCSPC_NOINLINE? It would be good to increase the
114 // chances that the downstream call will be inlined. But preliminary tests
115 // (Apple clang 14 arm64) suggest that when the downstream is simple enough
116 // to inline, it will be inlined, together with this loop, into upstream;
117 // conversely, if the downstream is too complex to inline, it won't be
118 // inlined even if this function is marked noinline. There may be
119 // borderline cases where this doesn't hold, but it is probably best to
120 // leave it to the compiler.
121 template <typename E>
122 requires std::convertible_to<std::remove_cvref_t<E>, ContainerEvent>
123 void handle(E &&event) {
124 if constexpr (std::is_lvalue_reference_v<E>) {
125 for (auto const &e : event)
126 downstream.handle(e);
127 } else {
128 for (auto &e : event)
129 downstream.handle(std::move(e));
130 }
131 }
132
133 template <typename E>
134 requires(
135 not std::convertible_to<std::remove_cvref_t<E>, ContainerEvent> and
136 handler_for<Downstream, std::remove_cvref_t<E>>)
137 void handle(E &&event) {
138 downstream.handle(std::forward<E>(event));
139 }
140
141 void flush() { downstream.flush(); }
142};
143
144} // namespace internal
145
180template <typename Event, typename Downstream>
181auto batch(std::shared_ptr<bucket_source<Event>> buffer_provider,
182 arg::batch_size<std::size_t> batch_size, Downstream downstream) {
183 return internal::batch<Event, Downstream>(
184 std::move(buffer_provider), batch_size, std::move(downstream));
185}
186
210template <typename ContainerEvent, typename Downstream>
211auto unbatch(Downstream downstream) {
212 return internal::unbatch<ContainerEvent, Downstream>(
213 std::move(downstream));
214}
215
250template <typename Event, typename Downstream>
252 Downstream downstream) {
253 return batch<Event>(
255 batch_size, unbatch<bucket<Event>>(std::move(downstream)));
256}
257
258} // namespace tcspc
static auto create(arg::max_bucket_count<> max_bucket_count=arg::max_bucket_count{std::numeric_limits< std::size_t >::max()}, arg::max_recycled_size<> max_recycled_size=arg::max_recycled_size<>{ 0}) -> std::shared_ptr< bucket_source< T > >
Create an instance.
Definition bucket.hpp:753
auto unbatch(Downstream downstream)
Create a processor transforming batches of events to individual events.
Definition batch_unbatch.hpp:211
auto batch(std::shared_ptr< bucket_source< Event > > buffer_provider, arg::batch_size< std::size_t > batch_size, Downstream downstream)
Create a processor that batches events into buckets for buffering.
Definition batch_unbatch.hpp:181
auto process_in_batches(arg::batch_size< std::size_t > batch_size, Downstream downstream)
Create a processor that buffers events up to equally sized batches and passes them downstream in a ti...
Definition batch_unbatch.hpp:251
libtcspc namespace.
Definition acquire.hpp:30
Function argument wrapper for batch size parameter.
Definition arg_wrappers.hpp:47
Function argument wrapper for maximum bucket count.
Definition arg_wrappers.hpp:227
Abstract base class for polymorphic bucket sources.
Definition bucket.hpp:504