libtcspc C++ API
Streaming TCSPC and time tag data processing
Loading...
Searching...
No Matches
bin_increment_cluster_encoding.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 <algorithm>
10#include <cassert>
11#include <cstddef>
12#include <cstring>
13#include <functional>
14#include <iterator>
15#include <limits>
16#include <span>
17#include <type_traits>
18#include <utility>
19
20namespace tcspc::internal {
21
22// Implementation of encoding/decoding for use by batch_bin_increment_clusters
23// and unbatch_bin_increment_clusters.
24//
25// Encode bin increment clusters in a single stream as follows.
26// - The stream element type E (a signed or unsigned integer) is equal to the
27// bin index type.
28// - Each cluster is prefixed with its size as follows. Let UE be the unsigned
29// integer type corresponding to E.
30// - If the cluster size is less than the maximum value of UE, it is stored
31// as a single stream element.
32// - Otherwise a single stream element containing the maximum value of UE is
33// stored, followed by sizeof(std::size_t) unaligned bytes containing the
34// size.
35// - The cluster's bin indices are stored in order following the size prefix.
36
37template <typename BinIndex> struct bin_increment_cluster_encoding_traits {
38 using encoded_size_type = std::make_unsigned_t<BinIndex>;
39 static_assert(sizeof(encoded_size_type) <= sizeof(std::size_t));
40 static constexpr auto encoded_size_max =
41 std::numeric_limits<encoded_size_type>::max();
42 static constexpr auto large_size_element_count =
43 sizeof(std::size_t) / sizeof(encoded_size_type);
44};
45
46template <typename BinIndex>
47[[nodiscard]] constexpr auto
48encoded_bin_increment_cluster_size(std::size_t cluster_size) noexcept
49 -> std::size_t {
50 using traits = bin_increment_cluster_encoding_traits<BinIndex>;
51 bool const is_long_mode = cluster_size >= traits::encoded_size_max;
52 std::size_t const size_of_size =
53 is_long_mode ? 1 + traits::large_size_element_count : 1;
54 return size_of_size + cluster_size;
55}
56
57// The Storage type must define the following member functions:
58// - [[nodiscard]] auto available_capacity() const -> std::size_t;
59// - [[nodiscard]] auto make_space(std::size_t) -> std::span<BinIndex>;
60// The latter is only called when capacity is available.
61//
62// Returns true if the encoded cluster fit in storge; false if not, in which
63// case storage is not modified.
64template <typename BinIndex, typename Storage>
65[[nodiscard]] auto
66encode_bin_increment_cluster(Storage dest, std::span<BinIndex const> cluster)
67 -> bool {
68 using traits = bin_increment_cluster_encoding_traits<BinIndex>;
69
70 std::size_t const size = cluster.size();
71 bool const is_long_mode = size >= traits::encoded_size_max;
72 std::size_t const size_of_size =
73 is_long_mode ? 1 + traits::large_size_element_count : 1;
74 std::size_t const total_size = size_of_size + size;
75 if (total_size > dest.available_capacity())
76 return false;
77
78 auto spn = dest.make_space(total_size);
79 if (is_long_mode) {
80 spn.front() = static_cast<BinIndex>(traits::encoded_size_max);
81 spn = spn.subspan(1);
82 auto const size_dest = std::as_writable_bytes(
83 spn.subspan(0, traits::large_size_element_count));
84 assert(size_dest.size() == sizeof(size));
85 std::memcpy(size_dest.data(), &size, sizeof(size));
86 spn = spn.subspan(size_dest.size());
87 } else {
88 spn.front() = static_cast<BinIndex>(
89 static_cast<typename traits::encoded_size_type>(size));
90 spn = spn.subspan(1);
91 }
92 assert(spn.size() == cluster.size());
93 std::copy(cluster.begin(), cluster.end(), spn.begin());
94 return true;
95}
96
97template <typename BinIndex> class bin_increment_cluster_decoder {
98 using traits = bin_increment_cluster_encoding_traits<BinIndex>;
99
100 std::span<BinIndex const> clusters;
101
102 public:
103 explicit bin_increment_cluster_decoder(std::span<BinIndex const> clusters)
104 : clusters(clusters) {}
105
106 // Constant forward iterator over the clusters. There is no non-const
107 // iteration support. Dereferencing yields `std::span<BinIndex const>`
108 // (including for empty clusters).
109 class const_iterator {
110 typename std::span<BinIndex const>::iterator it;
111
112 // Given a valid (not past-end) it, return the range of the encoded
113 // cluster pointed to by *this.
114 auto cluster_range() const -> std::pair<decltype(it), decltype(it)> {
115 bool const is_long_mode =
116 static_cast<typename traits::encoded_size_type>(*it) ==
117 traits::encoded_size_max;
118 std::size_t const size_of_size =
119 is_long_mode ? 1 + traits::large_size_element_count : 1;
120 std::size_t const cluster_size =
121 is_long_mode
122 ? std::invoke([sit = std::next(it)] {
123 std::size_t size{};
124 std::memcpy(&size, &*sit, sizeof(size));
125 return size;
126 })
127 : static_cast<typename traits::encoded_size_type>(*it);
128 auto const start =
129 std::next(it, static_cast<std::ptrdiff_t>(size_of_size));
130 auto const stop =
131 std::next(start, static_cast<std::ptrdiff_t>(cluster_size));
132 return {start, stop};
133 }
134
135 explicit const_iterator(decltype(it) iter) : it(iter) {}
136
137 friend class bin_increment_cluster_decoder;
138
139 public:
140 using value_type = std::span<BinIndex const>;
141 using difference_type = std::ptrdiff_t;
142 using reference = value_type const &;
143 using pointer = value_type const *;
144 using iterator_category = std::input_iterator_tag;
145
146 const_iterator() = delete;
147
148 auto operator++() -> const_iterator & {
149 auto const [start, stop] = cluster_range();
150 it = stop;
151 return *this;
152 }
153
154 auto operator++(int) -> const_iterator {
155 auto const ret = *this;
156 ++(*this);
157 return ret;
158 }
159
160 auto operator*() const -> value_type {
161 auto const [start, stop] = cluster_range();
162 return std::span(&*start, &*stop);
163 }
164
165 auto operator==(const_iterator const &) const noexcept
166 -> bool = default;
167 };
168
169 [[nodiscard]] auto begin() const noexcept -> const_iterator {
170 return const_iterator(clusters.begin());
171 }
172
173 [[nodiscard]] auto end() const noexcept -> const_iterator {
174 return const_iterator(clusters.end());
175 }
176};
177
178// Deduction guide for span-of-non-const.
179template <typename BinIndex>
180bin_increment_cluster_decoder(std::span<BinIndex>)
181 -> bin_increment_cluster_decoder<BinIndex>;
182
183} // namespace tcspc::internal
auto stop(std::string message_prefix, Downstream downstream)
Create a processor that ends the stream when a given event type is received.
Definition stop.hpp:138