libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
route.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 "common.hpp"
10#include "errors.hpp"
11#include "int_arith.hpp"
12#include "introspect.hpp"
13#include "numeric_traits.hpp"
14#include "processor.hpp"
15#include "type_erased_processor.hpp"
16#include "type_list.hpp"
17
18#include <algorithm>
19#include <array>
20#include <concepts>
21#include <cstddef>
22#include <exception>
23#include <functional>
24#include <limits>
25#include <numeric>
26#include <type_traits>
27#include <utility>
28
29namespace tcspc {
30
41template <typename R, typename... Events>
42concept router_for = std::move_constructible<R> &&
43 (... && requires(R const &r, Events const &e) {
44 { r(e) } -> std::same_as<std::size_t>;
45 });
46
47namespace internal {
48
49// Design note: Currently the router produces a single downstream index per
50// event. We could generalize this so that the router produces a boolean mask
51// of the downstreams, such that a single event can be routed to multiple
52// downstreams. But let's keep it simple. If necessary, a "multiroute"
53// processor can be added.
54
55template <typename Router, typename EventList> struct router_for_each_event;
56
57template <typename Router, typename... Events>
58struct router_for_each_event<Router, type_list<Events...>>
59 : std::bool_constant<router_for<Router, Events...>> {};
60
61// We do not require Downstream to handle all of RoutedEventList.
62template <typename RoutedEventList, typename Router, std::size_t N,
63 typename Downstream>
64 requires type_list_like<RoutedEventList> && processor<Downstream> &&
65 router_for_each_event<Router, RoutedEventList>::value
67 Router router;
68 std::array<Downstream, N> downstreams;
69
70 LIBTCSPC_NOINLINE void flush_all_but(Downstream &excluded) {
71 for (auto &d : downstreams) {
72 if (&d != &excluded) {
73 try {
74 d.flush();
75 } catch (end_of_processing const &) {
76 ;
77 }
78 }
79 }
80 }
81
82 public:
83 explicit route_homogeneous(Router router,
84 std::array<Downstream, N> downstreams)
85 : router(std::move(router)), downstreams(std::move(downstreams)) {}
86
87 [[nodiscard]] auto introspect_node() const -> processor_info {
88 return processor_info(this, "route_homogeneous");
89 }
90
91 [[nodiscard]] auto introspect_graph() const -> processor_graph {
92 return std::transform_reduce(
93 downstreams.begin(), downstreams.end(), processor_graph(),
94 merge_processor_graphs, [this](auto const &d) {
95 return d.introspect_graph().push_entry_point(this);
96 });
97 }
98
99 template <typename Event>
100 requires(convertible_to_type_list_member<std::remove_cvref_t<Event>,
101 RoutedEventList> and
102 handler_for<Downstream, std::remove_cvref_t<Event>>)
103 void handle(Event &&event) {
104 std::size_t index = router(std::as_const(event));
105 if (index >= N)
106 return;
107 try {
108 downstreams[index].handle(std::forward<Event>(event));
109 } catch (end_of_processing const &) {
110 flush_all_but(downstreams[index]);
111 throw;
112 }
113 }
114
115 template <typename Event>
116 requires(not convertible_to_type_list_member<
117 std::remove_cvref_t<Event>, RoutedEventList> and
118 handler_for<Downstream, std::remove_cvref_t<Event>>)
119 void handle(Event &&event) {
120 for (auto &d : downstreams) {
121 try {
122 d.handle(std::as_const(event));
123 } catch (end_of_processing const &) {
124 flush_all_but(d);
125 throw;
126 }
127 }
128 }
129
130 void flush() {
131 std::exception_ptr end;
132 for (auto &d : downstreams) {
133 try {
134 d.flush();
135 } catch (end_of_processing const &) {
136 if (not end)
137 end = std::current_exception();
138 }
139 }
140 if (end)
141 std::rethrow_exception(end);
142 }
143};
144
145} // namespace internal
146
184template <typename RoutedEventList, typename Router, std::size_t N,
185 typename Downstream>
186auto route_homogeneous(Router router, std::array<Downstream, N> downstreams) {
187 return internal::route_homogeneous<RoutedEventList, Router, N, Downstream>(
188 std::move(router), std::move(downstreams));
189}
190
227template <typename RoutedEventList, typename Router, typename... Downstreams>
228auto route_homogeneous(Router router, Downstreams... downstreams) {
230 std::move(router), std::array{std::move(downstreams)...});
231}
232
271template <typename RoutedEventList, typename BroadcastEventList = type_list<>,
272 typename Router, typename... Downstreams>
273auto route(Router router, Downstreams... downstreams) {
274 static_assert(type_list_like<RoutedEventList>);
276 static_assert(
279 0,
280 "routed event list and broadcast event list must not overlap");
281 using type_erased_downstream = type_erased_processor<
283 return route_homogeneous<RoutedEventList, Router, sizeof...(Downstreams),
284 type_erased_downstream>(
285 std::move(router),
286 std::array<type_erased_downstream, sizeof...(Downstreams)>{
287 type_erased_downstream(std::move(downstreams))...});
288}
289
298 public:
303 template <typename Event>
304 auto operator()(Event const & /* event */) const -> std::size_t {
305 return std::size_t(-1);
306 }
307};
308
318template <std::size_t N, typename NumericTraits = default_numeric_traits>
320 std::array<typename NumericTraits::channel_type, N> channels;
321 std::array<std::size_t, N> indices;
322
323 public:
333 template <typename ChannelIndexPair>
335 std::array<ChannelIndexPair, N> const &channel_indices)
336 : channels(std::invoke([&] {
337 std::array<typename NumericTraits::channel_type, N> ret{};
338 std::transform(channel_indices.begin(), channel_indices.end(),
339 ret.begin(),
340 [](auto p) { return std::get<0>(p); });
341 return ret;
342 })),
343 indices(std::invoke([&] {
344 std::array<std::size_t, N> ret{};
345 std::transform(channel_indices.begin(), channel_indices.end(),
346 ret.begin(),
347 [](auto p) { return std::get<1>(p); });
348 return ret;
349 })) {
350
351 static_assert(
352 std::is_convertible_v<decltype(std::get<0>(channel_indices[0])),
353 typename NumericTraits::channel_type> &&
354 std::is_convertible_v<
355 decltype(std::get<1>(channel_indices[0])), std::size_t>,
356 "channel_indices must be an array of pair-like convertible to (channel, std::size_t)");
357 }
358
360 template <typename Event>
361 auto operator()(Event const &event) const -> std::size_t {
362 static_assert(std::is_same_v<decltype(event.channel),
363 typename NumericTraits::channel_type>);
364 auto it = std::find(channels.begin(), channels.end(), event.channel);
365 if (it == channels.end())
366 return std::numeric_limits<std::size_t>::max();
367 return indices[internal::as_unsigned(
368 std::distance(channels.begin(), it))];
369 }
370};
371
393template <std::size_t N, typename Downstream>
394auto broadcast_homogeneous(std::array<Downstream, N> downstreams) {
395 return route_homogeneous<type_list<>, null_router, N, Downstream>(
396 null_router(), std::move(downstreams));
397}
398
419template <typename... Downstreams>
420auto broadcast_homogeneous(Downstreams... downstreams) {
421 auto arr = std::array{std::move(downstreams)...};
422 return broadcast_homogeneous(std::move(arr));
423}
424
446template <typename BroadcastEventList, typename... Downstreams>
447auto broadcast(Downstreams... downstreams) {
448 return route<type_list<>, BroadcastEventList, null_router, Downstreams...>(
449 null_router(), std::move(downstreams)...);
450}
451
452} // namespace tcspc
channel_router(std::array< ChannelIndexPair, N > const &channel_indices)
Construct with channels and corresponding downstream indices.
Definition route.hpp:334
auto operator()(Event const &event) const -> std::size_t
Implements router requirement.
Definition route.hpp:361
Router that does not route.
Definition route.hpp:297
auto operator()(Event const &) const -> std::size_t
Implements router requirement; always returns std::numeric_limits<std::size_t>::max().
Definition route.hpp:304
Processor that type-erases the downstream processor.
Definition type_erased_processor.hpp:126
Concept that is satisfied when R conforms to the libtcspc router interface for event types Events.
Definition route.hpp:42
Concept that is satisfied when a type is a tcspc::type_list specialization.
Definition type_list.hpp:64
auto merge_processor_graphs(processor_graph const &a, processor_graph const &b) -> processor_graph
Create a new processor graph by merging two existing ones.
Definition introspect.hpp:380
auto broadcast(Downstreams... downstreams)
Create a processor that broadcasts events to multiple downstream processors.
Definition route.hpp:447
auto route_homogeneous(Router router, std::array< Downstream, N > downstreams)
Create a processor that routes events to multiple downstreams of the same type.
Definition route.hpp:186
auto broadcast_homogeneous(std::array< Downstream, N > downstreams)
Create a processor that broadcasts events to multiple downstream processors of the same type.
Definition route.hpp:394
auto route(Router router, Downstreams... downstreams)
Create a processor that routes events to different downstreams.
Definition route.hpp:273
type_list_intersection< TL0, TL1 >::type type_list_intersection_t
Helper type for tcspc::type_list_intersection.
Definition type_list.hpp:455
constexpr std::size_t type_list_size_v
Helper variable template for tcspc::type_list_size.
Definition type_list.hpp:133
type_list_union< TL0, TL1 >::type type_list_union_t
Helper type for tcspc::type_list_union.
Definition type_list.hpp:399
libtcspc namespace.
Definition acquire.hpp:30
Compile-time representation of a list of types.
Definition type_list.hpp:38