FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
sim_threading.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2006-2008 Marc Brooker and Michael Inggs
4// Copyright (c) 2008-present FERS Contributors (see AUTHORS.md).
5//
6// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
7
8/**
9 * @file sim_threading.cpp
10 * @brief Implements the core event-driven simulation engine.
11 *
12 * This file contains the primary simulation loop, which orchestrates the entire
13 * simulation process. It operates on a unified, event-driven model capable of
14 * handling both pulsed and continuous-wave (CW) radar systems concurrently.
15 */
16
17#include "sim_threading.h"
18
19#include <algorithm>
20#include <atomic>
21#include <chrono>
22#include <cmath>
23#include <format>
24#include <utility>
25
26#include "logging.h"
27#include "parameters.h"
29#include "radar/receiver.h"
30#include "radar/target.h"
31#include "radar/transmitter.h"
32#include "sim_events.h"
34#include "thread_pool.h"
35#include "world.h"
36
37using logging::Level;
39using radar::Receiver;
41
42namespace core
43{
44 SimulationEngine::SimulationEngine(World* world, pool::ThreadPool& pool, std::shared_ptr<ProgressReporter> reporter,
45 std::string output_dir,
46 std::shared_ptr<OutputMetadataCollector> metadata_collector) :
47 _world(world), _pool(pool), _reporter(std::move(reporter)), _metadata_collector(std::move(metadata_collector)),
48 _last_report_time(std::chrono::steady_clock::now()), _output_dir(std::move(output_dir))
49 {
50 }
51
53 {
54 if (_reporter)
55 {
56 _reporter->report("Initializing event-driven simulation...", 0, 100);
57 }
58
59 initializeFinalizers();
60
61 LOG(Level::INFO, "Starting unified event-driven simulation loop.");
62
63 auto& event_queue = _world->getEventQueue();
64 auto& state = _world->getSimulationState();
65 const RealType end_time = params::endTime();
66
67 while (!event_queue.empty() && state.t_current <= end_time)
68 {
69 const Event event = event_queue.top();
70 event_queue.pop();
71
72 processCwPhysics(event.timestamp);
73
74 state.t_current = event.timestamp;
75
76 processEvent(event);
77 updateProgress();
78 }
79
80 shutdown();
81 }
82
83 void SimulationEngine::initializeFinalizers()
84 {
85 for (const auto& receiver_ptr : _world->getReceivers())
86 {
87 if (receiver_ptr->getMode() == OperationMode::PULSED_MODE)
88 {
89 _finalizer_threads.emplace_back(processing::runPulsedFinalizer, receiver_ptr.get(),
90 &_world->getTargets(), _reporter, _output_dir, _metadata_collector);
91 }
92 }
93 }
94
96 {
97 auto& [t_current, active_cw_transmitters] = _world->getSimulationState();
98
99 if (t_event <= t_current)
100 {
101 return;
102 }
103
104 const RealType dt_sim = 1.0 / (params::rate() * params::oversampleRatio());
105 const auto start_index = static_cast<size_t>(std::ceil((t_current - params::startTime()) / dt_sim));
106 const auto end_index = static_cast<size_t>(std::ceil((t_event - params::startTime()) / dt_sim));
107
108 for (size_t sample_index = start_index; sample_index < end_index; ++sample_index)
109 {
110 const RealType t_step = params::startTime() + static_cast<RealType>(sample_index) * dt_sim;
111
112 for (const auto& receiver_ptr : _world->getReceivers())
113 {
114 if (receiver_ptr->getMode() == OperationMode::CW_MODE && receiver_ptr->isActive())
115 {
116 ComplexType sample = calculateCwSample(receiver_ptr.get(), t_step, active_cw_transmitters);
117 receiver_ptr->setCwSample(sample_index, sample);
118 }
119 }
120 }
121 }
122
124 const std::vector<Transmitter*>& cw_sources) const
125 {
126 ComplexType total_sample{0.0, 0.0};
127 for (const auto& cw_source : cw_sources)
128 {
129 if (!rx->checkFlag(Receiver::RecvFlag::FLAG_NODIRECT))
130 {
131 total_sample += simulation::calculateDirectPathContribution(cw_source, rx, t_step);
132 }
133 for (const auto& target_ptr : _world->getTargets())
134 {
135 total_sample += simulation::calculateReflectedPathContribution(cw_source, rx, target_ptr.get(), t_step);
136 }
137 }
138 return total_sample;
139 }
140
142 {
143 // NOLINTBEGIN(cppcoreguidelines-pro-type-static-cast-downcast)
144 switch (event.type)
145 {
147 handleTxPulsedStart(static_cast<Transmitter*>(event.source_object), event.timestamp);
148 break;
150 handleRxPulsedWindowStart(static_cast<Receiver*>(event.source_object), event.timestamp);
151 break;
153 handleRxPulsedWindowEnd(static_cast<Receiver*>(event.source_object), event.timestamp);
154 break;
156 handleTxCwStart(static_cast<Transmitter*>(event.source_object));
157 break;
159 handleTxCwEnd(static_cast<Transmitter*>(event.source_object));
160 break;
162 handleRxCwStart(static_cast<Receiver*>(event.source_object));
163 break;
165 handleRxCwEnd(static_cast<Receiver*>(event.source_object));
166 break;
167 }
168 // NOLINTEND(cppcoreguidelines-pro-type-static-cast-downcast)
169 }
170
171 void SimulationEngine::routeResponse(Receiver* rx, std::unique_ptr<serial::Response> response) const
172 {
173 if (!response)
174 {
175 return;
176 }
177 if (rx->getMode() == OperationMode::PULSED_MODE)
178 {
179 rx->addResponseToInbox(std::move(response));
180 }
181 else
182 {
183 rx->addInterferenceToLog(std::move(response));
184 }
185 }
186
188 {
189 for (const auto& rx_ptr : _world->getReceivers())
190 {
191 if (!rx_ptr->checkFlag(Receiver::RecvFlag::FLAG_NODIRECT))
192 {
193 routeResponse(rx_ptr.get(), simulation::calculateResponse(tx, rx_ptr.get(), tx->getSignal(), t_event));
194 }
195 for (const auto& target_ptr : _world->getTargets())
196 {
197 routeResponse(
198 rx_ptr.get(),
199 simulation::calculateResponse(tx, rx_ptr.get(), tx->getSignal(), t_event, target_ptr.get()));
200 }
201 }
202
203 const RealType next_theoretical_time = t_event + 1.0 / tx->getPrf();
204 if (const auto next_pulse_opt = tx->getNextPulseTime(next_theoretical_time);
205 next_pulse_opt && *next_pulse_opt <= params::endTime())
206 {
207 _world->getEventQueue().push({*next_pulse_opt, EventType::TX_PULSED_START, tx});
208 }
209 }
210
212 {
213 rx->setActive(true);
214 _world->getEventQueue().push({t_event + rx->getWindowLength(), EventType::RX_PULSED_WINDOW_END, rx});
215 }
216
218 {
219 rx->setActive(false);
220 const auto& active_cw_transmitters = _world->getSimulationState().active_cw_transmitters;
221
222 RenderingJob job{.ideal_start_time = t_event - rx->getWindowLength(),
223 .duration = rx->getWindowLength(),
224 .responses = rx->drainInbox(),
225 .active_cw_sources = active_cw_transmitters};
226
227 rx->enqueueFinalizerJob(std::move(job));
228
229 const RealType next_theoretical = t_event - rx->getWindowLength() + 1.0 / rx->getWindowPrf();
230 if (const auto next_start = rx->getNextWindowTime(next_theoretical);
231 next_start && *next_start <= params::endTime())
232 {
233 _world->getEventQueue().push({*next_start, EventType::RX_PULSED_WINDOW_START, rx});
234 }
235 }
236
241
243 {
244 auto& cw_txs = _world->getSimulationState().active_cw_transmitters;
245 std::erase(cw_txs, tx);
246 }
247
249
251
252 void SimulationEngine::updateProgress()
253 {
254 if (!_reporter)
255 {
256 return;
257 }
258
259 const RealType t_current = _world->getSimulationState().t_current;
260 const RealType end_time = params::endTime();
261 const int progress = static_cast<int>(t_current / end_time * 100.0);
262
263 if (const auto now = std::chrono::steady_clock::now();
264 progress != _last_reported_percent || now - _last_report_time >= std::chrono::milliseconds(100))
265 {
266 _reporter->report(std::format("Simulating... {:.2f}s / {:.2f}s", t_current, end_time), progress, 100);
267 _last_reported_percent = progress;
268 _last_report_time = now;
269 }
270 }
271
272 void SimulationEngine::shutdown()
273 {
274 LOG(Level::INFO, "Main simulation loop finished. Waiting for finalization tasks...");
275 if (_reporter)
276 {
277 _reporter->report("Main simulation finished. Waiting for data export...", 100, 100);
278 }
279
280 for (const auto& receiver_ptr : _world->getReceivers())
281 {
282 if (receiver_ptr->getMode() == OperationMode::CW_MODE)
283 {
284 _pool.enqueue(processing::finalizeCwReceiver, receiver_ptr.get(), &_pool, _reporter, _output_dir,
285 _metadata_collector);
286 }
287 else if (receiver_ptr->getMode() == OperationMode::PULSED_MODE)
288 {
289 RenderingJob shutdown_job{};
290 shutdown_job.duration = -1.0;
291 receiver_ptr->enqueueFinalizerJob(std::move(shutdown_job));
292 }
293 }
294
295 _pool.wait();
296 for (auto& finalizer_thread : _finalizer_threads)
297 {
298 if (finalizer_thread.joinable())
299 {
300 finalizer_thread.join();
301 }
302 }
303
304 LOG(Level::INFO, "All finalization tasks complete.");
305 if (_reporter)
306 {
307 _reporter->report("Simulation complete", 100, 100);
308 }
309 LOG(Level::INFO, "Event-driven simulation loop finished.");
310 }
311
313 const std::function<void(const std::string&, int, int)>& progress_callback,
314 const std::string& output_dir)
315 {
316 auto reporter = std::make_shared<ProgressReporter>(progress_callback);
317 auto metadata_collector = std::make_shared<OutputMetadataCollector>(output_dir);
318 SimulationEngine engine(world, pool, reporter, output_dir, metadata_collector);
319 engine.run();
320 return metadata_collector->snapshot();
321 }
322}
Header for radar channel propagation and interaction models.
Encapsulates the state and logic of the event-driven simulation loop.
ComplexType calculateCwSample(radar::Receiver *rx, RealType t_step, const std::vector< radar::Transmitter * > &cw_sources) const
Calculates the total complex I/Q sample for a receiver at a specific time step.
void handleRxCwStart(radar::Receiver *rx)
Handles a continuous-wave receiver starting to record.
void handleRxPulsedWindowEnd(radar::Receiver *rx, RealType t_event)
Handles the closing of a pulsed receiver's listening window, triggering finalization.
void handleTxCwEnd(radar::Transmitter *tx)
Handles a continuous-wave transmitter turning off.
SimulationEngine(World *world, pool::ThreadPool &pool, std::shared_ptr< ProgressReporter > reporter, std::string output_dir, std::shared_ptr< OutputMetadataCollector > metadata_collector=nullptr)
Constructs the simulation engine.
void handleTxCwStart(radar::Transmitter *tx)
Handles a continuous-wave transmitter turning on.
void handleRxPulsedWindowStart(radar::Receiver *rx, RealType t_event)
Handles the opening of a pulsed receiver's listening window.
void run()
Starts and runs the main simulation loop until completion.
void processEvent(const Event &event)
Dispatches a discrete simulation event to its specific handler.
void processCwPhysics(RealType t_event)
Advances the time-stepped inner loop for active continuous-wave (CW) systems.
void handleRxCwEnd(radar::Receiver *rx)
Handles a continuous-wave receiver stopping recording.
void handleTxPulsedStart(radar::Transmitter *tx, RealType t_event)
Handles the start of a pulsed transmission.
The World class manages the simulator environment.
Definition world.h:39
const std::vector< std::unique_ptr< radar::Target > > & getTargets() const noexcept
Retrieves the list of radar targets.
Definition world.h:200
SimulationState & getSimulationState() noexcept
Gets a mutable reference to the global simulation state.
Definition world.h:284
std::priority_queue< Event, std::vector< Event >, EventComparator > & getEventQueue() noexcept
Gets a mutable reference to the global event queue.
Definition world.h:275
const std::vector< std::unique_ptr< radar::Receiver > > & getReceivers() const noexcept
Retrieves the list of radar receivers.
Definition world.h:210
A simple thread pool implementation.
Definition thread_pool.h:29
std::future< std::invoke_result_t< F, Args... > > enqueue(F &&f, Args &&... args)
Enqueues a task to be executed by the thread pool.
Definition thread_pool.h:57
void wait()
Waits for all tasks in the thread pool to finish.
Definition thread_pool.h:82
Manages radar signal reception and response processing.
Definition receiver.h:37
void addInterferenceToLog(std::unique_ptr< serial::Response > response) noexcept
Adds a pulsed interference response to the receiver's CW-mode log.
Definition receiver.cpp:36
void setActive(const bool active) noexcept
Sets the active state of the receiver.
Definition receiver.h:172
bool checkFlag(RecvFlag flag) const noexcept
Checks if a specific flag is set.
Definition receiver.h:88
std::vector< std::unique_ptr< serial::Response > > drainInbox() noexcept
Moves all responses from the inbox into a RenderingJob.
Definition receiver.cpp:42
std::optional< RealType > getNextWindowTime(RealType time) const
Determines the next valid window start time at or after the given time.
Definition receiver.cpp:132
void enqueueFinalizerJob(core::RenderingJob &&job)
Adds a completed RenderingJob to the finalizer queue.
Definition receiver.cpp:50
OperationMode getMode() const noexcept
Gets the operational mode of the receiver.
Definition receiver.h:160
RealType getWindowPrf() const noexcept
Retrieves the pulse repetition frequency (PRF) of the radar window.
Definition receiver.h:116
void addResponseToInbox(std::unique_ptr< serial::Response > response) noexcept
Adds a response to the receiver's pulsed-mode inbox.
Definition receiver.cpp:30
RealType getWindowLength() const noexcept
Retrieves the radar window length.
Definition receiver.h:109
Represents a radar transmitter system.
Definition transmitter.h:33
RealType getPrf() const noexcept
Retrieves the pulse repetition frequency (PRF).
Definition transmitter.h:64
fers_signal::RadarSignal * getSignal() const noexcept
Retrieves the radar signal currently being transmitted.
Definition transmitter.h:71
std::optional< RealType > getNextPulseTime(RealType time) const
Determines the valid simulation time for a pulse at or after the given time.
double RealType
Type for real numbers.
Definition config.h:27
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
Declares the functions for the asynchronous receiver finalization pipelines.
Header file for the logging system.
#define LOG(level,...)
Definition logging.h:19
OutputMetadata runEventDrivenSim(World *world, pool::ThreadPool &pool, const std::function< void(const std::string &, int, int)> &progress_callback, const std::string &output_dir)
Runs the unified, event-driven radar simulation.
@ RX_PULSED_WINDOW_START
A pulsed receiver opens its listening window.
@ RX_PULSED_WINDOW_END
A pulsed receiver closes its listening window.
@ TX_CW_START
A continuous-wave transmitter starts transmitting.
@ TX_CW_END
A continuous-wave transmitter stops transmitting.
@ TX_PULSED_START
A pulsed transmitter begins emitting a pulse.
@ RX_CW_END
A continuous-wave receiver stops listening.
@ RX_CW_START
A continuous-wave receiver starts listening.
RealType endTime() noexcept
Get the end time for the simulation.
Definition parameters.h:109
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
RealType startTime() noexcept
Get the start time for the simulation.
Definition parameters.h:103
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151
void finalizeCwReceiver(radar::Receiver *receiver, pool::ThreadPool *, std::shared_ptr< core::ProgressReporter > reporter, const std::string &output_dir, std::shared_ptr< core::OutputMetadataCollector > metadata_collector)
The finalization task for a continuous-wave (CW) mode receiver.
void runPulsedFinalizer(radar::Receiver *receiver, const std::vector< std::unique_ptr< radar::Target > > *targets, std::shared_ptr< core::ProgressReporter > reporter, const std::string &output_dir, std::shared_ptr< core::OutputMetadataCollector > metadata_collector)
The main function for a dedicated pulsed-mode receiver finalizer thread.
OperationMode
Defines the operational mode of a radar component.
Definition radar_obj.h:37
ComplexType calculateDirectPathContribution(const Transmitter *trans, const Receiver *recv, const RealType timeK)
Calculates the complex envelope contribution for a direct propagation path (Tx -> Rx) at a specific t...
std::unique_ptr< serial::Response > calculateResponse(const Transmitter *trans, const Receiver *recv, const RadarSignal *signal, const RealType startTime, const Target *targ)
Creates a Response object by simulating a signal's interaction over its duration.
ComplexType calculateReflectedPathContribution(const Transmitter *trans, const Receiver *recv, const Target *targ, const RealType timeK)
Calculates the complex envelope contribution for a reflected path (Tx -> Tgt -> Rx) at a specific tim...
Defines the Parameters struct and provides methods for managing simulation parameters.
Radar Receiver class for managing signal reception and response handling.
Defines the core structures for the event-driven simulation engine.
Header file for the main simulation runner.
Represents a single event in the simulation's time-ordered queue.
Definition sim_events.h:43
RealType timestamp
The simulation time at which the event occurs.
Definition sim_events.h:44
EventType type
The type of the event.
Definition sim_events.h:45
radar::Radar * source_object
Pointer to the object that generated the event.
Definition sim_events.h:46
A data packet containing all information needed to process one receive window.
RealType ideal_start_time
The ideal, jitter-free start time of the receive window.
std::vector< radar::Transmitter * > active_cw_transmitters
A global list of all currently active continuous-wave transmitters.
RealType t_current
The master simulation clock, advanced by the event loop.
Defines classes for radar targets and their Radar Cross-Section (RCS) models.
A simple thread pool implementation.
Header file for the Transmitter class in the radar namespace.
Header file for the World class in the simulator.