mvsim-cli-launch.cpp
Go to the documentation of this file.
1 /*+-------------------------------------------------------------------------+
2  | MultiVehicle simulator (libmvsim) |
3  | |
4  | Copyright (C) 2014-2023 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)
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\n",
115  static_cast<unsigned>(app->teleopIdxVeh + 1),
116  static_cast<unsigned>(vehs.size()));
117  if (vehs.size() > app->teleopIdxVeh)
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  // Get speed: ground truth
124  {
125  const mrpt::math::TTwist2D& vel =
126  it_veh->second->getVelocityLocal();
127  txt2gui_tmp += mrpt::format(
128  "gt. vel: lx=%7.03f, ly=%7.03f, w= %7.03fdeg/s\n", vel.vx,
129  vel.vy, mrpt::RAD2DEG(vel.omega));
130  }
131  // Get speed: ground truth
132  {
133  const mrpt::math::TTwist2D& vel =
134  it_veh->second->getVelocityLocalOdoEstimate();
135  txt2gui_tmp += mrpt::format(
136  "odo vel: lx=%7.03f, ly=%7.03f, w= %7.03fdeg/s\n", vel.vx,
137  vel.vy, mrpt::RAD2DEG(vel.omega));
138  }
139 
140  // Generic teleoperation interface for any controller that
141  // supports it:
142  {
143  ControllerBaseInterface* controller =
144  it_veh->second->getControllerInterface();
147  teleop_in.keycode = keyevent.keycode;
148  controller->teleop_interface(teleop_in, teleop_out);
149  txt2gui_tmp += teleop_out.append_gui_lines;
150  }
151  }
152 
153  return txt2gui_tmp;
154 }
155 
157 {
158  using namespace mvsim;
159 
160  // check args:
161  bool badArgs = false;
162  const auto& unlabeledArgs = cli->argCmd.getValue();
163  if (unlabeledArgs.size() != 2) badArgs = true;
164 
165  if (cli->argHelp.isSet() || badArgs)
166  {
167  fprintf(
168  stdout,
169  R"XXX(Usage: mvsim launch <WORLD_MODEL.xml> [options]
170 
171 Available options:
172  --headless Launch without GUI (e.g. suitable for dockerized envs.)
173  --full-profiler Enable full profiling (generates file with all timings)
174  --realtime-factor <1.0> Run slower (<1) or faster (>1) than real time if !=1.0
175  -v, --verbosity Set verbosity level: DEBUG, INFO (default), WARN, ERROR
176 )XXX");
177  return 0;
178  }
179 
180  // Handle CTRL+C:
182 
183  const auto verbosityLevel =
184  mrpt::typemeta::TEnumType<mrpt::system::VerbosityLevel>::name2value(
185  cli->argVerbosity.getValue());
186 
187  if (verbosityLevel <= mrpt::system::LVL_INFO)
188  {
189  mrpt::system::consoleColorAndStyle(
190  mrpt::system::ConsoleForegroundColor::BRIGHT_YELLOW);
191  std::cout //
192  << "\n"
193  << "====================================================\n"
194  << " MVSIM simulator running. Press CTRL+C to end. \n"
195  << "====================================================\n"
196  << "\n";
197  mrpt::system::consoleColorAndStyle(
198  mrpt::system::ConsoleForegroundColor::DEFAULT);
199  }
200 
201  const auto sXMLfilename = unlabeledArgs.at(1);
202 
203  app.emplace();
204 
205  app->world.setMinLoggingLevel(verbosityLevel);
206 
207  // CLI flags:
208  if (cli->argFullProfiler.isSet())
209  app->world.getTimeLogger().enableKeepWholeHistory();
210 
211  if (cli->argHeadless.isSet()) app->world.headless(true);
212 
213  // Load from XML:
214  try
215  {
216  rapidxml::file<> fil_xml(sXMLfilename.c_str());
217  app->world.load_from_XML(fil_xml.data(), sXMLfilename.c_str());
218  }
219  catch (const std::exception& e)
220  {
221  std::cerr << "Error: " << e.what() << std::endl;
223  return 1;
224  }
225 
226  // Start network server:
228 
229  // Attach world as a mvsim communications node:
230  app->world.connectToServer();
231 
232  // Launch GUI thread, unless we are in headless mode:
233  app->thread_params.world = &app->world;
234 
235  if (!cli->argHeadless.isSet())
236  {
237  // regular GUI:
238  app->thGUI = std::thread(
239  &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(
245  &mvsim_server_thread_headless, std::ref(app->thread_params));
246  }
247 
248  // Run simulation:
249  const double tAbsInit = mrpt::Clock::nowDouble();
250  const double rtFactor = cli->argRealTimeFactor.getValue();
251  bool doExit = false;
252 
253  while (!doExit)
254  {
255  // was the quit button hit in the GUI?
256  if (app->world.simulator_must_close()) break;
257 
258  // Simulation
259  // ============================================================
260  // Compute how much time has passed to simulate in real-time:
261  double tNew = mrpt::Clock::nowDouble();
262  double incrTime =
263  rtFactor * (tNew - tAbsInit) - app->world.get_simul_time();
264  int incrTimeSteps = static_cast<int>(
265  std::floor(incrTime / app->world.get_simul_timestep()));
266 
267  // Simulate:
268  if (incrTimeSteps > 0)
269  {
270  app->world.run_simulation(
271  incrTimeSteps * app->world.get_simul_timestep());
272  }
273 
274  std::this_thread::sleep_for(std::chrono::milliseconds(10));
275 
276  // GUI msgs, teleop, etc.
277  // ====================================================
278  gui_key_events_mtx.lock();
280  gui_key_events_mtx.unlock();
281 
282  // Global keys:
283  switch (keyevent.keycode)
284  {
285  case GLFW_KEY_ESCAPE:
286  doExit = true;
287  break;
288  case '1':
289  case '2':
290  case '3':
291  case '4':
292  case '5':
293  case '6':
294  case '7':
295  case '8':
296  case '9':
297  app->teleopIdxVeh = keyevent.keycode - '1';
298  break;
299  };
300 
301  const auto txt2gui_tmp = mvsim_launch_handle_teleop(keyevent);
302 
303  // Clear the keystroke buffer
304  gui_key_events_mtx.lock();
305  if (keyevent.keycode != 0) gui_key_events = World::TGUIKeyEvent();
306  gui_key_events_mtx.unlock();
307 
308  msg2gui = txt2gui_tmp; // send txt msgs to show in the GUI
309 
310  if (app->thread_params.isClosing()) doExit = true;
311 
312  } // end while()
313 
315 
316  return 0;
317 }
318 
320 {
321  try
322  {
323  ASSERT_(thread_params.world);
324  while (!thread_params.isClosing())
325  {
327  guiparams.msg_lines = msg2gui;
328 
329  thread_params.world->update_GUI(&guiparams);
330 
331  // Send key-strokes to the main thread:
332  if (guiparams.keyevent.keycode != 0)
333  {
334  gui_key_events_mtx.lock();
335  gui_key_events = guiparams.keyevent;
336  gui_key_events_mtx.unlock();
337  }
338 
339  std::this_thread::sleep_for(std::chrono::milliseconds(25));
340  }
341  }
342  catch (const std::exception& e)
343  {
344  std::cerr << "[mvsim_server_thread_update_GUI] Exception: " << e.what()
345  << std::endl;
346  }
347 }
348 
350 {
351  try
352  {
353  ASSERT_(thread_params.world);
354  while (!thread_params.isClosing() &&
355  !thread_params.world->simulator_must_close())
356  {
358 
359  std::this_thread::sleep_for(std::chrono::microseconds(mrpt::round(
360  thread_params.world->get_simul_timestep() * 1000000)));
361  }
362 
363  // in case we are here due to simulator_must_close()
364  thread_params.closing(true);
365  }
366  catch (const std::exception& e)
367  {
368  std::cerr << "[mvsim_server_thread_update_GUI] Exception: " << e.what()
369  << std::endl;
370  }
371 }
void commonLaunchServer()
std::mutex gui_key_events_mtx
bool simulator_must_close() const
Definition: World.h:242
Represents data loaded from a file.
mvsim::World * world
static void mvsim_server_thread_headless(TThreadParams &thread_params)
mvsim::World::TGUIKeyEvent gui_key_events
void closing(bool v)
std::multimap< std::string, VehicleBase::Ptr > VehicleList
Definition: World.h:281
TThreadParams thread_params
std::string msg2gui
TThreadParams()=default
TGUIKeyEvent keyevent
Keystrokes in the window are returned here.
Definition: World.h:163
std::string mvsim_launch_handle_teleop(const mvsim::World::TGUIKeyEvent keyevent)
std::thread thGUI
double get_simul_timestep() const
Simulation fixed-time interval for numerical integration.
Definition: World.cpp:333
#define GLFW_KEY_ESCAPE
Definition: glfw3.h:412
std::string msg_lines
Messages to show.
Definition: World.h:164
void mvsim_install_signal_handler()
int launchSimulation()
std::unique_ptr< cli_flags > cli
void mvsim_launch_shutdown()
std::optional< LaunchData > app
std::mutex closingMtx
void update_GUI(TUpdateGUIParams *params=nullptr)
Definition: World_gui.cpp:855
virtual void teleop_interface([[maybe_unused]] const TeleopInput &in, [[maybe_unused]] TeleopOutput &out)
void mvsim_signal_handler(int s)
void internalGraphicsLoopTasksForSimulation()
Definition: World_gui.cpp:1036
mvsim::World world
static void mvsim_server_thread_update_GUI(TThreadParams &thread_params)
int keycode
0=no Key. Otherwise, ASCII code.
Definition: World.h:152


mvsim
Author(s):
autogenerated on Tue Jul 4 2023 03:08:21