From 93251f07d921d0505a98993fce124ffccb022a7e Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Mon, 7 Apr 2025 03:53:42 -0700 Subject: [PATCH] Install timers by default in Python bindings PiperOrigin-RevId: 744663306 Change-Id: Ia322ace94d1f269ff9b0275567b2e453acc94843 --- python/mujoco/bindings_test.py | 14 ++++++++++++++ python/mujoco/structs.cc | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/python/mujoco/bindings_test.py b/python/mujoco/bindings_test.py index fb2ed004..3896b8ce 100644 --- a/python/mujoco/bindings_test.py +++ b/python/mujoco/bindings_test.py @@ -1083,6 +1083,20 @@ Euler integrator, semi-implicit in velocity. ): mujoco.mj_forward(self.model, self.data) + def test_timer_installed_by_default(self): + timer_step = mujoco.mjtTimer.mjTIMER_STEP + self.assertEqual(self.data.timer[timer_step].number, 0) + self.assertEqual(self.data.timer[timer_step].duration, 0.0) + + mujoco.mj_step(self.model, self.data) + self.assertEqual(self.data.timer[timer_step].number, 1) + duration_1 = self.data.timer[timer_step].duration + self.assertGreater(duration_1, 0.0) + + mujoco.mj_step(self.model, self.data, 5) + self.assertEqual(self.data.timer[timer_step].number, 6) + self.assertGreater(self.data.timer[timer_step].duration, duration_1) + def test_mjcb_time(self): class CallCounter: diff --git a/python/mujoco/structs.cc b/python/mujoco/structs.cc index 6925b980..77200386 100644 --- a/python/mujoco/structs.cc +++ b/python/mujoco/structs.cc @@ -18,6 +18,7 @@ #include #include +#include // NOLINT(build/c++11) #include #include #include @@ -553,6 +554,16 @@ MjDataWrapper* MjDataWrapper::FromRawPointer(raw::MjData* m) noexcept { } } +namespace { +// default timer callback (seconds) +mjtNum GetTime() { + using Clock = std::chrono::steady_clock; + using Seconds = std::chrono::duration; + static const Clock::time_point tm_start = Clock::now(); + return Seconds(Clock::now() - tm_start).count(); +} +} // namespace + MjDataWrapper::MjWrapper(MjModelWrapper* model) : WrapperBase(InterceptMjErrors(mj_makeData)(model->get()), &MjDataCapsuleDestructor), @@ -582,6 +593,14 @@ MjDataWrapper::MjWrapper(MjModelWrapper* model) throw UnexpectedError( "MjDataRawPointerMap already contains this raw mjData*"); } + + // install default timer if not already installed + { + py::gil_scoped_acquire gil; + if (!mjcb_time) { + mjcb_time = GetTime; + } + } } MjDataWrapper::MjWrapper(const MjDataWrapper& other)