A wrapper to execute the Drake simulator as a back-end.
This class exposes some of the functionality of the simulator via Ignition Transport services (e.g.: play/pause/step the simulation). At the same time, it has to periodically advance the simulation a certain amount of time. Finally, it notifies any given state changes to the outside world via Ignition Transport messages. All these operations occur in a main loop with the following structure:
- Process incoming messages: All incoming requests are queued asynchronously when received and in this step we process all of them. This request might involve flag the simulation as paused, insert a new model, remove an existing model, etc.
- Step the simulation: We advance the simulation a certain amount of time. The amount of time could be the default time step (if we are in play mode), a custom time step (if we are externally stepping) or no time at all if the simulation is paused.
- Notify changes in the simulation. The simulation will send messages over a a well-known topic every time that a relevant simulation state changed occur. E.g.: When the simulation is paused or a model is removed. These notifications should be used by all the Visualizer instances to update its state/widgets in order to keep them in sync with the back-end.
- Provides a mechanism to register callbacks to be triggered on each simulation step. Since this was originally conceived to be used with the Python bindings and plain typedef-based functions are not supported (see http://www.boost.org/doc/libs/1_65_1/libs/python/doc/html/faq.html) we need to pass a PyObject as an argument.
Finally, it is important to clarify how the Python-C++ callback mechanism is implemented, so we can understand a possible deadlock. The key elements here are:
- A typical main program will create a SimulatorRunner instance, configure it and call Start(). Let's call the thread the program runs on the MainThread.
- Calling Start() will in turn spawn a new thread. This thread will execute a tight loop (see the
Run()
method), making the simulation itself move forward. We will call this thread the RunThread.
- When the SimulatorRunner destructor is called, it will try to join the RunThread into the MainThread, to perform a clean shutdown. To do that a boolean flag is set to exit the tight loop in
Run()
and the join()
method is called on the RunThread, effectively blocking the MainThread until RunThread is done.
- When calling a Python-defined callback function from C++ we need to acquire the GIL (Python's Global Interpreter Lock), which basically gives us thread-safety between the C++ and Python worlds. The typical steps to invoke a Python callback from C++ are:
- Acquire the GIL.
- Invoke the callback.
- Release the GIL. Note that acquiring the GIL is a potentially blocking call, as it is basically getting the lock of a mutex.
Now, the following interleaving may occur when running a python-scripted simulation:
- Create the simulator runner and configure a python callback.
- Start the simulator. By this time we have the MainThread (which is the same thread that executes the python code) and the RunThread executing.
Stop the simulator from the MainThread (calling the Stop()
method) and consider the following events:
- The RunThread yielded the processor right before acquiring the GIL.
- The MainTread execute the
Stop()
method and then the destructor. Now the MainThread is waiting for RunThread to join.
- Execution is now returned to RunThread, which tries to acquire the GIL. However, the Python thread is blocked on the
join()
, so we are effectively in a deadlock.
It is interesting to note however that the lock on the GIL happens only if the code is blocked on the C++ side; in other words, if the code was blocked on the Python side (e.g. due to a sleep
call), C++ has no problem to acquire the GIL. Because of this, the current recommendation is to add a sleep after calling stop()
from the Python side, so the processor is yielded and the RunThread can finish its current (and last) loop before exiting the while.
|
| SimulationRunner (std::unique_ptr< AgentSimulation > sim, double time_step, double realtime_rate, bool paused, bool log, std::string logfile_name) |
| Default constructor. More...
|
|
| SimulationRunner (std::unique_ptr< AgentSimulation > sim, double time_step, bool paused, bool log) |
| Simplified constructor that runs the simulation at a real-time rate of 1.0. More...
|
|
| SimulationRunner (std::unique_ptr< AgentSimulation > sim, double time_step, bool paused, bool log, std::string logfile_name) |
| Simplified constructor that runs the simulation at a real-time rate of 1.0. More...
|
|
| SimulationRunner (std::unique_ptr< AgentSimulation > sim, double time_step, double realtime_rate) |
| Simplified constructor that starts the simulator with _paused = false, and log = true. More...
|
|
| SimulationRunner (std::unique_ptr< AgentSimulation > sim, double time_step) |
| Simplified constructor that runs the simulation with _paused = false, a real-time rate of 1.0, and log = true. More...
|
|
virtual | ~SimulationRunner () |
| Default destructor. More...
|
|
void | AddStepCallback (std::function< void()> callable) |
| Adds a python callback to be invoked on each simulation step. More...
|
|
void | AddCollisionCallback (CollisionCallback callable) |
| Adds a callback to be invoked on agent collision. More...
|
|
void | Start () |
| Spawns a new thread that runs the interactive simulation loop. More...
|
|
void | RunAsyncFor (double duration, std::function< void()> callback) |
| Spawns a new thread that runs the interactive simulation loop for the provided time period. More...
|
|
void | RunSyncFor (double duration) |
| Runs the interactive simulation loop for the provided time period. More...
|
|
void | Stop () |
| Stops the thread that is running the interactive simulation loop. More...
|
|
void | RequestSimulationStepExecution (unsigned int steps) |
| Enqueue a requests for a simulation step to be executed. More...
|
|
bool | IsInteractiveLoopRunning () const |
| Returns if the interactive simulation loop is currently running or not. More...
|
|
void | SetRealtimeRate (double realtime_rate) |
| See documentation of Simulator::set_target_realtime_rate() More...
|
|
double | GetRealtimeRate () const |
| See documentation of Simulator::get_target_realtime_rate() More...
|
|
bool | IsSimulationPaused () const |
| Returns the paused state of the simulation. More...
|
|
void | PauseSimulation () |
| Pauses the simulation, no-op if called multiple times. More...
|
|
void | UnpauseSimulation () |
| Unauses the simulation, no-op if called multiple times. More...
|
|
void | EnableCollisions () |
| Enables collisions during the simulation, no-op if called multiple times. More...
|
|
void | DisableCollisions () |
| Disables collisions during the simulation, no-op if called multiple times. More...
|
|
double | GetCurrentSimulationTime () const |
| Returns the current simulation time in seconds. More...
|
|
const AgentSimulation & | GetSimulation () const |
| Returns a reference to the simulation being run. More...
|
|
AgentSimulation * | GetMutableSimulation () |
| Returns a mutable reference to the simulation being run. More...
|
|
const InteractiveSimulationStats & | GetStats () const |
| Returns the collected interactive simulation statistics. More...
|
|
double | GetTimeStep () const |
| Returns the time step of the simulation runner. More...
|
|
bool | IsLogging () const |
| Returns the logging state. True indicates that logging is enabled. More...
|
|
void | StartLogging () |
| Start logging to a default named file. More...
|
|
void | StartLogging (const std::string &filename) |
| Start logging to a given file or path. More...
|
|
void | StopLogging () |
| Stop logging. More...
|
|
std::string | GetLogFilename () const |
| Get the log file name, or empty string if logging has not been started. More...
|
|