![]() |
ros2_control - humble
|
ROS 2-based container for the mujoco Simulate application. More...
#include <mujoco_simulation.hpp>
Classes | |
| struct | ControlState |
| Small snapshot of the state the hardware interface needs every control cycle. More... | |
Public Types | |
| using | ResetCallback = std::function< void(bool fill_initial_state)> |
| Callback invoked when the simulation's state must be reset. | |
| using | PreStepCallback = std::function< void(mjData *data)> |
Callback function type the set_pre_step_callback hook. | |
Public Member Functions | |
| MujocoSimulation ()=default | |
| Construct a new Mujoco Simulation object. This is a no-op until initialization. | |
| bool | initialize (rclcpp::Node::SharedPtr node, const std::string &model_path, const std::string &mujoco_model_topic, double sim_speed_factor, bool headless) |
| Construct the Simulate application and start the UI thread (if not headless). | |
| bool | apply_keyframe (const std::string &keyframe_name) |
| Apply a keyframe to the simulation by name. | |
| void | capture_initial_state () |
| Can be called by consumers of this class to store the current state as the "initial" state. | |
| void | set_reset_callback (ResetCallback callback) |
Register a callback function to be called on reset_world_state. | |
| void | set_pre_step_callback (PreStepCallback callback) |
Register the callback run before every mj_step(). | |
| void | start_physics_thread () |
| Start the physics thread. Must be called after load_model(). | |
| void | shutdown () |
| Stop the physics and UI threads if they are running. | |
| mjModel * | model () |
| Accessor for the mujoco model. | |
| mjData * | data () |
| Accessor for the raw mujoco simulation data. | |
| void | reset_world_state (bool fill_initial_state) |
| Reset simulation state (qpos/qvel/ctrl/sensors/forces) to the captured initial state. | |
| bool | set_free_joint_states (const std::vector< mujoco_ros2_control_msgs::msg::FreeJointState > &free_joints, std::string &error_message) |
| Sets the pose and velocity of one or more free-joint objects, identified by body name. | |
| void | copy_physics_model (mjModel *&destination) |
Copies mj_model_ into the provided container in a thread safe way. | |
| void | overwrite_physics_data (mjData *source) |
| Copies the provided mjData into mj_data_ in a thread safe way. | |
| void | copy_physics_data (mjData *&destination) |
Copies mj_data_ into the provided container in a thread safe way. | |
| mjData * | acquire_data_snapshot () |
Borrows the latest completed post-step snapshot of mj_data_ (producer-pays copying). | |
| void | copy_control_state (ControlState &destination) |
| Copies the latest per-step control state into the provided container. | |
| void | apply_control_data (mjData *control_data) |
Stages control fields from control_data for the physics loop in a thread safe way. | |
| std::recursive_mutex & | mutex () const |
| Accessor for the mutex which locks access to the data and model. | |
| uint64_t | step_count () const |
| Returns the number of steps takein by the physics simulation. | |
ROS 2-based container for the mujoco Simulate application.
This class wraps the MuJoCo simulation and Simulate application, while providing necessary hooks to the ros2_control system interface to enable interaction with the sim using "normal" ROS 2 constructs.
This class is responsible for mujoco model and data, along with threads for the main physics and rendering loops. It also provides several ROS interfaces for interacting with the underlying simulation - including publishing simulated time to /clock, as well as services for pausing, stepping, and resetting the simulation.
Importantly, the physics loop is intended to run at whatever speed (relative realtime) is requested by the user. It is important to not interrupt the loop with locking calls that interact with either the physics sim data, mj_data_, or the model, mj_model_. Instead, consumers of this class are provided with functions to read all sim data and provide control inputs from their own mjData containers.
The functions relevant to interacting with the physics sim's mjData are:
copy_physics_data(...) will lock the sim and do a full copy of the existing mj_data_ into the provided container, which can be used as the caller requires. Because the physics loop can hold the sim mutex for a large fraction of a display refresh while it batches steps, this can block the caller and should not be used from latency-sensitive threads.
acquire_data_snapshot() instead borrows the most recent completed post-step snapshot of mj_data_, produced by the physics loop into a separate buffer. Acquiring is a pointer swap under a mutex that is never held for longer than a swap, so the caller performs no scene-sized copy and never waits on physics stepping or refresh. The returned data may lag mj_data_ by a few timesteps. This is what the hardware interface uses in write().
apply_control_data(...) will copy control inputs from the provided mjData into staging buffers that the physics loop applies to mj_data_ immediately before each step. Specifically, it stages ctrl and qfrc_applied. Both of these values should come from the interfaces consuming this class. This This only takes the control staging mutex and never blocks on physics stepping.
overwrite_physics_data(...) will completely replace the data for the sim. Should be used with extreme caution.
set_pre_step_callback(...) registers a callback which will be triggered in the Physics Loop. It will be called immediately before mj_step(), providing direct access to mj_data_ immediately before progressing the simulation. This gives consumers direct access to access and modify the data immediately before integration. Users should be extremely careful with this callback, as it exposes "god like" powers to the simulation environment and can break, slow down, or otherwise damage the simulation. This should also be used with caution.
Thread safety is still somewhat messy, as callers are provided with a simulation mutex that locks the model and data while the actual mujoco engine moves the sim forward. Callers need to be wary of locking that mutex external to this class, as it can have significant consequences on the simulation's speed.
| using mujoco_ros2_control::MujocoSimulation::PreStepCallback = std::function<void(mjData* data)> |
Callback function type the set_pre_step_callback hook.
Called before mjStep in the physics loop, use with care.
| data | The data from the physics simulation, under thread lock. |
| using mujoco_ros2_control::MujocoSimulation::ResetCallback = std::function<void(bool fill_initial_state)> |
Callback invoked when the simulation's state must be reset.
Triggered by the ~/reset_world service or by a any other reset detected in the physics loop. The callback will be run with the sim mutex but after all data has been restored.
| fill_initial_state | When true, the caller has not already populated mj_data_->qpos/qvel/ctrl from a keyframe and the callback should restore the captured initial state. When false, a keyframe has already been applied. |
| mjData * mujoco_ros2_control::MujocoSimulation::acquire_data_snapshot | ( | ) |
Borrows the latest completed post-step snapshot of mj_data_ (producer-pays copying).
The physics loop fills snapshot buffers on its own thread; this call only swaps pointers to take ownership of the most recent completed one, so the caller never performs a scene-sized copy and never waits for a stepping batch,or in-process refresh. If no new snapshot has completed since the last call, the same buffer is returned again. Also requests a fresh snapshot for the next call.
Ownership contract: there is a single borrower slot. The returned buffer remains valid and is never touched by the physics loop until the next acquire_data_snapshot() call, at which point the previous buffer is recycled back to the producer. The borrower may freely write to the buffer (e.g., plugins composing control inputs); all such writes are discarded when the buffer is recycled and refilled. Only one consumer may use this API — concurrent callers would swap each other's buffer out from underneath them. Cold-path consumers that need their own copy should use copy_physics_data instead.
| void mujoco_ros2_control::MujocoSimulation::apply_control_data | ( | mjData * | control_data | ) |
Stages control fields from control_data for the physics loop in a thread safe way.
Specifically, copies control_data->ctrl and control_data->qfrc_applied into staging buffers which the physics loop copies into mj_data_ immediately before each step. These are the only two fields staged here because both are "held" quantities that persist correctly across a batch of steps. Anything else that requires direct access to simulation data can access it through the set_pre_step_callback functions.
| bool mujoco_ros2_control::MujocoSimulation::apply_keyframe | ( | const std::string & | keyframe_name | ) |
Apply a keyframe to the simulation by name.
This locks the simulation mutex and attempts to apply a keyframe by name by calling mj_resetDataKeyframe.
| void mujoco_ros2_control::MujocoSimulation::capture_initial_state | ( | ) |
Can be called by consumers of this class to store the current state as the "initial" state.
In particular, this persises qpos, qvel, and ctrl vectors from the data and writes them into our 'initial_*' vectors.
| void mujoco_ros2_control::MujocoSimulation::copy_control_state | ( | ControlState & | destination | ) |
Copies the latest per-step control state into the provided container.
The copy is under a dedicated mutex and is significantly faster than physics stepping or full-mjData copies. This is what the hardware interface uses in read() and write().
| void mujoco_ros2_control::MujocoSimulation::copy_physics_data | ( | mjData *& | destination | ) |
Copies mj_data_ into the provided container in a thread safe way.
This locks the sim mutex and will pause the physics loop, so should be used sparingly. Latency-sensitive callers should use acquire_data_snapshot or copy_control_state instead.
| void mujoco_ros2_control::MujocoSimulation::copy_physics_model | ( | mjModel *& | destination | ) |
Copies mj_model_ into the provided container in a thread safe way.
This locks the sim mutex and will pause the physics loop, so should be used sparingly.
|
inline |
Accessor for the raw mujoco simulation data.
Users should generally not interact with the physics sim data excepting during setup and other special circumstances. For "normal" processing it is recommended to use control_data or other containers populated by copy_mj_data.
| bool mujoco_ros2_control::MujocoSimulation::initialize | ( | rclcpp::Node::SharedPtr | node, |
| const std::string & | model_path, | ||
| const std::string & | mujoco_model_topic, | ||
| double | sim_speed_factor, | ||
| bool | headless | ||
| ) |
Construct the Simulate application and start the UI thread (if not headless).
This initializes the Simulate app and starts the UI thread in the background (if not running headless). It also sets up required publishers and services using the provided node.
| void mujoco_ros2_control::MujocoSimulation::overwrite_physics_data | ( | mjData * | source | ) |
Copies the provided mjData into mj_data_ in a thread safe way.
| void mujoco_ros2_control::MujocoSimulation::reset_world_state | ( | bool | fill_initial_state | ) |
Reset simulation state (qpos/qvel/ctrl/sensors/forces) to the captured initial state.
| bool mujoco_ros2_control::MujocoSimulation::set_free_joint_states | ( | const std::vector< mujoco_ros2_control_msgs::msg::FreeJointState > & | free_joints, |
| std::string & | error_message | ||
| ) |
Sets the pose and velocity of one or more free-joint objects, identified by body name.
Applied atomically: every entry is validated before anything is written, so a single invalid entry leaves the sim state unchanged. Duplicate body names are applied in order, so the last one wins. See FreeJointState.msg for per-entry fields.
| error_message | Set to a human-readable description (identifying the offending entry) if this returns false. |
| void mujoco_ros2_control::MujocoSimulation::set_pre_step_callback | ( | PreStepCallback | callback | ) |
Register the callback run before every mj_step().
See the class documentation for more information. Any function called here will hold the simulation mutex and block the physics loop. Use with care.
|
inline |
Returns the number of steps takein by the physics simulation.
Equivalent to the step counter that is shown in the simulate UI.