libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
batch_unbatch_from_bytes.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 "bucket.hpp"
10#include "common.hpp"
11#include "introspect.hpp"
12#include "processor.hpp"
13
14#include <algorithm>
15#include <array>
16#include <concepts>
17#include <cstddef>
18#include <memory>
19#include <span>
20#include <stdexcept>
21#include <type_traits>
22#include <utility>
23
24namespace tcspc {
25
26namespace internal {
27
28template <typename Event, typename Downstream>
29 requires processor<Downstream, bucket<Event>>
30class batch_from_bytes {
31 static_assert(std::is_trivial_v<Event>,
32 "batch_from_bytes requires Event to be a trivial type "
33 "(events are constructed by copying raw bytes)");
34
35 std::shared_ptr<bucket_source<Event>> bsource;
36
37 std::size_t bytes_buffered = 0; // < buf.size()
38 std::array<std::byte, sizeof(Event)> buf;
39
40 Downstream downstream;
41
42 public:
43 explicit batch_from_bytes(
44 std::shared_ptr<bucket_source<Event>> buffer_provider,
45 Downstream downstream)
46 : bsource(std::move(buffer_provider)),
47 downstream(std::move(downstream)) {}
48
49 [[nodiscard]] auto introspect_node() const -> processor_info {
50 return processor_info(this, "batch_from_bytes");
51 }
52
53 [[nodiscard]] auto introspect_graph() const -> processor_graph {
54 return downstream.introspect_graph().push_entry_point(this);
55 }
56
57 template <typename ByteSpan>
58 requires std::constructible_from<std::span<std::byte const>,
59 ByteSpan const &>
60 void handle(ByteSpan const &event) {
61 auto input_span = std::span<std::byte const>(event);
62 auto const bytes_available = bytes_buffered + input_span.size();
63 if (bytes_available < sizeof(Event)) {
64 std::copy(input_span.begin(), input_span.end(),
65 std::span(buf).subspan(bytes_buffered).begin());
66 bytes_buffered = bytes_available;
67 return;
68 }
69
70 auto const batch_size = bytes_available / sizeof(Event);
71 auto bucket = bsource->bucket_of_size(batch_size);
72 auto const output_span = std::as_writable_bytes(std::span(bucket));
73 auto const input_bulk =
74 input_span.first(output_span.size() - bytes_buffered);
75 auto const remainder = input_span.subspan(input_bulk.size());
76 auto const output_bulk = output_span.subspan(bytes_buffered);
77
78 std::copy_n(buf.begin(), bytes_buffered, output_span.begin());
79 std::copy(input_bulk.begin(), input_bulk.end(), output_bulk.begin());
80 std::copy(remainder.begin(), remainder.end(), buf.begin());
81 bytes_buffered = remainder.size();
82
83 downstream.handle(std::move(bucket));
84 }
85
86 void flush() {
87 if (bytes_buffered > 0)
88 throw std::runtime_error("excess bytes at end of stream");
89 downstream.flush();
90 }
91};
92
93template <typename Event, typename Downstream>
94 requires processor<Downstream, Event>
95class unbatch_from_bytes {
96 static_assert(std::is_trivial_v<Event>,
97 "unbatch_from_bytes requires Event to be a trivial type "
98 "(events are constructed by copying raw bytes)");
99
100 std::size_t bytes_buffered = 0; // < sizeof(buf)
101 std::array<std::byte, sizeof(Event)> buf;
102
103 Downstream downstream;
104
105 public:
106 explicit unbatch_from_bytes(Downstream downstream)
107 : downstream(std::move(downstream)) {}
108
109 [[nodiscard]] auto introspect_node() const -> processor_info {
110 return processor_info(this, "unbatch_from_bytes");
111 }
112
113 [[nodiscard]] auto introspect_graph() const -> processor_graph {
114 return downstream.introspect_graph().push_entry_point(this);
115 }
116
117 template <typename ByteSpan>
118 requires std::constructible_from<std::span<std::byte const>,
119 ByteSpan const &>
120 void handle(ByteSpan const &event) {
121 auto input_span = std::span<std::byte const>(event);
122 if (bytes_buffered > 0) {
123 auto const available_bytes = bytes_buffered + input_span.size();
124 if (available_bytes < sizeof(Event)) {
125 std::copy(input_span.begin(), input_span.end(),
126 std::span(buf).subspan(bytes_buffered).begin());
127 bytes_buffered = available_bytes;
128 return;
129 }
130 Event e;
131 auto const output_bytes = std::as_writable_bytes(std::span(&e, 1));
132 auto const bytes_to_fill = sizeof(Event) - bytes_buffered;
133 std::copy_n(buf.begin(), bytes_buffered, output_bytes.begin());
134 std::copy_n(input_span.begin(), bytes_to_fill,
135 output_bytes.subspan(bytes_buffered).begin());
136 downstream.handle(std::as_const(e));
137 input_span = input_span.subspan(bytes_to_fill);
138 }
139
140 auto const n_whole = input_span.size() / sizeof(Event);
141 auto const whole_event_bytes =
142 input_span.first(n_whole * sizeof(Event));
143 auto const remainder = input_span.subspan(whole_event_bytes.size());
144
145 if (is_aligned<Event>(input_span.data())) {
146 auto const *ptr =
147 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
148 reinterpret_cast<Event const *>(input_span.data());
149 for (Event const &e : std::span(ptr, n_whole))
150 downstream.handle(e);
151 } else {
152 for (std::size_t i = 0; i < whole_event_bytes.size();
153 i += sizeof(Event)) {
154 Event e;
155 std::copy_n(whole_event_bytes.subspan(i).begin(),
156 sizeof(Event),
157 std::as_writable_bytes(std::span(&e, 1)).begin());
158 downstream.handle(std::as_const(e));
159 }
160 }
161
162 std::copy(remainder.begin(), remainder.end(), buf.begin());
163 bytes_buffered = remainder.size();
164 }
165
166 void flush() {
167 if (bytes_buffered > 0)
168 throw std::runtime_error("excess bytes at end of stream");
169 downstream.flush();
170 }
171};
172
173} // namespace internal
174
210template <typename Event, typename Downstream>
211auto batch_from_bytes(std::shared_ptr<bucket_source<Event>> buffer_provider,
212 Downstream downstream) {
213 return internal::batch_from_bytes<Event, Downstream>(
214 std::move(buffer_provider), std::move(downstream));
215}
216
247template <typename Event, typename Downstream>
248auto unbatch_from_bytes(Downstream downstream) {
249 return internal::unbatch_from_bytes<Event, Downstream>(
250 std::move(downstream));
251}
252
253} // namespace tcspc
auto batch_from_bytes(std::shared_ptr< bucket_source< Event > > buffer_provider, Downstream downstream)
Create a processor that converts batches of bytes into batches of events.
Definition batch_unbatch_from_bytes.hpp:211
auto unbatch_from_bytes(Downstream downstream)
Create a processor that converts batches of bytes into individual events.
Definition batch_unbatch_from_bytes.hpp:248
libtcspc namespace.
Definition acquire.hpp:30
Abstract base class for polymorphic bucket sources.
Definition bucket.hpp:504