ros2_control - rolling
Loading...
Searching...
No Matches
mujoco_simulation.hpp
1
20#pragma once
21
22#include <atomic>
23#include <condition_variable>
24#include <functional>
25#include <memory>
26#include <mutex>
27#include <string>
28#include <thread>
29#include <vector>
30
31#include <rclcpp/rclcpp.hpp>
32#include <realtime_tools/realtime_publisher.hpp>
33#include <rosgraph_msgs/msg/clock.hpp>
34
35#include "glfw_adapter.h" // for mj::GlfwAdapter
36#include "simulate.h" // must be on your include path, handled by CMake
37
38#include <mujoco/mujoco.h>
39
40#include <geometry_msgs/msg/pose_stamped.hpp>
41#include <geometry_msgs/msg/twist_stamped.hpp>
42#include <mujoco_ros2_control_msgs/msg/free_joint_state.hpp>
43#include <mujoco_ros2_control_msgs/msg/simulation_state.hpp>
44#include <mujoco_ros2_control_msgs/srv/reset_world.hpp>
45#include <mujoco_ros2_control_msgs/srv/set_free_joint_state.hpp>
46#include <mujoco_ros2_control_msgs/srv/set_pause.hpp>
47#include <mujoco_ros2_control_msgs/srv/step_simulation.hpp>
48#include <mujoco_ros2_control_plugins/mujoco_ros2_control_plugins_base.hpp>
49#include <sensor_msgs/msg/joint_state.hpp>
50
51namespace mujoco_ros2_control
52{
53
106{
107public:
118 using ResetCallback = std::function<void(bool fill_initial_state)>;
119
123 MujocoSimulation() = default;
124
126
133 bool initialize(rclcpp::Node::SharedPtr node, const std::string& model_path, const std::string& mujoco_model_topic,
134 double sim_speed_factor, bool headless);
135
142 bool apply_keyframe(const std::string& keyframe_name);
143
151
155 void set_reset_callback(ResetCallback callback);
156
161
165 void shutdown();
166
170 mjModel* model()
171 {
172 return mj_model_;
173 }
174
182 mjData* data()
183 {
184 return mj_data_;
185 }
186
192 void reset_world_state(bool fill_initial_state);
193
205 bool set_free_joint_states(const std::vector<mujoco_ros2_control_msgs::msg::FreeJointState>& free_joints,
206 std::string& error_message);
207
213 void copy_physics_model(mjModel*& destination);
214
220 void overwrite_physics_data(mjData* source);
221
229 void copy_physics_data(mjData*& destination);
230
248 mjData* acquire_data_snapshot();
249
259 {
260 mjtNum time{ 0.0 };
261 std::vector<mjtNum> qpos;
262 std::vector<mjtNum> qvel;
263 std::vector<mjtNum> act;
264 std::vector<mjtNum> qfrc_actuator;
265 std::vector<mjtNum> sensordata;
266 std::vector<mjtNum> ctrl;
267 };
268
276 void copy_control_state(ControlState& destination);
277
290 void apply_control_data(mjData* control_data);
291
295 std::recursive_mutex& mutex() const
296 {
297 RCLCPP_WARN_EXPRESSION(logger_, sim_mutex_ == nullptr, "Sim recursive mutex is still nullptr");
298 return *sim_mutex_;
299 }
300
306 uint64_t step_count() const
307 {
308 return step_count_.load();
309 }
310
311private:
317 void apply_staged_control_inputs();
318
325 void publish_control_state();
326
334 void refresh_data_snapshot();
335
339 void physics_loop();
340
344 void publish_clock();
345
349 void update_sim_display();
350
361 void reset_world_state(bool fill_initial_state, const mujoco_ros2_control_msgs::msg::SimulationState& state_overrides);
362
370 int frame_body_id(const std::string& frame_id) const;
371
378 bool validate_frame_id(const std::string& frame_id, const std::string& field_label, std::string& error_message) const;
379
383 int find_free_joint_id(int body_id) const;
384
393 bool validate_free_joint_states(const std::vector<mujoco_ros2_control_msgs::msg::FreeJointState>& free_joints,
394 std::string& error_message);
395
404 void apply_free_joint_states(const std::vector<mujoco_ros2_control_msgs::msg::FreeJointState>& free_joints);
405
416 bool validate_joint_state_overrides(const sensor_msgs::msg::JointState& joint_state, std::string& error_message);
417
424 void apply_joint_state_overrides(const sensor_msgs::msg::JointState& joint_state);
425
426 // Service callbacks
427 void reset_world_callback(const std::shared_ptr<mujoco_ros2_control_msgs::srv::ResetWorld::Request> request,
428 std::shared_ptr<mujoco_ros2_control_msgs::srv::ResetWorld::Response> response);
429 void set_pause_callback(const std::shared_ptr<mujoco_ros2_control_msgs::srv::SetPause::Request> request,
430 std::shared_ptr<mujoco_ros2_control_msgs::srv::SetPause::Response> response);
431 void step_simulation_callback(const std::shared_ptr<mujoco_ros2_control_msgs::srv::StepSimulation::Request> request,
432 std::shared_ptr<mujoco_ros2_control_msgs::srv::StepSimulation::Response> response);
433 void
434 set_free_joint_state_callback(const std::shared_ptr<mujoco_ros2_control_msgs::srv::SetFreeJointState::Request> request,
435 std::shared_ptr<mujoco_ros2_control_msgs::srv::SetFreeJointState::Response> response);
436
437 rclcpp::Logger get_logger() const
438 {
439 return logger_;
440 }
441
442 // Logger
443 rclcpp::Logger logger_ = rclcpp::get_logger("MujocoSimulation");
444
445 // ROS node (owned by the HW interface, used here for services and clock publisher).
446 rclcpp::Node::SharedPtr node_;
447
448 // System information
449 std::string model_path_;
450 std::string mujoco_model_topic_;
451
452 // MuJoCo data pointers, these are the primary containers used by the physics simulation.
453 // It is generally not recommended to interact with them directly.
454 mjModel* mj_model_{ nullptr };
455
456 // Primary data container for the physics loop. We do not recommend interacting with this
457 // directly unless you are sure of what you are doing.
458 mjData* mj_data_{ nullptr };
459
460 // Double-buffered snapshot of mj_data_: the physics loop pays for all scene-sized copies,
461 // the consumer borrows the result via acquire_data_snapshot() and performs no copy at all.
462 // The producer (any writer holding the sim mutex) fills snapshot_write_ outside any shared
463 // lock and raises snapshot_ready_; the consumer, when the flag is up, swaps the two pointers
464 // under data_exchange_mutex_ and reads snapshot_read_ in place.
465 mjData* snapshot_write_{ nullptr };
466 mjData* snapshot_read_{ nullptr };
467 bool snapshot_ready_{ false };
468
469 // Control inputs staged by apply_control_data, applied to mj_data_ before each step.
470 std::vector<mjtNum> ctrl_staged_;
471 std::vector<mjtNum> qfrc_applied_staged_;
472
473 // False until apply_control_data is first called (and cleared on reset), so that initial /
474 // reset ctrl values in mj_data_ are not clobbered by stale staging buffers.
475 bool control_inputs_staged_{ false };
476
477 // Buffers to track actively applied Cartesian forces from both the plugins and the Simulate /
478 // viewer-only drag forces.
479 std::vector<mjtNum> xfrc_plugin_desired_; // Tracks forces from plugins
480 std::vector<mjtNum> xfrc_viewer_capture_; // Tracks forces from the viewer
481 std::vector<mjtNum> xfrc_last_written_; // tracks the last value written to xfrc_applied
482
483 // qvel as staged by apply_control_data: sized nv, NaN by default. A plugin requests a
484 // velocity override on a DOF by writing a finite value into control_data->qvel during its
485 // update()
486 std::vector<mjtNum> qvel_override_staged_;
487
488 // Guards only the snapshot pointer swap and snapshot_ready_ flag.
489 // Lock order: sim_mutex_ (if needed) is always taken before this one.
490 std::mutex data_exchange_mutex_;
491
492 // Set by acquire_data_snapshot when a consumer takes the snapshot; the physics loop only
493 // runs the expensive full refresh when this is set, so refresh bandwidth tracks consumer
494 // demand instead of the batch rate. Starts true so the first refresh happens.
495 std::atomic<bool> snapshot_refresh_requested_{ true };
496
497 // Guards the staged control inputs (ctrl_staged_, qfrc_applied_staged_, xfrc_plugin_desired_,
498 // control_inputs_staged_, qvel_override_staged_). Separate from data_exchange_mutex_ so
499 // that staging commands in write() and applying them before each physics step never queue
500 // behind a full mjData copy.
501 // Critical sections are all small buffer copies.
502 // Lock order: sim_mutex_ (if needed) before this one; never held with data_exchange_mutex_.
503 std::mutex control_staging_mutex_;
504
505 // Per-step control state served by copy_control_state. Guarded by its own mutex, separate
506 // from data_exchange_mutex_, so the reduced control-state copies never queue behind a full
507 // mjData snapshot copy.
508 // Lock order: sim_mutex_ (if needed) before this one; never held with data_exchange_mutex_.
509 ControlState control_state_;
510 std::mutex control_state_mutex_;
511
512 // For rendering
513 mjvCamera cam_;
514 mjvOption opt_;
515 mjvPerturb pert_;
516
517 // Speed scaling parameter. if set to >0 then we ignore the value set in the simulate app and instead
518 // attempt to loop at whatever this is set to. If this is <0, then we use the value from the app.
519 double sim_speed_factor_{ -1.0 };
520
521 // True when running without a display (no GLFW window)
522 bool headless_{ false };
523
524 // Primary simulate object
525 std::unique_ptr<mujoco::Simulate> sim_;
526
527 // Threads for rendering physics and the UI simulation
528 std::thread physics_thread_;
529 std::thread ui_thread_;
530
531 // Primary clock publisher for the world
532 std::shared_ptr<rclcpp::Publisher<rosgraph_msgs::msg::Clock>> clock_publisher_;
534
535 // Mutex used inside simulate.h for protecting model/data, we keep a reference
536 // here to protect access to shared data.
537 // TODO: It would be far better to put all relevant data into a single container with accessors
538 // in a common location rather than passing around the raw pointer to the mutex, but it would
539 // require more work to pull it out of simulate.h.
540 std::recursive_mutex* sim_mutex_{ nullptr };
541
542 // Reset world service
543 rclcpp::CallbackGroup::SharedPtr reset_world_cb_group_;
544 rclcpp::Service<mujoco_ros2_control_msgs::srv::ResetWorld>::SharedPtr reset_world_service_;
545
546 // Set pause service
547 rclcpp::CallbackGroup::SharedPtr set_pause_cb_group_;
548 rclcpp::Service<mujoco_ros2_control_msgs::srv::SetPause>::SharedPtr set_pause_service_;
549
550 // Step simulation service
551 rclcpp::CallbackGroup::SharedPtr step_simulation_cb_group_;
552 rclcpp::Service<mujoco_ros2_control_msgs::srv::StepSimulation>::SharedPtr step_simulation_service_;
553
554 // Set free joint state service (teleport/reset a free-joint object's pose)
555 rclcpp::CallbackGroup::SharedPtr set_free_joint_state_cb_group_;
556 rclcpp::Service<mujoco_ros2_control_msgs::srv::SetFreeJointState>::SharedPtr set_free_joint_state_service_;
557
558 // Pending steps to execute while paused, and synchronization for blocking callers
559 std::atomic<uint32_t> pending_steps_{ 0 };
560 std::atomic<bool> step_diverged_{ false };
561 std::atomic<bool> steps_interrupted_{ false };
562 std::atomic<bool> keyboard_step_requested_{ false };
563 std::atomic<uint64_t> step_count_{ 0 };
564 std::mutex steps_cv_mutex_;
565 std::condition_variable steps_cv_;
566
567 // Storage for initial state (used for reset_world)
568 std::vector<mjtNum> initial_qpos_;
569 std::vector<mjtNum> initial_qvel_;
570 std::vector<mjtNum> initial_ctrl_;
571
572 // Callback into the HW interface to perform component-side reset bookkeeping.
573 ResetCallback reset_callback_;
574};
575
576} // namespace mujoco_ros2_control
ROS 2-based container for the mujoco Simulate application.
Definition mujoco_simulation.hpp:106
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.
Definition mujoco_simulation.cpp:1242
void capture_initial_state()
Can be called by consumers of this class to store the current state as the "initial" state.
Definition mujoco_simulation.cpp:758
void reset_world_state(bool fill_initial_state)
Reset simulation state (qpos/qvel/ctrl/sensors/forces) to the captured initial state.
Definition mujoco_simulation.cpp:827
std::recursive_mutex & mutex() const
Accessor for the mutex which locks access to the data and model.
Definition mujoco_simulation.hpp:295
void start_physics_thread()
Start the physics thread. Must be called after load_model().
Definition mujoco_simulation.cpp:771
std::function< void(bool fill_initial_state)> ResetCallback
Callback invoked when the simulation's state must be reset.
Definition mujoco_simulation.hpp:118
void apply_control_data(mjData *control_data)
Stages control fields from control_data for the physics loop in a thread safe way.
Definition mujoco_simulation.cpp:1337
mjData * acquire_data_snapshot()
Borrows the latest completed post-step snapshot of mj_data_ (producer-pays copying).
Definition mujoco_simulation.cpp:1317
void copy_physics_data(mjData *&destination)
Copies mj_data_ into the provided container in a thread safe way.
Definition mujoco_simulation.cpp:1302
void shutdown()
Stop the physics and UI threads if they are running.
Definition mujoco_simulation.cpp:808
bool apply_keyframe(const std::string &keyframe_name)
Apply a keyframe to the simulation by name.
Definition mujoco_simulation.cpp:740
void copy_control_state(ControlState &destination)
Copies the latest per-step control state into the provided container.
Definition mujoco_simulation.cpp:1376
void copy_physics_model(mjModel *&destination)
Copies mj_model_ into the provided container in a thread safe way.
Definition mujoco_simulation.cpp:1288
MujocoSimulation()=default
Construct a new Mujoco Simulation object. This is a no-op until initialization.
mjModel * model()
Accessor for the mujoco model.
Definition mujoco_simulation.hpp:170
mjData * data()
Accessor for the raw mujoco simulation data.
Definition mujoco_simulation.hpp:182
uint64_t step_count() const
Returns the number of steps takein by the physics simulation.
Definition mujoco_simulation.hpp:306
void overwrite_physics_data(mjData *source)
Copies the provided mjData into mj_data_ in a thread safe way.
Definition mujoco_simulation.cpp:1294
void set_reset_callback(ResetCallback callback)
Register a callback function to be called on reset_world_state.
Definition mujoco_simulation.cpp:766
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).
Definition mujoco_simulation.cpp:530
Definition realtime_publisher.hpp:55
Definition data.hpp:31
Small snapshot of the state the hardware interface needs every control cycle.
Definition mujoco_simulation.hpp:259