ros2_control - rolling
Loading...
Searching...
No Matches
mujoco_simulation.hpp
1
20#pragma once
21
22#include <algorithm>
23#include <atomic>
24#include <chrono>
25#include <cmath>
26#include <condition_variable>
27#include <functional>
28#include <memory>
29#include <mutex>
30#include <string>
31#include <thread>
32#include <vector>
33
34#include <rclcpp/rclcpp.hpp>
35#include <realtime_tools/realtime_publisher.hpp>
36#include <rosgraph_msgs/msg/clock.hpp>
37
38#include "glfw_adapter.h" // for mj::GlfwAdapter
39#include "simulate.h" // must be on your include path, handled by CMake
40
41#include <mujoco/mujoco.h>
42
43#include <geometry_msgs/msg/pose_stamped.hpp>
44#include <geometry_msgs/msg/twist_stamped.hpp>
45#include <mujoco_ros2_control_msgs/msg/free_joint_state.hpp>
46#include <mujoco_ros2_control_msgs/msg/simulation_state.hpp>
47#include <mujoco_ros2_control_msgs/srv/reset_world.hpp>
48#include <mujoco_ros2_control_msgs/srv/set_free_joint_state.hpp>
49#include <mujoco_ros2_control_msgs/srv/set_pause.hpp>
50#include <mujoco_ros2_control_msgs/srv/step_simulation.hpp>
51#include <mujoco_ros2_control_plugins/mujoco_ros2_control_plugins_base.hpp>
52#include <sensor_msgs/msg/joint_state.hpp>
53
54namespace mujoco_ros2_control
55{
56
57namespace
58{
59
60constexpr double kIdleSleepFraction = 0.2;
61
62} // namespace
63
73inline std::chrono::microseconds physics_loop_sleep_duration(double timestep)
74{
75 return std::min(std::chrono::microseconds(1000),
76 std::chrono::microseconds(static_cast<int64_t>(std::llround(timestep * kIdleSleepFraction * 1e6))));
77}
78
134{
135public:
146 using ResetCallback = std::function<void(bool fill_initial_state)>;
147
155 using PreStepCallback = std::function<void(mjData* data)>;
156
160 MujocoSimulation() = default;
161
163
170 bool initialize(rclcpp::Node::SharedPtr node, const std::string& model_path, const std::string& mujoco_model_topic,
171 double sim_speed_factor, bool headless);
172
179 bool apply_keyframe(const std::string& keyframe_name);
180
188
192 void set_reset_callback(ResetCallback callback);
193
201
206
210 void shutdown();
211
215 mjModel* model()
216 {
217 return mj_model_;
218 }
219
227 mjData* data()
228 {
229 return mj_data_;
230 }
231
237 void reset_world_state(bool fill_initial_state);
238
250 bool set_free_joint_states(const std::vector<mujoco_ros2_control_msgs::msg::FreeJointState>& free_joints,
251 std::string& error_message);
252
258 void copy_physics_model(mjModel*& destination);
259
265 void overwrite_physics_data(mjData* source);
266
274 void copy_physics_data(mjData*& destination);
275
293 mjData* acquire_data_snapshot();
294
304 {
305 mjtNum time{ 0.0 };
306 std::vector<mjtNum> qpos;
307 std::vector<mjtNum> qvel;
308 std::vector<mjtNum> act;
309 std::vector<mjtNum> qfrc_actuator;
310 std::vector<mjtNum> sensordata;
311 std::vector<mjtNum> ctrl;
312 };
313
321 void copy_control_state(ControlState& destination);
322
332 void apply_control_data(mjData* control_data);
333
337 std::recursive_mutex& mutex() const
338 {
339 RCLCPP_WARN_EXPRESSION(logger_, sim_mutex_ == nullptr, "Sim recursive mutex is still nullptr");
340 return *sim_mutex_;
341 }
342
348 uint64_t step_count() const
349 {
350 return step_count_.load();
351 }
352
353private:
359 void apply_staged_control_inputs();
360
367 void publish_control_state();
368
376 void refresh_data_snapshot();
377
381 void physics_loop();
382
386 void publish_clock();
387
391 void update_sim_display();
392
403 void reset_world_state(bool fill_initial_state, const mujoco_ros2_control_msgs::msg::SimulationState& state_overrides);
404
412 int frame_body_id(const std::string& frame_id) const;
413
420 bool validate_frame_id(const std::string& frame_id, const std::string& field_label, std::string& error_message) const;
421
425 int find_free_joint_id(int body_id) const;
426
435 bool validate_free_joint_states(const std::vector<mujoco_ros2_control_msgs::msg::FreeJointState>& free_joints,
436 std::string& error_message);
437
446 void apply_free_joint_states(const std::vector<mujoco_ros2_control_msgs::msg::FreeJointState>& free_joints);
447
458 bool validate_joint_state_overrides(const sensor_msgs::msg::JointState& joint_state, std::string& error_message);
459
466 void apply_joint_state_overrides(const sensor_msgs::msg::JointState& joint_state);
467
468 // Service callbacks
469 void reset_world_callback(const std::shared_ptr<mujoco_ros2_control_msgs::srv::ResetWorld::Request> request,
470 std::shared_ptr<mujoco_ros2_control_msgs::srv::ResetWorld::Response> response);
471 void set_pause_callback(const std::shared_ptr<mujoco_ros2_control_msgs::srv::SetPause::Request> request,
472 std::shared_ptr<mujoco_ros2_control_msgs::srv::SetPause::Response> response);
473 void step_simulation_callback(const std::shared_ptr<mujoco_ros2_control_msgs::srv::StepSimulation::Request> request,
474 std::shared_ptr<mujoco_ros2_control_msgs::srv::StepSimulation::Response> response);
475 void
476 set_free_joint_state_callback(const std::shared_ptr<mujoco_ros2_control_msgs::srv::SetFreeJointState::Request> request,
477 std::shared_ptr<mujoco_ros2_control_msgs::srv::SetFreeJointState::Response> response);
478
479 rclcpp::Logger get_logger() const
480 {
481 return logger_;
482 }
483
484 // Logger
485 rclcpp::Logger logger_ = rclcpp::get_logger("MujocoSimulation");
486
487 // ROS node (owned by the HW interface, used here for services and clock publisher).
488 rclcpp::Node::SharedPtr node_;
489
490 // System information
491 std::string model_path_;
492 std::string mujoco_model_topic_;
493
494 // MuJoCo data pointers, these are the primary containers used by the physics simulation.
495 // It is generally not recommended to interact with them directly.
496 mjModel* mj_model_{ nullptr };
497
498 // Primary data container for the physics loop. We do not recommend interacting with this
499 // directly unless you are sure of what you are doing.
500 mjData* mj_data_{ nullptr };
501
502 // Double-buffered snapshot of mj_data_: the physics loop pays for all scene-sized copies,
503 // the consumer borrows the result via acquire_data_snapshot() and performs no copy at all.
504 // The producer (any writer holding the sim mutex) fills snapshot_write_ outside any shared
505 // lock and raises snapshot_ready_; the consumer, when the flag is up, swaps the two pointers
506 // under data_exchange_mutex_ and reads snapshot_read_ in place.
507 mjData* snapshot_write_{ nullptr };
508 mjData* snapshot_read_{ nullptr };
509 bool snapshot_ready_{ false };
510
511 // Control inputs staged by apply_control_data, applied to mj_data_ before each step.
512 std::vector<mjtNum> ctrl_staged_;
513 std::vector<mjtNum> qfrc_applied_staged_;
514
515 // False until apply_control_data is first called (and cleared on reset), so that initial /
516 // reset ctrl values in mj_data_ are not clobbered by stale staging buffers.
517 bool control_inputs_staged_{ false };
518
519 // Guards only the snapshot pointer swap and snapshot_ready_ flag.
520 // Lock order: sim_mutex_ (if needed) is always taken before this one.
521 std::mutex data_exchange_mutex_;
522
523 // Set by acquire_data_snapshot when a consumer takes the snapshot; the physics loop only
524 // runs the expensive full refresh when this is set, so refresh bandwidth tracks consumer
525 // demand instead of the batch rate. Starts true so the first refresh happens.
526 std::atomic<bool> snapshot_refresh_requested_{ true };
527
528 // Guards the staged control inputs (ctrl_staged_, qfrc_applied_staged_,
529 // control_inputs_staged_). Separate from data_exchange_mutex_ so that staging commands in
530 // write() and applying them before each physics step never queue behind a full mjData copy.
531 // Critical sections are all small buffer copies.
532 // Lock order: sim_mutex_ (if needed) before this one; never held with data_exchange_mutex_.
533 std::mutex control_staging_mutex_;
534
535 // Per-step control state served by copy_control_state. Guarded by its own mutex, separate
536 // from data_exchange_mutex_, so the reduced control-state copies never queue behind a full
537 // mjData snapshot copy.
538 // Lock order: sim_mutex_ (if needed) before this one; never held with data_exchange_mutex_.
539 ControlState control_state_;
540 std::mutex control_state_mutex_;
541
542 // For rendering
543 mjvCamera cam_;
544 mjvOption opt_;
545 mjvPerturb pert_;
546
547 // Speed scaling parameter. if set to >0 then we ignore the value set in the simulate app and instead
548 // attempt to loop at whatever this is set to. If this is <0, then we use the value from the app.
549 double sim_speed_factor_{ -1.0 };
550
551 // True when running without a display (no GLFW window)
552 bool headless_{ false };
553
554 // Primary simulate object
555 std::unique_ptr<mujoco::Simulate> sim_;
556
557 // Threads for rendering physics and the UI simulation
558 std::thread physics_thread_;
559 std::thread ui_thread_;
560 // Distinguishes a programmatic RenderLoop wakeup from the user closing its window.
561 std::atomic<bool> explicit_shutdown_requested_{ false };
562
563 // Primary clock publisher for the world
564 std::shared_ptr<rclcpp::Publisher<rosgraph_msgs::msg::Clock>> clock_publisher_;
566
567 // Mutex used inside simulate.h for protecting model/data, we keep a reference
568 // here to protect access to shared data.
569 // TODO: It would be far better to put all relevant data into a single container with accessors
570 // in a common location rather than passing around the raw pointer to the mutex, but it would
571 // require more work to pull it out of simulate.h.
572 std::recursive_mutex* sim_mutex_{ nullptr };
573
574 // Reset world service
575 rclcpp::CallbackGroup::SharedPtr reset_world_cb_group_;
576 rclcpp::Service<mujoco_ros2_control_msgs::srv::ResetWorld>::SharedPtr reset_world_service_;
577
578 // Set pause service
579 rclcpp::CallbackGroup::SharedPtr set_pause_cb_group_;
580 rclcpp::Service<mujoco_ros2_control_msgs::srv::SetPause>::SharedPtr set_pause_service_;
581
582 // Step simulation service
583 rclcpp::CallbackGroup::SharedPtr step_simulation_cb_group_;
584 rclcpp::Service<mujoco_ros2_control_msgs::srv::StepSimulation>::SharedPtr step_simulation_service_;
585
586 // Set free joint state service (teleport/reset a free-joint object's pose)
587 rclcpp::CallbackGroup::SharedPtr set_free_joint_state_cb_group_;
588 rclcpp::Service<mujoco_ros2_control_msgs::srv::SetFreeJointState>::SharedPtr set_free_joint_state_service_;
589
590 // Pending steps to execute while paused, and synchronization for blocking callers
591 std::atomic<uint32_t> pending_steps_{ 0 };
592 std::atomic<bool> step_diverged_{ false };
593 std::atomic<bool> steps_interrupted_{ false };
594 std::atomic<bool> keyboard_step_requested_{ false };
595 std::atomic<uint64_t> step_count_{ 0 };
596 std::mutex steps_cv_mutex_;
597 std::condition_variable steps_cv_;
598
599 // Storage for initial state (used for reset_world)
600 std::vector<mjtNum> initial_qpos_;
601 std::vector<mjtNum> initial_qvel_;
602 std::vector<mjtNum> initial_ctrl_;
603
604 // Callback into the HW interface to perform component-side reset bookkeeping.
605 ResetCallback reset_callback_;
606
607 // Callback run immediately before every mj_step(), defaults to nothing.
608 PreStepCallback pre_step_callback_{ [](mjData* /*data*/) {} };
609};
610
611} // namespace mujoco_ros2_control
ROS 2-based container for the mujoco Simulate application.
Definition mujoco_simulation.hpp:134
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:1269
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:773
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:855
std::recursive_mutex & mutex() const
Accessor for the mutex which locks access to the data and model.
Definition mujoco_simulation.hpp:337
void start_physics_thread()
Start the physics thread. Must be called after load_model().
Definition mujoco_simulation.cpp:797
std::function< void(bool fill_initial_state)> ResetCallback
Callback invoked when the simulation's state must be reset.
Definition mujoco_simulation.hpp:146
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:1364
mjData * acquire_data_snapshot()
Borrows the latest completed post-step snapshot of mj_data_ (producer-pays copying).
Definition mujoco_simulation.cpp:1344
void set_pre_step_callback(PreStepCallback callback)
Register the callback run before every mj_step().
Definition mujoco_simulation.cpp:786
void copy_physics_data(mjData *&destination)
Copies mj_data_ into the provided container in a thread safe way.
Definition mujoco_simulation.cpp:1329
void shutdown()
Stop the physics and UI threads if they are running.
Definition mujoco_simulation.cpp:834
bool apply_keyframe(const std::string &keyframe_name)
Apply a keyframe to the simulation by name.
Definition mujoco_simulation.cpp:755
void copy_control_state(ControlState &destination)
Copies the latest per-step control state into the provided container.
Definition mujoco_simulation.cpp:1401
void copy_physics_model(mjModel *&destination)
Copies mj_model_ into the provided container in a thread safe way.
Definition mujoco_simulation.cpp:1315
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:215
mjData * data()
Accessor for the raw mujoco simulation data.
Definition mujoco_simulation.hpp:227
std::function< void(mjData *data)> PreStepCallback
Callback function type the set_pre_step_callback hook.
Definition mujoco_simulation.hpp:155
uint64_t step_count() const
Returns the number of steps takein by the physics simulation.
Definition mujoco_simulation.hpp:348
void overwrite_physics_data(mjData *source)
Copies the provided mjData into mj_data_ in a thread safe way.
Definition mujoco_simulation.cpp:1321
void set_reset_callback(ResetCallback callback)
Register a callback function to be called on reset_world_state.
Definition mujoco_simulation.cpp:781
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:529
Definition realtime_publisher.hpp:56
Definition data.hpp:32
std::chrono::microseconds physics_loop_sleep_duration(double timestep)
Idle sleep used by the physics loop between iterations when not busy-waiting.
Definition mujoco_simulation.hpp:73
Small snapshot of the state the hardware interface needs every control cycle.
Definition mujoco_simulation.hpp:304