diff --git a/python/mujoco/mjpython/mjpython.mm b/python/mujoco/mjpython/mjpython.mm index db857d04..12f1a6bd 100644 --- a/python/mujoco/mjpython/mjpython.mm +++ b/python/mujoco/mjpython/mjpython.mm @@ -36,6 +36,13 @@ extern char **environ; // for execve // so that we can dlsym and call them from Python via ctypes. __attribute__((used)) void mjpython_hide_dock_icon() { [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; + + // Drain pending Cocoa events on the main thread. The activation policy + // change triggers an asynchronous dock/screen reconfiguration via + // SkyLight. If we return before it's processed, the notification lands + // on a GCD worker thread and hits NSScreen's main-thread assertion. + NSDate* soon = [NSDate dateWithTimeIntervalSinceNow:0.01]; + [[NSRunLoop mainRunLoop] runUntilDate:soon]; } __attribute__((used)) void mjpython_show_dock_icon() { [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; diff --git a/python/mujoco/simulate.cc b/python/mujoco/simulate.cc index 22209510..bf412381 100644 --- a/python/mujoco/simulate.cc +++ b/python/mujoco/simulate.cc @@ -247,6 +247,11 @@ class SimulateWrapper { if (simulate_) { py::gil_scoped_release no_gil; while (atomic.load() != expected) { + // If the viewer is exiting, stop waiting. The render loop will + // never consume the pending request once it has set exitrequest. + if (simulate_->exitrequest.load()) { + return true; + } // TODO(robotics-simulation): replace with `atomic.wait(expected)` when // we migrate python bindings to C++20 (we may need to drop GCC 10). std::this_thread::yield();