diff --git a/python/mujoco/simulate.cc b/python/mujoco/simulate.cc index ec9f2dd3..707a8f86 100644 --- a/python/mujoco/simulate.cc +++ b/python/mujoco/simulate.cc @@ -203,7 +203,11 @@ PYBIND11_MODULE(_simulate, pymodule) { })) .def("destroy", &SimulateWrapper::Destroy, py::call_guard()) + .def("load_message", CallIfNotNull(&mujoco::Simulate::LoadMessage), + py::call_guard()) .def("load", &SimulateWrapper::Load) + .def("load_message_clear", CallIfNotNull(&mujoco::Simulate::LoadMessageClear), + py::call_guard()) .def("sync", CallIfNotNull(&mujoco::Simulate::Sync), py::call_guard()) diff --git a/python/mujoco/viewer.py b/python/mujoco/viewer.py index 67d65e89..48ad9405 100644 --- a/python/mujoco/viewer.py +++ b/python/mujoco/viewer.py @@ -174,9 +174,11 @@ def _reload( ) -> Optional[Tuple[mujoco.MjModel, mujoco.MjData]]: """Internal function for reloading a model in the viewer.""" try: + simulate.load_message('') # path is unknown at this point load_tuple = loader() except Exception as e: # pylint: disable=broad-except simulate.load_error = str(e) + simulate.load_message_clear() else: m, d = load_tuple[:2] diff --git a/simulate/main.cc b/simulate/main.cc index 509f4187..5ccc3ee6 100644 --- a/simulate/main.cc +++ b/simulate/main.cc @@ -259,6 +259,7 @@ void PhysicsLoop(mj::Simulate& sim) { // run until asked to exit while (!sim.exitrequest.load()) { if (sim.droploadrequest.load()) { + sim.LoadMessage(sim.dropfilename); mjModel* mnew = LoadModel(sim.dropfilename, sim); sim.droploadrequest.store(false); @@ -279,10 +280,14 @@ void PhysicsLoop(mj::Simulate& sim) { ctrlnoise = (mjtNum*) malloc(sizeof(mjtNum)*m->nu); mju_zero(ctrlnoise, m->nu); } + else { + sim.LoadMessageClear(); + } } if (sim.uiloadrequest.load()) { sim.uiloadrequest.fetch_sub(1); + sim.LoadMessage(sim.filename); mjModel* mnew = LoadModel(sim.filename, sim); mjData* dnew = nullptr; if (mnew) dnew = mj_makeData(mnew); @@ -301,6 +306,9 @@ void PhysicsLoop(mj::Simulate& sim) { ctrlnoise = static_cast(malloc(sizeof(mjtNum)*m->nu)); mju_zero(ctrlnoise, m->nu); } + else { + sim.LoadMessageClear(); + } } // sleep for 1 ms or yield, to let main thread run @@ -405,6 +413,7 @@ void PhysicsLoop(mj::Simulate& sim) { void PhysicsThread(mj::Simulate* sim, const char* filename) { // request loadmodel if file given (otherwise drag-and-drop) if (filename != nullptr) { + sim->LoadMessage(filename); m = LoadModel(filename, *sim); if (m) d = mj_makeData(m); if (d) { @@ -415,6 +424,8 @@ void PhysicsThread(mj::Simulate* sim, const char* filename) { free(ctrlnoise); ctrlnoise = static_cast(malloc(sizeof(mjtNum)*m->nu)); mju_zero(ctrlnoise, m->nu); + } else { + sim->LoadMessageClear(); } } diff --git a/simulate/simulate.cc b/simulate/simulate.cc index d5ae8836..a20fe6e7 100644 --- a/simulate/simulate.cc +++ b/simulate/simulate.cc @@ -1977,6 +1977,15 @@ void Simulate::Sync() { } //------------------------- Tell the render thread to load a file and wait ------------------------- +void Simulate::LoadMessage(const char* displayed_filename) { + mju::strcpy_arr(this->filename, displayed_filename); + + { + MutexLock lock(mtx); + this->loadrequest = 3; + } +} + void Simulate::Load(mjModel* m, mjData* d, const char* displayed_filename) { this->mnew_ = m; this->dnew_ = d; @@ -1993,6 +2002,15 @@ void Simulate::Load(mjModel* m, mjData* d, const char* displayed_filename) { } } +void Simulate::LoadMessageClear(void) { + { + MutexLock lock(mtx); + this->loadrequest = 0; + } +} + + + //------------------------------------- load mjb or xml model -------------------------------------- void Simulate::LoadOnRenderThread() { this->m_ = this->mnew_; @@ -2167,7 +2185,7 @@ void Simulate::Render() { // label if (this->loadrequest) { - mjr_overlay(mjFONT_BIG, mjGRID_TOPRIGHT, smallrect, "loading", nullptr, + mjr_overlay(mjFONT_BIG, mjGRID_TOPLEFT, smallrect, "loading", nullptr, &this->platform_ui->mjr_context()); } else { char intro_message[Simulate::kMaxFilenameLength]; @@ -2450,7 +2468,7 @@ void Simulate::RenderLoop() { // load model (not on first pass, to show "loading" label) if (this->loadrequest==1) { this->LoadOnRenderThread(); - } else if (this->loadrequest>1) { + } else if (this->loadrequest == 2) { this->loadrequest = 1; } diff --git a/simulate/simulate.h b/simulate/simulate.h index 08be8d22..a1567c0d 100644 --- a/simulate/simulate.h +++ b/simulate/simulate.h @@ -62,10 +62,18 @@ class Simulate { void UpdateMesh(int meshid); void UpdateTexture(int texid); + // Request that the Simulate UI display a "loading" message + // Called prior to Load or LoadMessageClear + void LoadMessage(const char* displayed_filename); + // Request that the Simulate UI thread render a new model - // optionally delete the old model and data when done void Load(mjModel* m, mjData* d, const char* displayed_filename); + // Clear the loading message + // Can be called instead of Load to clear the message without + // requesting the UI load a model + void LoadMessageClear(void); + // functions below are used by the renderthread // load mjb or xml model that has been requested by load() void LoadOnRenderThread(); @@ -173,6 +181,7 @@ class Simulate { std::atomic_int uiloadrequest = 0; // loadrequest + // 3: display a loading message // 2: render thread asked to update its model // 1: showing "loading" label, about to load // 0: model loaded or no load requested.