mvsim-cli-launch.cpp
Go to the documentation of this file.
1 /*+-------------------------------------------------------------------------+
2  | MultiVehicle simulator (libmvsim) |
3  | |
4  | Copyright (C) 2014-2024 Jose Luis Blanco Claraco |
5  | Copyright (C) 2017 Borys Tymchenko (Odessa Polytechnic University) |
6  | Distributed under 3-clause BSD License |
7  | See COPYING |
8  +-------------------------------------------------------------------------+ */
9 
10 #include <mrpt/core/exceptions.h>
11 #include <mrpt/core/round.h>
12 #include <mrpt/system/os.h> // consoleColorAndStyle()
13 #include <mvsim/World.h>
14 
15 #include <csignal> // sigaction
16 #include <rapidxml_utils.hpp>
17 #include <thread>
18 
19 #if defined(WIN32)
20 #include <windows.h> // SetConsoleCtrlHandler
21 MRPT_TODO("win32: add SetConsoleCtrlHandler");
22 #endif
23 
24 #include "mvsim-cli.h"
25 
27 {
28  mvsim::World* world = nullptr;
29  std::mutex closingMtx;
30 
31  TThreadParams() = default;
32 
33  bool isClosing()
34  {
35  closingMtx.lock();
36  bool ret = closing_;
37  closingMtx.unlock();
38  return ret;
39  }
40  void closing(bool v)
41  {
42  closingMtx.lock();
43  closing_ = v;
44  closingMtx.unlock();
45  }
46 
47  private:
48  bool closing_ = false;
49 };
50 
51 static void mvsim_server_thread_update_GUI(TThreadParams& thread_params);
52 static void mvsim_server_thread_headless(TThreadParams& thread_params);
53 
55 std::mutex gui_key_events_mtx;
56 std::string msg2gui;
57 
58 struct LaunchData
59 {
62  std::thread thGUI;
63  size_t teleopIdxVeh = 0; // Index of the vehicle to teleop
64 };
65 
66 std::optional<LaunchData> app;
67 
69 {
70  app->thread_params.closing(true);
71 
72  if (app->thGUI.joinable()) app->thGUI.join();
73 
74  // save full profiling, if enabled:
75  if (app->world.getTimeLogger().isEnabledKeepWholeHistory())
76  {
77  const std::string sFil = "mvsim_profiler.m";
78  std::cout << "\n***SAVING PROFILER DATA TO***: " << sFil << std::endl;
79  app->world.getTimeLogger().saveToMFile(sFil);
80  }
81 
82  app->world.free_opengl_resources();
83  app.reset(); // destroy all
84 }
85 
87 {
88  std::cerr << "Caught signal " << s << ". Shutting down..." << std::endl;
90  exit(0);
91 }
92 
94 {
95  struct sigaction sigIntHandler;
96 
97  sigIntHandler.sa_handler = &mvsim_signal_handler;
98  sigemptyset(&sigIntHandler.sa_mask);
99  sigIntHandler.sa_flags = 0;
100 
101  sigaction(SIGINT, &sigIntHandler, nullptr);
102 }
103 
104 // returns log strings
106  const mvsim::World::TGUIKeyEvent keyevent, const std::optional<mvsim::TJoyStickEvent>& js)
107 {
108  using namespace mvsim;
109 
110  std::string txt2gui_tmp;
111 
112  const World::VehicleList& vehs = app->world.getListOfVehicles();
113  txt2gui_tmp += mrpt::format(
114  "Selected vehicle: %u/%u", static_cast<unsigned>(app->teleopIdxVeh + 1),
115  static_cast<unsigned>(vehs.size()));
116 
117  if (app->teleopIdxVeh >= vehs.size()) return txt2gui_tmp;
118 
119  // Get iterator to selected vehicle:
120  World::VehicleList::const_iterator it_veh = vehs.begin();
121  std::advance(it_veh, app->teleopIdxVeh);
122 
123  auto& veh = *it_veh->second;
124 
125  // is it logging?
126  if (veh.isLogging())
127  txt2gui_tmp += " (LOGGING)\n";
128  else
129  txt2gui_tmp += "\n";
130 
131  // Get speed: ground truth
132  {
133  const mrpt::math::TTwist2D& vel = veh.getVelocityLocal();
134  txt2gui_tmp += mrpt::format(
135  "gt. vel: lx=%7.03f, ly=%7.03f, w= %7.03fdeg/s\n", vel.vx, vel.vy,
136  mrpt::RAD2DEG(vel.omega));
137  }
138  // Get speed: ground truth
139  {
140  const mrpt::math::TTwist2D& vel = veh.getVelocityLocalOdoEstimate();
141  txt2gui_tmp += mrpt::format(
142  "odo vel: lx=%7.03f, ly=%7.03f, w= %7.03fdeg/s\n", vel.vx, vel.vy,
143  mrpt::RAD2DEG(vel.omega));
144  }
145 
146  // Generic teleoperation interface for any controller that
147  // supports it:
148  {
149  ControllerBaseInterface* controller = veh.getControllerInterface();
152  teleop_in.keycode = keyevent.keycode;
153  teleop_in.js = js;
154  controller->teleop_interface(teleop_in, teleop_out);
155  txt2gui_tmp += teleop_out.append_gui_lines;
156  }
157 
158  return txt2gui_tmp;
159 }
160 
162 {
163  using namespace mvsim;
164 
165  // check args:
166  bool badArgs = false;
167  const auto& unlabeledArgs = cli->argCmd.getValue();
168  if (unlabeledArgs.size() != 2) badArgs = true;
169 
170  if (cli->argHelp.isSet() || badArgs)
171  {
172  fprintf(
173  stdout,
174  R"XXX(Usage: mvsim launch <WORLD_MODEL.xml> [options]
175 
176 Available options:
177  --headless Launch without GUI (e.g. suitable for dockerized envs.)
178  --full-profiler Enable full profiling (generates file with all timings)
179  --realtime-factor <1.0> Run slower (<1) or faster (>1) than real time if !=1.0
180  -v, --verbosity Set verbosity level: DEBUG, INFO (default), WARN, ERROR
181 )XXX");
182  return 0;
183  }
184 
185  // Handle CTRL+C:
187 
188  const auto verbosityLevel = mrpt::typemeta::TEnumType<mrpt::system::VerbosityLevel>::name2value(
189  cli->argVerbosity.getValue());
190 
191  if (verbosityLevel <= mrpt::system::LVL_INFO)
192  {
193  mrpt::system::consoleColorAndStyle(mrpt::system::ConsoleForegroundColor::BRIGHT_YELLOW);
194  std::cout //
195  << "\n"
196  << "====================================================\n"
197  << " MVSIM simulator running. Press CTRL+C to end. \n"
198  << "====================================================\n"
199  << "\n";
200  mrpt::system::consoleColorAndStyle(mrpt::system::ConsoleForegroundColor::DEFAULT);
201  }
202 
203  const auto sXMLfilename = unlabeledArgs.at(1);
204 
205  app.emplace();
206 
207  app->world.setMinLoggingLevel(verbosityLevel);
208 
209  // CLI flags:
210  if (cli->argFullProfiler.isSet()) app->world.getTimeLogger().enableKeepWholeHistory();
211 
212  if (cli->argHeadless.isSet()) app->world.headless(true);
213 
214  // Load from XML:
215  try
216  {
217  rapidxml::file<> fil_xml(sXMLfilename.c_str());
218  app->world.load_from_XML(fil_xml.data(), sXMLfilename.c_str());
219  }
220  catch (const std::exception& e)
221  {
222  std::cerr << "Error: " << e.what() << std::endl;
224  return 1;
225  }
226 
227  // Start network server:
229 
230  // Attach world as a mvsim communications node:
231  app->world.connectToServer();
232 
233  // Launch GUI thread, unless we are in headless mode:
234  app->thread_params.world = &app->world;
235 
236  if (!cli->argHeadless.isSet())
237  {
238  // regular GUI:
239  app->thGUI = std::thread(&mvsim_server_thread_update_GUI, std::ref(app->thread_params));
240  }
241  else
242  {
243  // headless thread for off-screen rendering sensors:
244  app->thGUI = std::thread(&mvsim_server_thread_headless, std::ref(app->thread_params));
245  }
246 
247  // Run simulation:
248  const double tAbsInit = mrpt::Clock::nowDouble();
249  const double rtFactor = cli->argRealTimeFactor.getValue();
250  bool doExit = false;
251 
252  while (!doExit)
253  {
254  // was the quit button hit in the GUI?
255  if (app->world.simulator_must_close()) break;
256 
257  // Simulation
258  // ============================================================
259  // Compute how much time has passed to simulate in real-time:
260  double tNew = mrpt::Clock::nowDouble();
261  double incrTime = rtFactor * (tNew - tAbsInit) - app->world.get_simul_time();
262  int incrTimeSteps =
263  static_cast<int>(std::floor(incrTime / app->world.get_simul_timestep()));
264 
265  // Simulate:
266  if (incrTimeSteps > 0)
267  {
268  app->world.run_simulation(incrTimeSteps * app->world.get_simul_timestep());
269  }
270 
271  std::this_thread::sleep_for(std::chrono::milliseconds(10));
272 
273  // GUI msgs, teleop, etc.
274  // ====================================================
275  gui_key_events_mtx.lock();
277  gui_key_events_mtx.unlock();
278 
279  // Global keys:
280  switch (keyevent.keycode)
281  {
282  case GLFW_KEY_ESCAPE:
283  doExit = true;
284  break;
285  case '1':
286  case '2':
287  case '3':
288  case '4':
289  case '5':
290  case '6':
291  case '7':
292  case '8':
293  case '9':
294  app->teleopIdxVeh = keyevent.keycode - '1';
295  break;
296  };
297 
298  const auto js = app->world.getJoystickState();
299 
300  const auto txt2gui_tmp = mvsim_launch_handle_teleop(keyevent, js);
301 
302  // Clear the keystroke buffer
303  gui_key_events_mtx.lock();
304  if (keyevent.keycode != 0) gui_key_events = World::TGUIKeyEvent();
305  gui_key_events_mtx.unlock();
306 
307  msg2gui = txt2gui_tmp; // send txt msgs to show in the GUI
308 
309  if (app->thread_params.isClosing()) doExit = true;
310 
311  } // end while()
312 
314 
315  return 0;
316 }
317 
319 {
320  try
321  {
322  ASSERT_(thread_params.world);
323  while (!thread_params.isClosing())
324  {
326  guiparams.msg_lines = msg2gui;
327 
328  thread_params.world->update_GUI(&guiparams);
329 
330  // Send key-strokes to the main thread:
331  if (guiparams.keyevent.keycode != 0)
332  {
333  gui_key_events_mtx.lock();
334  gui_key_events = guiparams.keyevent;
335  gui_key_events_mtx.unlock();
336  }
337 
338  std::this_thread::sleep_for(std::chrono::milliseconds(25));
339  }
340  }
341  catch (const std::exception& e)
342  {
343  std::cerr << "[mvsim_server_thread_update_GUI] Exception: " << e.what() << std::endl;
344  }
345 }
346 
347 void mvsim_server_thread_headless(TThreadParams& thread_params)
348 {
349  try
350  {
351  ASSERT_(thread_params.world);
352  while (!thread_params.isClosing() && !thread_params.world->simulator_must_close())
353  {
355 
356  std::this_thread::sleep_for(std::chrono::microseconds(
357  mrpt::round(thread_params.world->get_simul_timestep() * 1000000)));
358  }
359 
360  // in case we are here due to simulator_must_close()
361  thread_params.closing(true);
362  }
363  catch (const std::exception& e)
364  {
365  std::cerr << "[mvsim_server_thread_update_GUI] Exception: " << e.what() << std::endl;
366  }
367 }
TThreadParams
Definition: mvsim-cli-launch.cpp:26
mvsim_launch_handle_teleop
std::string mvsim_launch_handle_teleop(const mvsim::World::TGUIKeyEvent keyevent, const std::optional< mvsim::TJoyStickEvent > &js)
Definition: mvsim-cli-launch.cpp:105
mvsim
Definition: Client.h:21
mvsim::World::VehicleList
std::multimap< std::string, VehicleBase::Ptr > VehicleList
Definition: World.h:291
mvsim_server_thread_headless
static void mvsim_server_thread_headless(TThreadParams &thread_params)
Definition: mvsim-cli-launch.cpp:340
mvsim::World::internalGraphicsLoopTasksForSimulation
void internalGraphicsLoopTasksForSimulation()
Definition: World_gui.cpp:1175
TThreadParams::closingMtx
std::mutex closingMtx
Definition: mvsim-cli-launch.cpp:29
s
XmlRpcServer s
mvsim_launch_shutdown
void mvsim_launch_shutdown()
Definition: mvsim-cli-launch.cpp:68
World.h
mvsim::ControllerBaseInterface::TeleopOutput::append_gui_lines
std::string append_gui_lines
Definition: ControllerBase.h:35
mvsim_server_thread_update_GUI
static void mvsim_server_thread_update_GUI(TThreadParams &thread_params)
Definition: mvsim-cli-launch.cpp:311
LaunchData::thread_params
TThreadParams thread_params
Definition: mvsim-cli-launch.cpp:61
TThreadParams::closing_
bool closing_
Definition: mvsim-cli-launch.cpp:48
gui_key_events
mvsim::World::TGUIKeyEvent gui_key_events
Definition: mvsim-cli-launch.cpp:54
commonLaunchServer
void commonLaunchServer()
Definition: mvsim-cli-server.cpp:23
launchSimulation
int launchSimulation()
Definition: mvsim-cli-launch.cpp:161
mvsim::World::TUpdateGUIParams::msg_lines
std::string msg_lines
Messages to show.
Definition: World.h:182
LaunchData::thGUI
std::thread thGUI
Definition: mvsim-cli-launch.cpp:62
rapidxml::file
Represents data loaded from a file.
Definition: rapidxml_utils.hpp:21
TThreadParams::world
mvsim::World * world
Definition: mvsim-cli-launch.cpp:28
app
std::optional< LaunchData > app
Definition: mvsim-cli-launch.cpp:66
rapidxml::file::data
Ch * data()
Definition: rapidxml_utils.hpp:65
mvsim_signal_handler
void mvsim_signal_handler(int s)
Definition: mvsim-cli-launch.cpp:86
mvsim::ControllerBaseInterface::TeleopOutput
Definition: ControllerBase.h:33
mvsim::World::TUpdateGUIParams
Definition: World.h:179
mvsim::World::TUpdateGUIParams::keyevent
TGUIKeyEvent keyevent
Keystrokes in the window are returned here.
Definition: World.h:181
rapidxml_utils.hpp
mvsim_install_signal_handler
void mvsim_install_signal_handler()
Definition: mvsim-cli-launch.cpp:93
mvsim::World
Definition: World.h:82
mvsim::World::update_GUI
void update_GUI(TUpdateGUIParams *params=nullptr)
Definition: World_gui.cpp:1006
gui_key_events_mtx
std::mutex gui_key_events_mtx
Definition: mvsim-cli-launch.cpp:55
TThreadParams::closing
void closing(bool v)
Definition: mvsim-cli-launch.cpp:40
GLFW_KEY_ESCAPE
#define GLFW_KEY_ESCAPE
Definition: glfw3.h:412
mvsim::ControllerBaseInterface
Definition: ControllerBase.h:22
LaunchData::teleopIdxVeh
size_t teleopIdxVeh
Definition: mvsim-cli-launch.cpp:63
mvsim::ControllerBaseInterface::teleop_interface
virtual void teleop_interface([[maybe_unused]] const TeleopInput &in, [[maybe_unused]] TeleopOutput &out)
Definition: ControllerBase.h:38
TThreadParams::isClosing
bool isClosing()
Definition: mvsim-cli-launch.cpp:33
mvsim::World::get_simul_timestep
double get_simul_timestep() const
Simulation fixed-time interval for numerical integration.
Definition: World_simul.cpp:171
mvsim::World::TGUIKeyEvent
Definition: World.h:168
mvsim::ControllerBaseInterface::TeleopInput
Definition: ControllerBase.h:25
mvsim-cli.h
LaunchData
Definition: mvsim-cli-launch.cpp:58
mvsim::ControllerBaseInterface::TeleopInput::js
std::optional< TJoyStickEvent > js
Definition: ControllerBase.h:28
mvsim::ControllerBaseInterface::TeleopInput::keycode
int keycode
Definition: ControllerBase.h:27
TThreadParams::TThreadParams
TThreadParams()=default
cli
std::unique_ptr< cli_flags > cli
Definition: mvsim-cli-main.cpp:24
mvsim::World::simulator_must_close
bool simulator_must_close() const
Definition: World.h:252
mvsim::World::TGUIKeyEvent::keycode
int keycode
0=no Key. Otherwise, ASCII code.
Definition: World.h:170
LaunchData::world
mvsim::World world
Definition: mvsim-cli-launch.cpp:60
msg2gui
std::string msg2gui
Definition: mvsim-cli-launch.cpp:56


mvsim
Author(s):
autogenerated on Wed May 28 2025 02:13:08