diff --git a/python/mujoco/simulate.cc b/python/mujoco/simulate.cc index 36fdf407..35532daa 100644 --- a/python/mujoco/simulate.cc +++ b/python/mujoco/simulate.cc @@ -97,9 +97,9 @@ class SimulateWrapper { } } - void WaitUntilDestroyed() { + void WaitUntilExit() { // TODO: replace with atomic wait when we migrate to C++20 - while (!destroyed_.load()) { + while (simulate_ && simulate_->exitrequest.load() != 2) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } @@ -206,8 +206,7 @@ PYBIND11_MODULE(_simulate, pymodule) { key_callback), scn, cam, opt, pert, is_passive); })) - .def("destroy", &SimulateWrapper::Destroy, - py::call_guard()) + .def("destroy", &SimulateWrapper::Destroy) .def("load_message", CallIfNotNull(&mujoco::Simulate::LoadMessage), py::call_guard()) .def("load", &SimulateWrapper::Load) @@ -266,9 +265,8 @@ PYBIND11_MODULE(_simulate, pymodule) { int value = 0; sim->exitrequest.compare_exchange_strong(value, 1); - wrapper.WaitUntilDestroyed(); - }, - py::call_guard()) + wrapper.WaitUntilExit(); + }) .def_property_readonly("uiloadrequest", CallIfNotNull(+[](mujoco::Simulate& sim) { diff --git a/python/mujoco/viewer.py b/python/mujoco/viewer.py index 80621eea..107ee83f 100644 --- a/python/mujoco/viewer.py +++ b/python/mujoco/viewer.py @@ -99,35 +99,43 @@ class Handle: if sim is not None: sim.exit() - def is_running(self) -> bool: + def _get_sim(self) -> Optional[_Simulate]: sim = self._sim() if sim is not None: - return sim.exitrequest < 2 - return False + try: + return sim if sim.exitrequest == 0 else None + except mujoco.UnexpectedError: + # UnexpectedError is raised when accessing `exitrequest` after the + # underlying simulate instance has been deleted in C++. + return None + return None + + def is_running(self) -> bool: + return self._get_sim() is not None def lock(self): - sim = self._sim() + sim = self._get_sim() if sim is not None: return sim.lock() return contextlib.nullcontext() def sync(self): - sim = self._sim() + sim = self._get_sim() if sim is not None: sim.sync() # locks internally def update_hfield(self, hfieldid: int): - sim = self._sim() + sim = self._get_sim() if sim is not None: sim.update_hfield(hfieldid) # locks internally and blocks until done def update_mesh(self, meshid: int): - sim = self._sim() + sim = self._get_sim() if sim is not None: sim.update_mesh(meshid) # locks internally and blocks until done def update_texture(self, texid: int): - sim = self._sim() + sim = self._get_sim() if sim is not None: sim.update_texture(texid) # locks internally and blocks until done