// Copyright 2022 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include "errors.h" #include "structs.h" #include #include #include namespace mujoco::python { namespace { namespace py = ::pybind11; template constexpr inline std::size_t sizeof_arr(const T (&arr)[N]) { return sizeof(arr); } class SimulateWrapper { public: SimulateWrapper(std::unique_ptr platform_ui_adapter, py::object scn, py::object cam, py::object opt, py::object pert, bool fully_managed) : simulate_(new mujoco::Simulate( std::move(platform_ui_adapter), scn.cast().get(), cam.cast().get(), opt.cast().get(), pert.cast().get(), fully_managed)), m_(py::none()), d_(py::none()), scn_(scn), cam_(cam), opt_(opt), pert_(pert) {} ~SimulateWrapper() { Destroy(); } void Destroy() { if (simulate_) { delete simulate_; simulate_ = nullptr; destroyed_.store(1); } } void WaitUntilDestroyed() { // TODO: replace with atomic wait when we migrate to C++20 while (!destroyed_.load()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } void Load(py::object m, py::object d, const std::string& path) { if (!simulate_) { return; } mjModel* m_raw = m.cast().get(); mjData* d_raw = d.cast().get(); { py::gil_scoped_release no_gil; simulate_->Load(m_raw, d_raw, path.c_str()); } m_ = m; d_ = d; m_raw_ = m_raw; d_raw_ = d_raw; } mujoco::Simulate* simulate() { return simulate_; } private: mujoco::Simulate* simulate_; std::atomic_int destroyed_ = 0; // Hold references to keep these Python objects alive for as long as the // simulate object. py::object m_; py::object d_; py::object scn_; py::object cam_; py::object opt_; py::object pert_; mjModel* m_raw_ = nullptr; mjData* d_raw_ = nullptr; }; inline mujoco::Simulate& SimulateRefOrThrow(SimulateWrapper& wrapper) { auto* sim = wrapper.simulate(); if (!sim) { throw UnexpectedError("simulate object is already deleted"); } return *sim; } template inline auto CallIfNotNull(T (*func)(mujoco::Simulate&, Args...)) { return [func](SimulateWrapper& wrapper, Args&&... args) { return func(SimulateRefOrThrow(wrapper), std::forward(args)...); }; } template inline auto CallIfNotNull(void (*func)(mujoco::Simulate&, Args...)) { return [func](SimulateWrapper& wrapper, Args&&... args) -> void { func(SimulateRefOrThrow(wrapper), std::forward(args)...); }; } template inline auto CallIfNotNull(void (mujoco::Simulate::*func)(Args...)) { return [func](SimulateWrapper& wrapper, Args&&... args) -> void { (SimulateRefOrThrow(wrapper).*func)(std::forward(args)...); }; } template inline auto GetIfNotNull(T mujoco::Simulate::*member) { return [member](SimulateWrapper& wrapper) -> T& { return SimulateRefOrThrow(wrapper).*member; }; } template inline auto SetIfNotNull(T mujoco::Simulate::*member) { return [member](SimulateWrapper& wrapper, const T& value) -> void { SimulateRefOrThrow(wrapper).*member = value; }; } PYBIND11_MODULE(_simulate, pymodule) { py::class_(pymodule, "Mutex") .def( "__enter__", [](SimulateMutex& mtx) { mtx.lock(); }, py::call_guard()) .def( "__exit__", [](SimulateMutex& mtx, py::handle, py::handle, py::handle) { mtx.unlock(); }, py::call_guard()); py::class_(pymodule, "Simulate") .def_readonly_static("MAX_GEOM", &mujoco::Simulate::kMaxGeom) .def(py::init([](py::object scn, py::object cam, py::object opt, py::object pert, bool fully_managed) { return std::make_unique( std::make_unique(), scn, cam, opt, pert, fully_managed); })) .def("destroy", &SimulateWrapper::Destroy, py::call_guard()) .def("load", &SimulateWrapper::Load) .def("sync", CallIfNotNull(&mujoco::Simulate::Sync), py::call_guard()) .def("render_loop", CallIfNotNull(&mujoco::Simulate::RenderLoop), py::call_guard()) .def("lock", GetIfNotNull(&mujoco::Simulate::mtx), py::call_guard(), py::return_value_policy::reference_internal) .def_property_readonly("ctrl_noise_std", GetIfNotNull(&mujoco::Simulate::ctrl_noise_std), py::call_guard()) .def_property_readonly("ctrl_noise_rate", GetIfNotNull(&mujoco::Simulate::ctrl_noise_rate), py::call_guard()) .def_property_readonly("real_time_index", GetIfNotNull(&mujoco::Simulate::real_time_index), py::call_guard()) .def_property("speed_changed", GetIfNotNull(&mujoco::Simulate::speed_changed), SetIfNotNull(&mujoco::Simulate::speed_changed), py::call_guard()) .def_property("measured_slowdown", GetIfNotNull(&mujoco::Simulate::measured_slowdown), SetIfNotNull(&mujoco::Simulate::measured_slowdown), py::call_guard()) .def_property_readonly("refresh_rate", GetIfNotNull(&mujoco::Simulate::refresh_rate), py::call_guard()) .def_property_readonly("busywait", GetIfNotNull(&mujoco::Simulate::busywait), py::call_guard()) .def_property_readonly("run", GetIfNotNull(&mujoco::Simulate::run), py::call_guard()) .def_property_readonly("exitrequest", CallIfNotNull(+[](mujoco::Simulate& sim) { return sim.exitrequest.load(); }), py::call_guard()) .def( "exit", [](SimulateWrapper& wrapper) { mujoco::Simulate* sim = wrapper.simulate(); if (!sim) { return; } int value = 0; sim->exitrequest.compare_exchange_strong(value, 1); wrapper.WaitUntilDestroyed(); }, py::call_guard()) .def_property_readonly("uiloadrequest", CallIfNotNull(+[](mujoco::Simulate& sim) { return sim.uiloadrequest.load(); }), py::call_guard()) .def("uiloadrequest_decrement", CallIfNotNull(+[](mujoco::Simulate& sim) { sim.uiloadrequest.fetch_sub(1); }), py::call_guard()) .def("update_hfield", CallIfNotNull(+[](mujoco::Simulate& sim, int hfieldid) { sim.UpdateHField(hfieldid); }), py::call_guard()) .def("update_mesh", CallIfNotNull(+[](mujoco::Simulate& sim, int meshid) { sim.UpdateMesh(meshid); }), py::call_guard()) .def("update_texture", CallIfNotNull(+[](mujoco::Simulate& sim, int texid) { sim.UpdateTexture(texid); }), py::call_guard()) .def_property( "droploadrequest", CallIfNotNull(+[](mujoco::Simulate& sim) { return sim.droploadrequest.load(); }), CallIfNotNull(+[](mujoco::Simulate& sim, bool droploadrequest) { sim.droploadrequest.store(droploadrequest); }), py::call_guard()) .def_property_readonly("dropfilename", GetIfNotNull(&mujoco::Simulate::dropfilename), py::call_guard()) .def_property_readonly("filename", GetIfNotNull(&mujoco::Simulate::filename), py::call_guard()) .def_property( "load_error", GetIfNotNull(&mujoco::Simulate::load_error), CallIfNotNull(+[](mujoco::Simulate& sim, const std::string& error) { const auto max_length = sizeof_arr(sim.load_error); std::strncpy(sim.load_error, error.c_str(), max_length - 1); sim.load_error[max_length - 1] = '\0'; })); pymodule.def("set_glfw_dlhandle", [](std::uintptr_t dlhandle) { mujoco::Glfw(reinterpret_cast(dlhandle)); }); } } // namespace } // namespace mujoco::python