Files
Mujoco_WASM/src/experimental/platform/sim/step_control.cc
T
Yuval Tassa 724a6c6623 Replace functionally opaque Viscous Pause with functionally transparent "Viscous posing mode" toggle.
PiperOrigin-RevId: 946286636
Change-Id: I3e542b12bb57c67f3f194d7bcfdffe39de4f2b94
2026-07-11 13:00:59 -07:00

236 lines
6.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2025 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 "experimental/platform/sim/step_control.h"
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <functional>
#include <ratio>
#include <mujoco/mujoco.h>
namespace mujoco::platform {
static mjtNum Timer() {
using Clock = std::chrono::steady_clock;
using Milliseconds = std::chrono::duration<double, std::milli>;
static Clock::time_point start = Clock::now();
return Milliseconds(Clock::now() - start).count();
}
StepControl::StepControl() { mjcb_time = Timer; }
float StepControl::GetSpeedMeasured() const { return speed_measured_; }
float StepControl::GetSpeed() const { return speed_; }
void StepControl::SetSpeed(float speed_percent_real_time) {
float prev_speed = speed_;
speed_ = std::clamp(speed_percent_real_time, .1f, 100.f);
if (speed_ != prev_speed) {
ForceSync();
}
}
void StepControl::ForceSync() { force_sync_ = true; }
void StepControl::GetNoiseParameters(float& ctrl_noise_scale,
float& ctrl_noise_rate) const {
ctrl_noise_scale = ctrl_noise_std_;
ctrl_noise_rate = ctrl_noise_rate_;
}
void StepControl::SetNoiseParameters(float ctrl_noise_scale,
float ctrl_noise_rate) {
ctrl_noise_std_ = ctrl_noise_scale;
ctrl_noise_rate_ = ctrl_noise_rate;
}
void StepControl::SetPauseState(PauseState state) {
pause_state_ = state;
}
StepControl::PauseState StepControl::GetPauseState() const {
return pause_state_;
}
StepControl::Status StepControl::Advance(mjModel* m, mjData* d) {
if (!m) {
return Status::kOk;
}
if (pause_state_ == PauseState::kNormalPaused) {
// When we eventually unpause, we need to make sure we sync to immediately
// and step once. Without this we could step many times before rendering
// resulting in a noticeable delay before the simulation restarts
// (especially for large slowdowns).
force_sync_ = true;
if (!single_step_) {
// Run mj_forward to update rendering and joint sliders.
mj_forward(m, d);
if (pause_update_) {
mju_copy(d->qacc_warmstart, d->qacc, m->nv);
}
return Status::kPaused;
}
single_step_ = false;
}
const Clock::time_point start_cpu = Clock::now();
const double slowdown = 100. / std::clamp<double>(speed_, 0.001, 100.);
double elapsed_cpu = Seconds(start_cpu - sync_cpu_).count();
double elapsed_sim = d->time - sync_sim_;
bool resync = false;
// Resync if we're forced to.
if (force_sync_) {
force_sync_ = false;
resync = true;
}
// Resync if we've never synced.
if (sync_cpu_.time_since_epoch().count() == 0) {
resync = true;
}
// Resync if any elapsed time is negative.
if (elapsed_cpu < 0 || elapsed_sim < 0) {
resync = true;
}
// Resync if the distance from the target simulation time is bigger than
// sync_misalign_ (misalignment condition).
if (std::abs(elapsed_cpu / slowdown - elapsed_sim) > sync_misalign_) {
resync = true;
}
if (resync) {
// Reset sync times.
sync_cpu_ = start_cpu;
sync_sim_ = d->time;
}
// Stepping loop.
while (true) {
const Clock::time_point now_cpu = Clock::now();
elapsed_cpu = Seconds(now_cpu - sync_cpu_).count();
elapsed_sim = d->time - sync_sim_;
// Stop stepping if simulation no longer lags cpu.
if (elapsed_sim * slowdown >= elapsed_cpu) {
return Status::kOk;
}
// Stop stepping if simulation is taking too long to catch up.
// Note: 12ms == 70% of 1/60 seconds/frame.
constexpr Clock::duration kMaxCpuTimeForSim = std::chrono::milliseconds(12);
if (now_cpu - start_cpu >= kMaxCpuTimeForSim) {
// Note: GetSpeed() and GetSpeedMeasured() will be different in this case.
return Status::kOk;
}
// Measure slowdown here in first viable in-sync step. This update location
// is chosen to minimize visual noise caused by changing measurements.
if (elapsed_sim > 0) {
double measured_slowdown = elapsed_cpu / elapsed_sim;
speed_measured_ = 100. / measured_slowdown;
}
mjtNum prev_time = d->time;
InjectNoise(m, d);
if (pre_step_) {
pre_step_(m, d);
}
mj_step(m, d);
if (post_step_) {
post_step_(m, d);
}
if (mjDISABLED(mjDSBL_AUTORESET)) {
for (mjtWarning w : kDivergedWarnings) {
if (d->warning[w].number > 0) {
// Stop stepping if the simulation diverged.
SetPauseState(PauseState::kNormalPaused);
return Status::kDiverged;
}
}
} else {
// Stop stepping if we auto reset.
if (d->time < prev_time) {
return Status::kAutoReset;
}
}
// Stop after one step if we resynced; next iteration will deal with timing.
if (resync) {
return Status::kOk;
}
}
return Status::kDiverged; // Unreachable
}
void StepControl::InjectNoise(const mjModel* m, mjData* d) {
// no noise, return
if (ctrl_noise_std_ <= 0) {
return;
}
// convert rate and scale to discrete time (Ornstein–Uhlenbeck)
mjtNum rate = mju_exp(-m->opt.timestep / ctrl_noise_rate_);
mjtNum scale = ctrl_noise_std_ * mju_sqrt(1 - rate * rate);
for (int i = 0; i < m->nu; i++) {
mjtNum bottom = 0;
mjtNum top = 0;
mjtNum midpoint = 0;
mjtNum halfrange = 1;
if (m->actuator_ctrllimited[i]) {
bottom = m->actuator_ctrlrange[2 * i];
top = m->actuator_ctrlrange[2 * i + 1];
midpoint = 0.5 * (top + bottom); // target of exponential decay
halfrange = 0.5 * (top - bottom); // scales noise
}
// exponential convergence to midpoint at ctrl_noise_rate
d->ctrl[i] = rate * d->ctrl[i] + (1 - rate) * midpoint;
// add noise
d->ctrl[i] += scale * halfrange * mju_standardNormal(nullptr);
// clip to range if limited
if (m->actuator_ctrllimited[i]) {
d->ctrl[i] = mju_clip(d->ctrl[i], bottom, top);
}
}
}
void StepControl::SetPreStepCallback(StepFn step_fn) {
pre_step_ = step_fn;
}
void StepControl::SetPostStepCallback(StepFn step_fn) {
post_step_ = step_fn;
}
} // namespace mujoco::platform