libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
recover_order.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 "common.hpp"
11#include "errors.hpp"
12#include "int_arith.hpp"
13#include "introspect.hpp"
14#include "numeric_traits.hpp"
15#include "processor.hpp"
16#include "type_list.hpp"
17#include "variant_event.hpp"
18
19#include <algorithm>
20#include <concepts>
21#include <limits>
22#include <stdexcept>
23#include <type_traits>
24#include <utility>
25#include <vector>
26
27namespace tcspc {
28
29namespace internal {
30
31template <typename EventList, typename NumericTraits, typename Downstream>
33class recover_order {
34 static_assert(type_list_like<EventList>);
35 static_assert(is_move_constructible_list_v<EventList>,
36 "recover_order requires every event in EventList to be "
37 "move-constructible (events are buffered in a vector)");
38 static_assert(is_move_assignable_list_v<EventList>,
39 "recover_order requires every event in EventList to be "
40 "move-assignable (the buffer is kept sorted by shifting)");
41 // Copy-constructibility is required only for const-lvalue inputs (which
42 // are copied into the buffer); checked per-call in handle() so that
43 // move-only events fed as rvalues are accepted.
44
45 using abstime_type = NumericTraits::abstime_type;
46 abstime_type window_size;
47
48 // We just use a sorted vector, because the intended use cases do not
49 // require buffering large numbers of events.
50 // Always in ascending abstime order:
51 std::vector<variant_or_single_event<EventList>> buf;
52
53 // For error checking
54 abstime_type last_emitted_time = std::numeric_limits<abstime_type>::min();
55
56 Downstream downstream;
57
58 public:
59 explicit recover_order(arg::time_window<abstime_type> time_window,
60 Downstream downstream)
61 : window_size(time_window.value), downstream(std::move(downstream)) {
62 if (window_size < 0)
63 throw std::invalid_argument(
64 "recover_order time_window must not be negative");
65 }
66
67 [[nodiscard]] auto introspect_node() const -> processor_info {
68 return processor_info(this, "recover_order");
69 }
70
71 [[nodiscard]] auto introspect_graph() const -> processor_graph {
72 return downstream.introspect_graph().push_entry_point(this);
73 }
74
75 template <typename Event>
76 requires convertible_to_type_list_member<std::remove_cvref_t<Event>,
77 EventList>
78 void handle(Event &&event) {
79 static_assert(std::is_same_v<decltype(event.abstime), abstime_type>);
80 if constexpr (std::is_lvalue_reference_v<Event>) {
81 static_assert(
82 std::copy_constructible<std::remove_cvref_t<Event>>,
83 "recover_order copies const-lvalue inputs into its buffer; "
84 "pass the event as an rvalue or make it copy-constructible");
85 }
86 if (event.abstime < last_emitted_time) {
87 throw data_validation_error(
88 "recover_order encountered event outside of time window");
89 }
90
91 // We perform a sliding-window version of insertion sort, enabled by
92 // the known time bound of out-of-order events.
93
94 // Both finding the events that are ready to emit and finding the
95 // position to insert the new one could be done with log complexity
96 // using std::lower_bound() and std::upper_bound(), but we expect the
97 // buffer to be small in the anticipated use cases, so prefer to do
98 // simple linear searches. (This choice could be made compile-time
99 // selectable.)
100
101 auto const cutoff = pairing_cutoff(event.abstime, window_size);
102 auto keep_it =
103 std::find_if_not(buf.begin(), buf.end(), [&](auto const &v) {
104 return visit_variant_or_single_event(
105 [&](auto const &e) { return e.abstime < cutoff; }, v);
106 });
107
108 std::for_each(buf.begin(), keep_it, [&](auto &v) {
109 visit_variant_or_single_event(
110 [&]<typename E>(E &&e) {
111 last_emitted_time = e.abstime;
112 downstream.handle(std::forward<E>(e));
113 },
114 std::move(v));
115 });
116 buf.erase(buf.begin(), keep_it);
117
118 auto ins_it =
119 std::find_if(buf.rbegin(), buf.rend(), [&](auto const &v) {
120 return visit_variant_or_single_event(
121 [&](auto const &e) { return e.abstime < event.abstime; },
122 v);
123 });
124 if (ins_it == buf.rend())
125 buf.insert(buf.begin(), std::forward<Event>(event));
126 else
127 buf.insert(ins_it.base(), std::forward<Event>(event));
128 }
129
130 // Do not allow other events.
131
132 void flush() {
133 std::for_each(buf.begin(), buf.end(), [&](auto &v) {
134 visit_variant_or_single_event(
135 [&]<typename E>(E &&e) {
136 downstream.handle(std::forward<E>(e));
137 },
138 std::move(v));
139 });
140 buf.clear();
141 downstream.flush();
142 }
143};
144
145} // namespace internal
146
175template <typename EventList, typename NumericTraits = default_numeric_traits,
176 typename Downstream>
179 Downstream downstream) {
180 static_assert(type_list_size_v<EventList> > 0,
181 "recover_order requires non-empty event list");
182 return internal::recover_order<EventList, NumericTraits, Downstream>(
183 time_window, std::move(downstream));
184}
185
186} // namespace tcspc
constexpr bool is_processor_of_list_v
Trait variable to check whether a processor handles a list of event types and flush.
Definition processor.hpp:223
auto recover_order(arg::time_window< typename NumericTraits::abstime_type > time_window, Downstream downstream)
Create a processor that sorts events by abstime, provided that they are out of order only within a bo...
Definition recover_order.hpp:177
constexpr std::size_t type_list_size_v
Helper variable template for tcspc::type_list_size.
Definition type_list.hpp:133
libtcspc namespace.
Definition acquire.hpp:30
Function argument wrapper for time window parameter.
Definition arg_wrappers.hpp:417