Refactor compiler warning handling.

Compiler warnings are now accumulated in a vector of strings within the mjSpec object. New API functions `mjs_numWarnings` and `mjs_getWarning` are added to access these warnings. The compiler's log handler now chains warnings to the global log handler, ensuring they are still displayed immediately. Call sites in `mj_loadXML`, `mj_compile`, and the Python and WASM bindings have been updated to use the new warning API.

PiperOrigin-RevId: 933361650
Change-Id: I47cab98a460c57b0898c0a1a43fce2a5b9648eb1
This commit is contained in:
Yuval Tassa
2026-06-16 16:27:52 -07:00
committed by Copybara-Service
parent 55c6332f20
commit 6f8bb5ef55
25 changed files with 640 additions and 89 deletions
+38 -9
View File
@@ -113,15 +113,20 @@ EMSCRIPTEN_DECLARE_VAL_TYPE(StringOrNull);
mju_error("Invalid argument: %s is undefined", #val); \
}
void ThrowMujocoErrorToJS(const char* msg) {
// Get a handle to the JS global Error constructor function, create a new
// object instance and then throw the object as an exception using the
// val::throw_() helper function.
val(val::global("Error").new_(val("MuJoCo Error: " + std::string(msg))))
.throw_();
void ThrowMujocoErrorToJS(const mjLogMessage* msg) {
if (msg->level == mjLOG_ERROR) {
std::string message = msg->subject;
if (msg->func) {
message = std::string(msg->func) + ": " + msg->subject;
}
// Get a handle to the JS global Error constructor function, create a new
// object instance and then throw the object as an exception using the
// val::throw_() helper function.
val(val::global("Error").new_(val("MuJoCo Error: " + message))).throw_();
}
}
__attribute__((constructor)) void InitMuJoCoErrorHandler() {
mju_user_error = ThrowMujocoErrorToJS;
mju_setLogHandler(ThrowMujocoErrorToJS);
}
// Generates a descriptive error message for when a key lookup fails.
@@ -798,20 +803,44 @@ std::unique_ptr<MjSpec> parseXMLString_wrapper(const std::string &xml) {
std::unique_ptr<MjModel> mj_compile_wrapper_1(const MjSpec& spec) {
mjSpec* spec_ptr = spec.get();
// suppress stderr playback: warnings are raised via console.warn() below
mjfLogHandler prev = _mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
mjModel* model = mj_compile(spec_ptr, nullptr);
if (!model || mjs_isWarning(spec_ptr)) {
_mjPRIVATE_setTlsLogHandler(prev);
if (!model) {
mju_error("%s", mjs_getError(spec_ptr));
}
int num_warnings = mjs_numWarnings(spec_ptr);
if (num_warnings > 0) {
for (int i = 0; i < num_warnings; ++i) {
val::global("console").call<void>(
"warn",
val("MuJoCo Warning: " + std::string(mjs_getWarning(spec_ptr, i))));
}
}
return std::unique_ptr<MjModel>(new MjModel(model));
}
std::unique_ptr<MjModel> mj_compile_wrapper_2(const MjSpec& spec, const MjVFS& vfs) {
mjSpec* spec_ptr = spec.get();
mjVFS* vfs_ptr = vfs.get();
// suppress stderr playback: warnings are raised via console.warn() below
mjfLogHandler prev = _mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
mjModel* model = mj_compile(spec_ptr, vfs_ptr);
if (!model || mjs_isWarning(spec_ptr)) {
_mjPRIVATE_setTlsLogHandler(prev);
if (!model) {
mju_error("%s", mjs_getError(spec_ptr));
}
int num_warnings = mjs_numWarnings(spec_ptr);
if (num_warnings > 0) {
for (int i = 0; i < num_warnings; ++i) {
val::global("console").call<void>(
"warn",
val("MuJoCo Warning: " + std::string(mjs_getWarning(spec_ptr, i))));
}
}
return std::unique_ptr<MjModel>(new MjModel(model));
}