Skip to content

Commit 18a3a66

Browse files
committed
fix: triggering viewer reloads from outside the viewer
1 parent 076acff commit 18a3a66

File tree

3 files changed

+33
-19
lines changed

3 files changed

+33
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
### Fixed
55
* Fixed segfault when changing settings in the option panel of the GUI (caused by a wrong conditional assignment).
6+
* Fixed deadlock when reloading viewers from outside the viewer (not clicking Ctrl-L but using the ROS service).
67

78
<a name="0.10.0"></a>
89
## [0.10.0] - 2025-07-13

mujoco_ros/include/mujoco_ros/viewer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
#include <atomic>
5656
#include <chrono>
5757
#include <condition_variable>
58+
#include <future>
5859
#include <memory>
5960
#include <mutex>
6061
#include <optional>
@@ -235,7 +236,7 @@ class Viewer
235236
} pending_ = {};
236237

237238
ViewerMutex mtx; // Should move to MujocoEnv
238-
std::condition_variable_any cond_loadrequest;
239+
std::optional<std::promise<void>> reload_promise_;
239240

240241
int frames_ = 0;
241242

mujoco_ros/src/viewer.cpp

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,8 +1887,9 @@ Viewer::Viewer(std::unique_ptr<PlatformUIAdapter> platform_ui_adapter, MujocoEnv
18871887
// operations which require holding the mutex, prevents racing with physics thread
18881888
void Viewer::Sync(bool state_only)
18891889
{
1890-
MutexLock lock(this->mtx);
1891-
MutexLock lock_env(env_->physics_thread_mutex_);
1890+
MutexLock lock_env(env_->physics_thread_mutex_, std::defer_lock);
1891+
MutexLock lock(this->mtx, std::defer_lock);
1892+
std::lock(lock_env, lock); // avoid deadlock
18921893
if (!m_ || !d_) {
18931894
return;
18941895
}
@@ -2263,33 +2264,40 @@ void Viewer::LoadMessage(const char *displayed_filename)
22632264

22642265
{
22652266
MutexLock lock(mtx);
2266-
this->loadrequest = 3;
2267+
this->loadrequest.store(3);
22672268
}
22682269
}
22692270

22702271
void Viewer::LoadMessageClear()
22712272
{
22722273
{
22732274
MutexLock lock(mtx);
2274-
this->loadrequest = 0;
2275+
this->loadrequest.store(0);
22752276
}
22762277
}
22772278

22782279
void Viewer::Load(mjModelPtr m, mjDataPtr d, const char *displayed_filename)
22792280
{
2281+
ROS_DEBUG("Model load requested from physics thread");
22802282
this->mnew_ = std::move(m);
22812283
this->dnew_ = std::move(d);
22822284
mju::strcpy_arr(this->filename, displayed_filename);
22832285

2286+
std::future<void> reload_future;
22842287
{
2288+
// Lock mutex to create promise
22852289
MutexLock lock(mtx);
2286-
this->loadrequest = 2;
2287-
2288-
// Wait for the render thread to be done loading
2289-
// so that we know the old model and data's memory can
2290-
// be freed by the other thread (sometimes python)
2291-
cond_loadrequest.wait(lock, [this]() { return this->loadrequest == 0; });
2290+
reload_promise_.emplace();
2291+
reload_future = reload_promise_->get_future();
22922292
}
2293+
this->loadrequest.store(2);
2294+
2295+
// Wait for the render thread to be done loading
2296+
// so that we know the old model and data's memory can
2297+
// be freed by the other thread (sometimes python)
2298+
reload_future.wait();
2299+
reload_promise_.reset();
2300+
ROS_DEBUG("Model load completed in physics thread");
22932301
}
22942302

22952303
void Viewer::LoadOnRenderThread()
@@ -2475,8 +2483,10 @@ void Viewer::LoadOnRenderThread()
24752483

24762484
// clear request
24772485
ROS_DEBUG("Notifying load request complete");
2478-
this->loadrequest = 0;
2479-
cond_loadrequest.notify_all();
2486+
this->loadrequest.store(0);
2487+
if (reload_promise_) {
2488+
reload_promise_->set_value();
2489+
}
24802490

24812491
// set real time index
24822492
int numclicks = sizeof(MujocoEnv::percentRealTime) / sizeof(MujocoEnv::percentRealTime[0]);
@@ -2516,7 +2526,7 @@ void Viewer::Render()
25162526
mjr_rectangle(rect, 0.2f, 0.3f, 0.4f, 1);
25172527

25182528
// label
2519-
if (this->loadrequest) {
2529+
if (this->loadrequest.load()) {
25202530
mjr_overlay(mjFONT_BIG, mjGRID_TOP, smallrect, "LOADING...", nullptr, &this->platform_ui->mjr_context());
25212531
} else {
25222532
char intro_message[Viewer::kMaxFilenameLength];
@@ -2636,9 +2646,9 @@ void Viewer::Render()
26362646
}
26372647

26382648
// show pause/loading label
2639-
if (!this->run || this->loadrequest) {
2649+
if (!this->run || this->loadrequest.load()) {
26402650
char label[30] = { '\0' };
2641-
if (this->loadrequest) {
2651+
if (this->loadrequest.load()) {
26422652
std::snprintf(label, sizeof(label), "LOADING...");
26432653
} else if (this->scrub_index == 0) {
26442654
std::snprintf(label, sizeof(label), "PAUSE");
@@ -2853,10 +2863,12 @@ void Viewer::RenderLoop()
28532863
const MutexLock lock(this->mtx);
28542864

28552865
// Load model (not on first pass, to show "loading" label)
2856-
if (this->loadrequest == 1) {
2866+
if (this->loadrequest.load() == 1) {
2867+
ROS_DEBUG("Model load triggered in render thread");
28572868
this->LoadOnRenderThread();
2858-
} else if (this->loadrequest == 2) {
2859-
this->loadrequest = 1;
2869+
} else if (this->loadrequest.load() == 2) {
2870+
ROS_DEBUG("Model load announced in render thread");
2871+
this->loadrequest.store(1);
28602872
}
28612873

28622874
// Poll and handle events

0 commit comments

Comments
 (0)