FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
paced_sender.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
4//
5// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
6
8
9#include <algorithm>
10#include <atomic>
11#include <chrono>
12#include <stdexcept>
13
14#if defined(__i386__) || defined(__x86_64__)
15#include <immintrin.h>
16#endif
17
19{
20 namespace
21 {
22 constexpr auto kCoarsePacingSleep = std::chrono::milliseconds(1);
23 constexpr auto kFinePacingSpin = std::chrono::microseconds(200);
24
25 void cpuPause() noexcept
26 {
27#if defined(__i386__) || defined(__x86_64__)
28 _mm_pause();
29#elif defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__))
30 __asm__ __volatile__("yield" ::: "memory");
31#else
32 std::atomic_signal_fence(std::memory_order_seq_cst);
33#endif
34 }
35 }
36
37 PacedSender::PacedSender(std::unique_ptr<DatagramSender> sender, const std::size_t queue_depth) :
38 _sender(std::move(sender)), _queue_depth(queue_depth)
39 {
40 if (!_sender)
41 {
42 throw std::invalid_argument("PacedSender requires a datagram sender");
43 }
44 if (queue_depth == 0)
45 {
46 throw std::invalid_argument("PacedSender queue depth must be positive");
47 }
48 }
49
51
52 void PacedSender::open(const std::string& host, const std::uint16_t port) { _sender->open(host, port); }
53
55 {
56 std::scoped_lock const lock(_mutex);
57 if (_started)
58 {
59 return;
60 }
61 _simulation_epoch_time = simulation_epoch_time;
62 _steady_epoch = std::chrono::steady_clock::now();
63 _stopping = false;
64 _started = true;
65 _thread = std::thread([this] { run(); });
66 }
67
69 {
70 std::unique_lock lock(_mutex);
71 if (!_started)
72 {
73 throw std::logic_error("PacedSender must be started before enqueue");
74 }
75 if (_stopping)
76 {
77 return EnqueueResult{
78 .enqueued = false,
79 .dropped = std::nullopt,
80 };
81 }
82
83 while (queuedOrSendingCount() >= _queue_depth)
84 {
85 const bool precedes_latest = !_queue.empty() && packet.first_sample_time < _queue.back().first_sample_time;
86 if (!_priority_overflow_in_progress && precedes_latest)
87 {
88 _priority_overflow_in_progress = true;
89 insertByDeadlineUnlocked(std::move(packet));
90 _cv.notify_all();
91 _cv.wait(lock, [this] { return _stopping || queuedOrSendingCount() <= _queue_depth; });
92 _priority_overflow_in_progress = false;
93 _cv.notify_all();
94 return EnqueueResult{.enqueued = true, .dropped = std::nullopt};
95 }
96
97 _cv.wait(lock);
98 if (_stopping)
99 {
100 return EnqueueResult{
101 .enqueued = false,
102 .dropped = std::nullopt,
103 };
104 }
105 }
106
107 insertByDeadlineUnlocked(std::move(packet));
108 _cv.notify_all();
109 return EnqueueResult{.enqueued = true, .dropped = std::nullopt};
110 }
111
112 bool PacedSender::enqueueBatch(std::vector<SerializedPacket> packets)
113 {
114 if (packets.empty())
115 {
116 return true;
117 }
118
119 std::unique_lock lock(_mutex);
120 if (!_started)
121 {
122 throw std::logic_error("PacedSender must be started before enqueue");
123 }
124 if (_stopping)
125 {
126 return false;
127 }
128
129 for (auto& packet : packets)
130 {
131 insertByDeadlineUnlocked(std::move(packet));
132 }
133 _cv.notify_all();
134 _cv.wait(lock, [this] { return _stopping || queuedOrSendingCount() <= _queue_depth; });
135 return true;
136 }
137
139 {
140 std::unique_lock lock(_mutex);
141 _cv.wait(lock, [this] { return _queue.empty() && !_send_in_progress; });
142 }
143
145 {
146 {
147 std::scoped_lock const lock(_mutex);
148 if (!_started && !_thread.joinable())
149 {
150 _sender->close();
151 return;
152 }
153 _stopping = true;
154 _cv.notify_all();
155 }
156
157 if (_thread.joinable())
158 {
159 _thread.join();
160 }
161
162 {
163 std::scoped_lock const lock(_mutex);
164 _started = false;
165 _stopping = false;
166 _send_in_progress = false;
167 _cv.notify_all();
168 }
169 _sender->close();
170 }
171
172 std::uint64_t PacedSender::lateDataPacketCount(const std::uint32_t stream_id) const
173 {
174 std::scoped_lock const lock(_mutex);
175 const auto found = _late_data_packets.find(stream_id);
176 return found == _late_data_packets.end() ? 0 : found->second;
177 }
178
179 std::uint64_t PacedSender::lateContextPacketCount(const std::uint32_t stream_id) const
180 {
181 std::scoped_lock const lock(_mutex);
182 const auto found = _late_context_packets.find(stream_id);
183 return found == _late_context_packets.end() ? 0 : found->second;
184 }
185
186 std::uint64_t PacedSender::sentPacketCount(const std::uint32_t stream_id) const
187 {
188 std::scoped_lock const lock(_mutex);
189 const auto found = _sent_packets.find(stream_id);
190 return found == _sent_packets.end() ? 0 : found->second;
191 }
192
193 std::uint64_t PacedSender::sendFailureCount(const std::uint32_t stream_id) const
194 {
195 std::scoped_lock const lock(_mutex);
196 const auto found = _send_failures.find(stream_id);
197 return found == _send_failures.end() ? 0 : found->second;
198 }
199
200 std::uint64_t PacedSender::droppedDataPacketCount(const std::uint32_t stream_id) const
201 {
202 std::scoped_lock const lock(_mutex);
203 const auto found = _dropped_data_packets.find(stream_id);
204 return found == _dropped_data_packets.end() ? 0 : found->second;
205 }
206
207 std::uint64_t PacedSender::droppedContextPacketCount(const std::uint32_t stream_id) const
208 {
209 std::scoped_lock const lock(_mutex);
210 const auto found = _dropped_context_packets.find(stream_id);
211 return found == _dropped_context_packets.end() ? 0 : found->second;
212 }
213
214 std::uint64_t PacedSender::droppedSampleCount(const std::uint32_t stream_id) const
215 {
216 std::scoped_lock const lock(_mutex);
217 const auto found = _dropped_samples.find(stream_id);
218 return found == _dropped_samples.end() ? 0 : found->second;
219 }
220
221 std::vector<DroppedDatagram> PacedSender::consumeDroppedDatagrams()
222 {
223 std::scoped_lock const lock(_mutex);
224 auto result = std::move(_pending_dropped_datagrams);
225 _pending_dropped_datagrams.clear();
226 return result;
227 }
228
229 void PacedSender::run()
230 {
231 std::unique_lock lock(_mutex);
232 while (true)
233 {
234 if (_queue.empty())
235 {
236 if (_stopping)
237 {
238 return;
239 }
240 _cv.wait(lock, [this] { return _stopping || !_queue.empty(); });
241 continue;
242 }
243
244 const auto due = dueTime(_queue.front());
245 if (std::chrono::steady_clock::now() < due)
246 {
247 waitUntilDue(lock, due);
248 continue;
249 }
250
251 auto packet = std::move(_queue.front());
252 _queue.pop_front();
253 _send_in_progress = true;
254 const auto now = std::chrono::steady_clock::now();
255 lock.unlock();
256 sendOneUnlocked(std::move(packet), now);
257 lock.lock();
258 _send_in_progress = false;
259 _cv.notify_all();
260 }
261 }
262
263 void PacedSender::waitUntilDue(std::unique_lock<std::mutex>& lock, const std::chrono::steady_clock::time_point due)
264 {
265 const auto now = std::chrono::steady_clock::now();
266 if (now >= due)
267 {
268 return;
269 }
270
271 const auto remaining = due - now;
272 const auto fine_spin = std::chrono::duration_cast<std::chrono::steady_clock::duration>(kFinePacingSpin);
273 if (remaining > fine_spin)
274 {
275 const auto coarse_sleep =
276 std::chrono::duration_cast<std::chrono::steady_clock::duration>(kCoarsePacingSleep);
277 const auto wait_duration = std::min(remaining - fine_spin, coarse_sleep);
278 _cv.wait_for(lock, wait_duration);
279 return;
280 }
281
282 lock.unlock();
283 while (std::chrono::steady_clock::now() < due)
284 {
285 cpuPause();
286 }
287 lock.lock();
288 }
289
290 void PacedSender::insertByDeadlineUnlocked(SerializedPacket packet)
291 {
292 const auto position = std::upper_bound(_queue.begin(), _queue.end(), packet.first_sample_time,
293 [](const RealType deadline, const SerializedPacket& queued)
294 { return deadline < queued.first_sample_time; });
295 _queue.insert(position, std::move(packet));
296 }
297
298 void PacedSender::sendOneUnlocked(SerializedPacket packet, const std::chrono::steady_clock::time_point now)
299 {
300 const auto due = dueTime(packet);
301 try
302 {
303 _sender->send(packet.bytes);
304 }
305 catch (...)
306 {
307 std::scoped_lock const lock(_mutex);
308 ++_send_failures[packet.stream_id];
309 recordDroppedUnlocked(packet);
310 _pending_dropped_datagrams.push_back(makeDroppedDatagram(packet));
311 return;
312 }
313 std::scoped_lock const lock(_mutex);
314 ++_sent_packets[packet.stream_id];
315 if (now > due + std::chrono::milliseconds(1))
316 {
317 if (packet.context_packet)
318 {
319 ++_late_context_packets[packet.stream_id];
320 }
321 else
322 {
323 ++_late_data_packets[packet.stream_id];
324 }
325 }
326 }
327
328 void PacedSender::recordDroppedUnlocked(const SerializedPacket& packet)
329 {
330 if (packet.data_packet || (!packet.context_packet && packet.sample_count > 0))
331 {
332 ++_dropped_data_packets[packet.stream_id];
333 _dropped_samples[packet.stream_id] += packet.sample_count;
334 return;
335 }
336 if (packet.context_packet)
337 {
338 ++_dropped_context_packets[packet.stream_id];
339 }
340 }
341
342 DroppedDatagram PacedSender::makeDroppedDatagram(const SerializedPacket& packet) const noexcept
343 {
344 return DroppedDatagram{.stream_id = packet.stream_id,
345 .sample_count = packet.sample_count,
346 .data_packet = packet.data_packet,
347 .context_packet = packet.context_packet};
348 }
349
350 std::size_t PacedSender::queuedOrSendingCount() const noexcept
351 {
352 return _queue.size() + (_send_in_progress ? 1u : 0u);
353 }
354
355 std::chrono::steady_clock::time_point PacedSender::dueTime(const SerializedPacket& packet) const
356 {
357 const auto seconds = packet.first_sample_time - _simulation_epoch_time;
358 const auto nanos = static_cast<std::int64_t>(seconds * 1'000'000'000.0);
359 return _steady_epoch + std::chrono::nanoseconds(nanos);
360 }
361
362}
Vec3 position
bool enqueueBatch(std::vector< SerializedPacket > packets)
std::vector< DroppedDatagram > consumeDroppedDatagrams()
PacedSender(std::unique_ptr< DatagramSender > sender, std::size_t queue_depth)
std::uint64_t droppedContextPacketCount(std::uint32_t stream_id) const
std::uint64_t droppedSampleCount(std::uint32_t stream_id) const
std::uint64_t sentPacketCount(std::uint32_t stream_id) const
std::uint64_t droppedDataPacketCount(std::uint32_t stream_id) const
void start(RealType simulation_epoch_time=0.0)
std::uint64_t lateDataPacketCount(std::uint32_t stream_id) const
std::uint64_t lateContextPacketCount(std::uint32_t stream_id) const
void open(const std::string &host, std::uint16_t port)
EnqueueResult enqueue(SerializedPacket packet)
std::uint64_t sendFailureCount(std::uint32_t stream_id) const
double RealType
Type for real numbers.
Definition config.h:27
math::Vec3 max