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:
committed by
Copybara-Service
parent
55c6332f20
commit
6f8bb5ef55
@@ -2161,7 +2161,25 @@ Get compiler timing diagnostics from spec, returns pointer to array of size mjNC
|
|||||||
|
|
||||||
.. mujoco-include:: mjs_isWarning
|
.. mujoco-include:: mjs_isWarning
|
||||||
|
|
||||||
Return 1 if compiler error is a warning.
|
Return 1 if compiler error is a warning. Deprecated: use mjs_numWarnings(s) > 0.
|
||||||
|
|
||||||
|
.. _mjs_numWarnings:
|
||||||
|
|
||||||
|
`mjs_numWarnings <#mjs_numWarnings>`__
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. mujoco-include:: mjs_numWarnings
|
||||||
|
|
||||||
|
Get number of warnings accumulated in the spec.
|
||||||
|
|
||||||
|
.. _mjs_getWarning:
|
||||||
|
|
||||||
|
`mjs_getWarning <#mjs_getWarning>`__
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. mujoco-include:: mjs_getWarning
|
||||||
|
|
||||||
|
Get the i-th warning message (returns nullptr if index out of bounds).
|
||||||
|
|
||||||
.. _Miscellaneous:
|
.. _Miscellaneous:
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ General
|
|||||||
- New types: :ref:`mjtLogLevel`, :ref:`mjtLogTopic`, :ref:`mjLogMessage`, :ref:`mjLogConfig`.
|
- New types: :ref:`mjtLogLevel`, :ref:`mjtLogTopic`, :ref:`mjLogMessage`, :ref:`mjLogConfig`.
|
||||||
- The legacy callbacks :ref:`mju_user_error` and :ref:`mju_user_warning` are deprecated but remain functional.
|
- The legacy callbacks :ref:`mju_user_error` and :ref:`mju_user_warning` are deprecated but remain functional.
|
||||||
|
|
||||||
|
- Added :ref:`mjs_numWarnings` and :ref:`mjs_getWarning` for retrieving all warnings accumulated during model
|
||||||
|
compilation and attachment. Deprecated :ref:`mjs_isWarning` in favor of ``mjs_numWarnings(s) > 0``.
|
||||||
- Improved primal solver convergence under float32. Improvements initially proposed by :github:user:`n3b` in
|
- Improved primal solver convergence under float32. Improvements initially proposed by :github:user:`n3b` in
|
||||||
:issue:`2313` and :github:user:`denzeler-nvidia` in :doc:`MJWarp <mjwarp/index>` pull request
|
:issue:`2313` and :github:user:`denzeler-nvidia` in :doc:`MJWarp <mjwarp/index>` pull request
|
||||||
`1374 <https://github.com/google-deepmind/mujoco_warp/pull/1374>`__.
|
`1374 <https://github.com/google-deepmind/mujoco_warp/pull/1374>`__.
|
||||||
|
|||||||
@@ -3555,6 +3555,8 @@ void mju_writeLog(const char* type, const char* msg);
|
|||||||
const char* mjs_getError(mjSpec* s);
|
const char* mjs_getError(mjSpec* s);
|
||||||
const double* mjs_getTimer(mjSpec* s);
|
const double* mjs_getTimer(mjSpec* s);
|
||||||
int mjs_isWarning(mjSpec* s);
|
int mjs_isWarning(mjSpec* s);
|
||||||
|
int mjs_numWarnings(const mjSpec* spec);
|
||||||
|
const char* mjs_getWarning(const mjSpec* spec, int index);
|
||||||
void mju_zero3(mjtNum res[3]);
|
void mju_zero3(mjtNum res[3]);
|
||||||
void mju_copy3(mjtNum res[3], const mjtNum data[3]);
|
void mju_copy3(mjtNum res[3], const mjtNum data[3]);
|
||||||
void mju_scl3(mjtNum res[3], const mjtNum vec[3], mjtNum scl);
|
void mju_scl3(mjtNum res[3], const mjtNum vec[3], mjtNum scl);
|
||||||
|
|||||||
@@ -1015,9 +1015,14 @@ MJAPI const char* mjs_getError(mjSpec* s);
|
|||||||
// Get compiler timing diagnostics from spec, returns pointer to array of size mjNCTIMER.
|
// Get compiler timing diagnostics from spec, returns pointer to array of size mjNCTIMER.
|
||||||
MJAPI const double* mjs_getTimer(mjSpec* s);
|
MJAPI const double* mjs_getTimer(mjSpec* s);
|
||||||
|
|
||||||
// Return 1 if compiler error is a warning.
|
// Return 1 if compiler error is a warning. Deprecated: use mjs_numWarnings(s) > 0.
|
||||||
MJAPI int mjs_isWarning(mjSpec* s);
|
MJAPI int mjs_isWarning(mjSpec* s);
|
||||||
|
|
||||||
|
// Get number of warnings accumulated in the spec.
|
||||||
|
MJAPI int mjs_numWarnings(const mjSpec* spec);
|
||||||
|
|
||||||
|
// Get the i-th warning message (returns nullptr if index out of bounds).
|
||||||
|
MJAPI const char* mjs_getWarning(const mjSpec* spec, int index);
|
||||||
|
|
||||||
//---------------------------------- Standard math -------------------------------------------------
|
//---------------------------------- Standard math -------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -6486,7 +6486,41 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
doc='Return 1 if compiler error is a warning.',
|
doc='Return 1 if compiler error is a warning. Deprecated: use mjs_numWarnings(s) > 0.', # pylint: disable=line-too-long
|
||||||
|
)),
|
||||||
|
('mjs_numWarnings',
|
||||||
|
FunctionDecl(
|
||||||
|
name='mjs_numWarnings',
|
||||||
|
return_type=ValueType(name='int'),
|
||||||
|
parameters=(
|
||||||
|
FunctionParameterDecl(
|
||||||
|
name='spec',
|
||||||
|
type=PointerType(
|
||||||
|
inner_type=ValueType(name='mjSpec', is_const=True),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
doc='Get number of warnings accumulated in the spec.',
|
||||||
|
)),
|
||||||
|
('mjs_getWarning',
|
||||||
|
FunctionDecl(
|
||||||
|
name='mjs_getWarning',
|
||||||
|
return_type=PointerType(
|
||||||
|
inner_type=ValueType(name='char', is_const=True),
|
||||||
|
),
|
||||||
|
parameters=(
|
||||||
|
FunctionParameterDecl(
|
||||||
|
name='spec',
|
||||||
|
type=PointerType(
|
||||||
|
inner_type=ValueType(name='mjSpec', is_const=True),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FunctionParameterDecl(
|
||||||
|
name='index',
|
||||||
|
type=ValueType(name='int'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
doc='Get the i-th warning message (returns nullptr if index out of bounds).', # pylint: disable=line-too-long
|
||||||
)),
|
)),
|
||||||
('mju_zero3',
|
('mju_zero3',
|
||||||
FunctionDecl(
|
FunctionDecl(
|
||||||
|
|||||||
@@ -597,6 +597,18 @@ class SpecsTest(absltest.TestCase):
|
|||||||
with self.assertRaisesRegex(ValueError, expected_error):
|
with self.assertRaisesRegex(ValueError, expected_error):
|
||||||
spec.compile()
|
spec.compile()
|
||||||
|
|
||||||
|
def test_compile_warnings(self):
|
||||||
|
xml = """
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<flexcomp name="my_flex" type="grid" count="3 3 1" spacing=".05 .05 .05" radius=".01" dim="2"/>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
"""
|
||||||
|
spec = mujoco.MjSpec.from_string(xml)
|
||||||
|
with self.assertWarnsRegex(UserWarning, 'is not rigid'):
|
||||||
|
spec.compile()
|
||||||
|
|
||||||
def test_recompile(self):
|
def test_recompile(self):
|
||||||
# Create a spec.
|
# Create a spec.
|
||||||
spec = mujoco.MjSpec()
|
spec = mujoco.MjSpec()
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
#include "errors.h"
|
#include "errors.h"
|
||||||
#include "indexers.h" // IWYU pragma: keep
|
#include "indexers.h" // IWYU pragma: keep
|
||||||
|
#include "private.h"
|
||||||
#include "raw.h"
|
#include "raw.h"
|
||||||
#include "structs.h" // IWYU pragma: keep
|
#include "structs.h" // IWYU pragma: keep
|
||||||
#include <pybind11/cast.h>
|
#include <pybind11/cast.h>
|
||||||
@@ -118,7 +119,14 @@ raw::MjModel* MjSpec::Compile(mjVFS* vfs) {
|
|||||||
raw::MjModel* m;
|
raw::MjModel* m;
|
||||||
{
|
{
|
||||||
py::gil_scoped_release no_gil;
|
py::gil_scoped_release no_gil;
|
||||||
|
|
||||||
|
// Install a no-op handler to suppress stderr output from warnings.
|
||||||
|
// Compile() installs its own (setjmp/longjmp) handler and then chains to
|
||||||
|
// prev. We want to raise a `warnings.warn`, so pass in a no-op handler.
|
||||||
|
mjfLogHandler prev =
|
||||||
|
_mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
|
||||||
m = mj_compile(ptr, vfs);
|
m = mj_compile(ptr, vfs);
|
||||||
|
_mjPRIVATE_setTlsLogHandler(prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (local_vfs.has_value()) {
|
if (local_vfs.has_value()) {
|
||||||
@@ -127,9 +135,17 @@ raw::MjModel* MjSpec::Compile(mjVFS* vfs) {
|
|||||||
local_vfs = std::nullopt;
|
local_vfs = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m || mjs_isWarning(ptr)) {
|
if (!m) {
|
||||||
throw py::value_error(mjs_getError(ptr));
|
throw py::value_error(mjs_getError(ptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int num_warnings = mjs_numWarnings(ptr);
|
||||||
|
if (num_warnings > 0) {
|
||||||
|
py::object warnings = py::module_::import("warnings");
|
||||||
|
for (int i = 0; i < num_warnings; ++i) {
|
||||||
|
warnings.attr("warn")(mjs_getWarning(ptr, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-4
@@ -483,15 +483,37 @@ const double* mjs_getTimer(mjSpec* s) {
|
|||||||
return modelC->timer;
|
return modelC->timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if model has warnings (but no error)
|
||||||
|
// TODO(tassa): delete this function
|
||||||
// check if model has warnings
|
|
||||||
int mjs_isWarning(mjSpec* s) {
|
int mjs_isWarning(mjSpec* s) {
|
||||||
|
if (!s) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
mjCModel* modelC = static_cast<mjCModel*>(s->element);
|
mjCModel* modelC = static_cast<mjCModel*>(s->element);
|
||||||
return modelC->GetError().warning;
|
return modelC->GetError().message[0] == '\0' &&
|
||||||
|
!modelC->GetWarnings().empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get number of warnings
|
||||||
|
int mjs_numWarnings(const mjSpec* spec) {
|
||||||
|
if (!spec) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const mjCModel* modelC = static_cast<const mjCModel*>(spec->element);
|
||||||
|
return static_cast<int>(modelC->GetWarnings().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the i-th warning message
|
||||||
|
const char* mjs_getWarning(const mjSpec* spec, int index) {
|
||||||
|
if (!spec) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const mjCModel* modelC = static_cast<const mjCModel*>(spec->element);
|
||||||
|
if (index < 0 || index >= static_cast<int>(modelC->GetWarnings().size())) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return modelC->GetWarnings()[index].c_str();
|
||||||
|
}
|
||||||
|
|
||||||
// delete model
|
// delete model
|
||||||
void mj_deleteSpec(mjSpec* s) {
|
void mj_deleteSpec(mjSpec* s) {
|
||||||
|
|||||||
@@ -1546,7 +1546,7 @@ void mjCMesh::Process() {
|
|||||||
for (int i = 0; i < nface(); i++) {
|
for (int i = 0; i < nface(); i++) {
|
||||||
SetBoundingVolume(i, dvert.data());
|
SetBoundingVolume(i, dvert.data());
|
||||||
}
|
}
|
||||||
tree_.CreateBVH();
|
tree_.CreateBVH(model, this);
|
||||||
}
|
}
|
||||||
mesh_timer_[mjCTIMER_MESH_BVH] += Seconds(Clock::now() - t0).count();
|
mesh_timer_[mjCTIMER_MESH_BVH] += Seconds(Clock::now() - t0).count();
|
||||||
|
|
||||||
@@ -5451,7 +5451,7 @@ void mjCFlex::CreateBVH() {
|
|||||||
|
|
||||||
// create hierarchy
|
// create hierarchy
|
||||||
tree.RemoveInactiveVolumes(nbvh);
|
tree.RemoveInactiveVolumes(nbvh);
|
||||||
tree.CreateBVH();
|
tree.CreateBVH(model, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+66
-18
@@ -1239,6 +1239,7 @@ void mjCModel::Clear() {
|
|||||||
hasImplicitPluginElem = false;
|
hasImplicitPluginElem = false;
|
||||||
compiled = false;
|
compiled = false;
|
||||||
errInfo = mjCError();
|
errInfo = mjCError();
|
||||||
|
ClearCompileWarnings();
|
||||||
qpos0.clear();
|
qpos0.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1508,7 +1509,21 @@ const mjCError& mjCModel::GetError() const {
|
|||||||
return errInfo;
|
return errInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add warning to vector (immediate delivery outside compile)
|
||||||
|
void mjCModel::AddWarning(std::string msg, const mjCBase* obj) {
|
||||||
|
if (obj) {
|
||||||
|
msg += "\nElement name '" + obj->name + "', id " + std::to_string(obj->id);
|
||||||
|
if (!obj->info.empty()) {
|
||||||
|
msg += ", " + obj->info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// outside compile: deliver immediately via normal handler chain
|
||||||
|
if (!compiling_) {
|
||||||
|
mju_warning("%s", msg.c_str());
|
||||||
|
}
|
||||||
|
warnings_.push_back(std::move(msg));
|
||||||
|
}
|
||||||
|
|
||||||
// pointer to world body
|
// pointer to world body
|
||||||
mjCBody* mjCModel::GetWorld() {
|
mjCBody* mjCModel::GetWorld() {
|
||||||
@@ -3551,8 +3566,10 @@ void mjCModel::CopyObjects(mjModel* m) {
|
|||||||
if (!pfl->rigid && m->flex_edgeequality[i] == 0 &&
|
if (!pfl->rigid && m->flex_edgeequality[i] == 0 &&
|
||||||
!pfl->edgestiffness && !pfl->edgedamping && !pfl->damping &&
|
!pfl->edgestiffness && !pfl->edgedamping && !pfl->damping &&
|
||||||
pfl->bending.empty()) {
|
pfl->bending.empty()) {
|
||||||
mju_warning("flex '%s' is not rigid and has no equality constraints "
|
AddWarning("flex '" + pfl->name +
|
||||||
"or passive forces", pfl->name.c_str());
|
"' is not rigid and has no equality constraints or "
|
||||||
|
"passive forces",
|
||||||
|
pfl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy bvh data (flex aabb computed dynamically in mjData)
|
// copy bvh data (flex aabb computed dynamically in mjData)
|
||||||
@@ -4212,9 +4229,10 @@ template void mjCModel::RestoreState<mjtNum>(
|
|||||||
// resolve keyframe references
|
// resolve keyframe references
|
||||||
void mjCModel::StoreKeyframes(mjCModel* dest) {
|
void mjCModel::StoreKeyframes(mjCModel* dest) {
|
||||||
if (this != dest && !key_pending_.empty()) {
|
if (this != dest && !key_pending_.empty()) {
|
||||||
mju_warning(
|
dest->AddWarning(
|
||||||
"Child model has pending keyframes. They will not be namespaced correctly. "
|
"Child model has pending keyframes. They will not be namespaced "
|
||||||
"To prevent this, compile the child model before attaching it again.");
|
"correctly. "
|
||||||
|
"To prevent this, compile the child model before attaching it again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not change compilation quantities in case the user wants to recompile preserving the state
|
// do not change compilation quantities in case the user wants to recompile preserving the state
|
||||||
@@ -4633,10 +4651,17 @@ static void compilerLogHandler(const mjLogMessage* msg) {
|
|||||||
mju::strcpy_arr(errortext, msg->subject);
|
mju::strcpy_arr(errortext, msg->subject);
|
||||||
std::longjmp(error_jmp_buf, 1);
|
std::longjmp(error_jmp_buf, 1);
|
||||||
} else if (msg->level == mjLOG_WARNING) {
|
} else if (msg->level == mjLOG_WARNING) {
|
||||||
|
// buffer for structured capture (append, not overwrite)
|
||||||
if (local_warningtext_ptr) {
|
if (local_warningtext_ptr) {
|
||||||
*local_warningtext_ptr = msg->subject;
|
if (!local_warningtext_ptr->empty()) {
|
||||||
|
*local_warningtext_ptr += '\n';
|
||||||
|
}
|
||||||
|
*local_warningtext_ptr += msg->subject;
|
||||||
} else {
|
} else {
|
||||||
mju::strcpy_arr(warningtext, msg->subject);
|
if (warningtext[0]) {
|
||||||
|
mju::strcat_arr(warningtext, "\n");
|
||||||
|
}
|
||||||
|
mju::strcat_arr(warningtext, msg->subject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4661,10 +4686,15 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
|
|||||||
mjModel* volatile model = (m && *m) ? *m : nullptr;
|
mjModel* volatile model = (m && *m) ? *m : nullptr;
|
||||||
mjData* volatile data = nullptr;
|
mjData* volatile data = nullptr;
|
||||||
|
|
||||||
// save log handler
|
// install compiler log handler (captures warnings silently)
|
||||||
mjfLogHandler save_handler = _mjPRIVATE_setTlsLogHandler(compilerLogHandler);
|
mjfLogHandler prev_tls = _mjPRIVATE_setTlsLogHandler(compilerLogHandler);
|
||||||
|
|
||||||
errInfo = mjCError();
|
errInfo = mjCError();
|
||||||
|
|
||||||
|
// set flag so warnings are captured in the spec vector rather than delivered
|
||||||
|
// immediately
|
||||||
|
compiling_ = true;
|
||||||
|
ClearCompileWarnings();
|
||||||
warningtext[0] = 0;
|
warningtext[0] = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -4677,7 +4707,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
|
|||||||
// also include the last warning that was issued. this is useful for
|
// also include the last warning that was issued. this is useful for
|
||||||
// warnings that came out of plugin implementations.
|
// warnings that came out of plugin implementations.
|
||||||
if (warningtext[0]) {
|
if (warningtext[0]) {
|
||||||
error_msg += "\n";
|
error_msg += '\n';
|
||||||
error_msg += warningtext;
|
error_msg += warningtext;
|
||||||
}
|
}
|
||||||
throw mjCError(0, "engine error: %s", error_msg.c_str());
|
throw mjCError(0, "engine error: %s", error_msg.c_str());
|
||||||
@@ -4701,13 +4731,21 @@ mjModel* mjCModel::Compile(const mjVFS* vfs, mjModel** m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// restore handler, return 0
|
// restore handler, return 0
|
||||||
_mjPRIVATE_setTlsLogHandler(save_handler);
|
_mjPRIVATE_setTlsLogHandler(prev_tls);
|
||||||
|
compiling_ = false;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore log handler, mark as compiled, return mjModel
|
// restore log handler
|
||||||
_mjPRIVATE_setTlsLogHandler(save_handler);
|
_mjPRIVATE_setTlsLogHandler(prev_tls);
|
||||||
|
compiling_ = false;
|
||||||
compiled = true;
|
compiled = true;
|
||||||
|
|
||||||
|
// play back compile warnings through the normal handler chain
|
||||||
|
for (int i = num_attach_warnings_; i < warnings_.size(); ++i) {
|
||||||
|
mju_warning("%s", warnings_[i].c_str());
|
||||||
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5353,7 +5391,22 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
|||||||
throw mjCError(0, "could not create mjData");
|
throw mjCError(0, "could not create mjData");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pass compiler warnings into structured warning vector before validation
|
||||||
|
if (warningtext[0]) {
|
||||||
|
std::string warnings(warningtext);
|
||||||
|
std::istringstream stream(warnings);
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(stream, line)) {
|
||||||
|
if (!line.empty()) {
|
||||||
|
AddWarning(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// test forward simulation unless asleep_init is true (potentially expensive)
|
// test forward simulation unless asleep_init is true (potentially expensive)
|
||||||
|
// reset warningtext: engine warnings from validation are not compiler
|
||||||
|
// warnings
|
||||||
|
warningtext[0] = 0;
|
||||||
if (!asleep_init) {
|
if (!asleep_init) {
|
||||||
mj_step(m, d);
|
mj_step(m, d);
|
||||||
}
|
}
|
||||||
@@ -5364,11 +5417,6 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
|
|||||||
m->opt.enableflags = enableflags;
|
m->opt.enableflags = enableflags;
|
||||||
d = nullptr;
|
d = nullptr;
|
||||||
|
|
||||||
// pass warning back
|
|
||||||
if (warningtext[0]) {
|
|
||||||
mju::strcpy_arr(errInfo.message, warningtext);
|
|
||||||
errInfo.warning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// save signature
|
// save signature
|
||||||
m->signature = Signature();
|
m->signature = Signature();
|
||||||
|
|||||||
+25
-3
@@ -249,6 +249,23 @@ class mjCModel : public mjCModel_, private mjSpec {
|
|||||||
bool IsCompiled() const; // is model already compiled
|
bool IsCompiled() const; // is model already compiled
|
||||||
const mjCError& GetError() const; // get reference of error object
|
const mjCError& GetError() const; // get reference of error object
|
||||||
void SetError(const mjCError& error) { errInfo = error; } // set value of error object
|
void SetError(const mjCError& error) { errInfo = error; } // set value of error object
|
||||||
|
void AddWarning(std::string msg, // add warning to vector
|
||||||
|
const mjCBase* obj = nullptr);
|
||||||
|
const std::vector<std::string>& GetWarnings()
|
||||||
|
const { // get accumulated warnings
|
||||||
|
return warnings_;
|
||||||
|
}
|
||||||
|
void ClearWarnings() {
|
||||||
|
warnings_.clear();
|
||||||
|
num_attach_warnings_ = 0;
|
||||||
|
} // clear all warnings
|
||||||
|
void ClearCompileWarnings() {
|
||||||
|
warnings_.resize(num_attach_warnings_);
|
||||||
|
} // clear compile warnings
|
||||||
|
void SetAttachWarningBoundary() { // snapshot attach warning count
|
||||||
|
num_attach_warnings_ = warnings_.size();
|
||||||
|
}
|
||||||
|
|
||||||
mjCBody* GetWorld(); // pointer to world body
|
mjCBody* GetWorld(); // pointer to world body
|
||||||
mjCDef* FindDefault(const std::string& name) const; // find defaults class name
|
mjCDef* FindDefault(const std::string& name) const; // find defaults class name
|
||||||
mjCDef* AddDefault(std::string name, mjCDef* parent = nullptr); // add defaults class to array
|
mjCDef* AddDefault(std::string name, mjCDef* parent = nullptr); // add defaults class to array
|
||||||
@@ -488,10 +505,15 @@ class mjCModel : public mjCModel_, private mjSpec {
|
|||||||
// expand all keyframes in the model
|
// expand all keyframes in the model
|
||||||
void ExpandAllKeyframes();
|
void ExpandAllKeyframes();
|
||||||
|
|
||||||
mjListKeyMap ids; // map from object names to ids
|
mjListKeyMap ids; // map from object names to ids
|
||||||
mjCError errInfo; // last error info
|
mjCError errInfo; // last error info
|
||||||
|
std::vector<std::string>
|
||||||
|
warnings_; // chronological list of non-fatal warnings
|
||||||
|
int num_attach_warnings_ =
|
||||||
|
0; // boundary: [0, n) are attach, [n, size) are compile
|
||||||
|
bool compiling_ = false; // true during Compile()
|
||||||
std::vector<mjKeyInfo> key_pending_; // attached keyframes
|
std::vector<mjKeyInfo> key_pending_; // attached keyframes
|
||||||
bool deepcopy_; // copy objects when attaching
|
bool deepcopy_; // copy objects when attaching
|
||||||
bool attached_ = false; // true if model is attached to a parent model
|
bool attached_ = false; // true if model is attached to a parent model
|
||||||
std::unordered_map<const mjsCompiler*, mjSpec*> compiler2spec_; // map from compiler to spec
|
std::unordered_map<const mjsCompiler*, mjSpec*> compiler2spec_; // map from compiler to spec
|
||||||
std::vector<mjCBase*> detached_; // list of detached objects
|
std::vector<mjCBase*> detached_; // list of detached objects
|
||||||
|
|||||||
+16
-17
@@ -38,12 +38,11 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "lodepng.h"
|
#include "lodepng.h" // NOLINT
|
||||||
#include "cc/array_safety.h"
|
|
||||||
#include "engine/engine_passive.h"
|
|
||||||
#include "engine/engine_support.h"
|
|
||||||
#include <mujoco/mjspec.h>
|
#include <mujoco/mjspec.h>
|
||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
|
#include "cc/array_safety.h"
|
||||||
|
#include "engine/engine_passive.h"
|
||||||
#include "user/user_api.h"
|
#include "user/user_api.h"
|
||||||
#include "user/user_cache.h"
|
#include "user/user_cache.h"
|
||||||
#include "user/user_model.h"
|
#include "user/user_model.h"
|
||||||
@@ -197,7 +196,6 @@ mjCError::mjCError(const mjCBase* obj, const char* msg, const char* str, int pos
|
|||||||
char temp[600];
|
char temp[600];
|
||||||
|
|
||||||
// init
|
// init
|
||||||
warning = false;
|
|
||||||
if (obj || msg) {
|
if (obj || msg) {
|
||||||
mju::sprintf_arr(message, "Error");
|
mju::sprintf_arr(message, "Error");
|
||||||
} else {
|
} else {
|
||||||
@@ -396,13 +394,13 @@ mjCBoundingVolumeHierarchy::AddBoundingVolume(const int* id, int contype, int co
|
|||||||
|
|
||||||
|
|
||||||
// create bounding volume hierarchy
|
// create bounding volume hierarchy
|
||||||
void mjCBoundingVolumeHierarchy::CreateBVH() {
|
void mjCBoundingVolumeHierarchy::CreateBVH(mjCModel* model,
|
||||||
|
const mjCBase* owner) {
|
||||||
std::vector<BVElement> elements;
|
std::vector<BVElement> elements;
|
||||||
Make(elements);
|
Make(elements);
|
||||||
MakeBVH(elements.begin(), elements.end());
|
MakeBVH(elements.begin(), elements.end(), 0, model, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void mjCBoundingVolumeHierarchy::Make(std::vector<BVElement>& elements) {
|
void mjCBoundingVolumeHierarchy::Make(std::vector<BVElement>& elements) {
|
||||||
// precompute the positions of each element in the hierarchy's axes, and drop
|
// precompute the positions of each element in the hierarchy's axes, and drop
|
||||||
// visual-only elements.
|
// visual-only elements.
|
||||||
@@ -424,8 +422,9 @@ void mjCBoundingVolumeHierarchy::Make(std::vector<BVElement>& elements) {
|
|||||||
|
|
||||||
// compute bounding volume hierarchy
|
// compute bounding volume hierarchy
|
||||||
int mjCBoundingVolumeHierarchy::MakeBVH(
|
int mjCBoundingVolumeHierarchy::MakeBVH(
|
||||||
std::vector<BVElement>::iterator elements_begin,
|
std::vector<BVElement>::iterator elements_begin,
|
||||||
std::vector<BVElement>::iterator elements_end, int lev) {
|
std::vector<BVElement>::iterator elements_end, int lev, mjCModel* model,
|
||||||
|
const mjCBase* owner) {
|
||||||
int nelements = elements_end - elements_begin;
|
int nelements = elements_end - elements_begin;
|
||||||
if (nelements == 0) {
|
if (nelements == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -525,11 +524,13 @@ int mjCBoundingVolumeHierarchy::MakeBVH(
|
|||||||
|
|
||||||
// recursive calls
|
// recursive calls
|
||||||
if (m > 0) {
|
if (m > 0) {
|
||||||
child_[2*index + 0] = MakeBVH(elements_begin, elements_begin + m, lev + 1);
|
child_[2 * index + 0] =
|
||||||
|
MakeBVH(elements_begin, elements_begin + m, lev + 1, model, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m != nelements) {
|
if (m != nelements) {
|
||||||
child_[2*index + 1] = MakeBVH(elements_begin + m, elements_end, lev + 1);
|
child_[2 * index + 1] =
|
||||||
|
MakeBVH(elements_begin + m, elements_end, lev + 1, model, owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SHOULD NOT OCCUR
|
// SHOULD NOT OCCUR
|
||||||
@@ -539,14 +540,12 @@ int mjCBoundingVolumeHierarchy::MakeBVH(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lev > mjMAXTREEDEPTH) {
|
if (lev > mjMAXTREEDEPTH) {
|
||||||
mju_warning("max tree depth exceeded in body=%s", name_.c_str());
|
model->AddWarning("max tree depth exceeded", owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//------------------------- class mjCOctree implementation --------------------------------------------
|
//------------------------- class mjCOctree implementation --------------------------------------------
|
||||||
|
|
||||||
void mjCOctree::CopyLevel(int* level) const {
|
void mjCOctree::CopyLevel(int* level) const {
|
||||||
@@ -2631,7 +2630,7 @@ void mjCBody::ComputeBVH() {
|
|||||||
tree.AddBoundingVolume(&geom->id, geom->contype, geom->conaffinity,
|
tree.AddBoundingVolume(&geom->id, geom->contype, geom->conaffinity,
|
||||||
geom->pos, geom->quat, geom->aabb);
|
geom->pos, geom->quat, geom->aabb);
|
||||||
}
|
}
|
||||||
tree.CreateBVH();
|
tree.CreateBVH(model, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -3871,7 +3870,7 @@ void mjCGeom::SetFluidCoefs(void) {
|
|||||||
|
|
||||||
// compute bounding box
|
// compute bounding box
|
||||||
void mjCGeom::ComputeAABB(void) {
|
void mjCGeom::ComputeAABB(void) {
|
||||||
double aamm[6]; // axis-aligned bounding box in (min, max) format
|
double aamm[6]; // axis-aligned bounding box in (min, max) format
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case mjGEOM_HFIELD:
|
case mjGEOM_HFIELD:
|
||||||
aamm[0] = -hfield->size[0];
|
aamm[0] = -hfield->size[0];
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ class [[nodiscard]] mjCError {
|
|||||||
int pos2 = 0);
|
int pos2 = 0);
|
||||||
|
|
||||||
char message[500]; // error message
|
char message[500]; // error message
|
||||||
bool warning; // is this a warning instead of error
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// alternative specifications of frame orientation
|
// alternative specifications of frame orientation
|
||||||
@@ -172,7 +171,7 @@ struct mjCBoundingVolumeHierarchy_ {
|
|||||||
class mjCBoundingVolumeHierarchy : public mjCBoundingVolumeHierarchy_ {
|
class mjCBoundingVolumeHierarchy : public mjCBoundingVolumeHierarchy_ {
|
||||||
public:
|
public:
|
||||||
// make bounding volume hierarchy
|
// make bounding volume hierarchy
|
||||||
void CreateBVH();
|
void CreateBVH(mjCModel* model, const mjCBase* owner);
|
||||||
void Set(double ipos_element[3], double iquat_element[4]);
|
void Set(double ipos_element[3], double iquat_element[4]);
|
||||||
void AllocateBoundingVolumes(int nleaf);
|
void AllocateBoundingVolumes(int nleaf);
|
||||||
void RemoveInactiveVolumes(int nmax);
|
void RemoveInactiveVolumes(int nmax);
|
||||||
@@ -210,7 +209,8 @@ class mjCBoundingVolumeHierarchy : public mjCBoundingVolumeHierarchy_ {
|
|||||||
};
|
};
|
||||||
void Make(std::vector<BVElement>& elements);
|
void Make(std::vector<BVElement>& elements);
|
||||||
int MakeBVH(std::vector<BVElement>::iterator elements_begin,
|
int MakeBVH(std::vector<BVElement>::iterator elements_begin,
|
||||||
std::vector<BVElement>::iterator elements_end, int lev = 0);
|
std::vector<BVElement>::iterator elements_end, int lev,
|
||||||
|
mjCModel* model, const mjCBase* owner);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -58,8 +58,16 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle compile warning
|
// handle compile warning
|
||||||
if (mjs_isWarning(spec.get())) {
|
int num_warnings = mjs_numWarnings(spec.get());
|
||||||
mjCopyError(error, mjs_getError(spec.get()), error_sz);
|
if (num_warnings > 0) {
|
||||||
|
std::string all_warnings;
|
||||||
|
for (int i = 0; i < num_warnings; ++i) {
|
||||||
|
if (!all_warnings.empty()) {
|
||||||
|
all_warnings += '\n';
|
||||||
|
}
|
||||||
|
all_warnings += mjs_getWarning(spec.get(), i);
|
||||||
|
}
|
||||||
|
mjCopyError(error, all_warnings.c_str(), error_sz);
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
error[0] = '\0';
|
error[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ struct ActLimitedTestCase {
|
|||||||
mjtIntegrator integrator;
|
mjtIntegrator integrator;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ParametrizedForwardTest = ::testing::TestWithParam<ActLimitedTestCase>;
|
class ParametrizedForwardTest
|
||||||
|
: public MujocoTest,
|
||||||
|
public ::testing::WithParamInterface<ActLimitedTestCase> {};
|
||||||
|
|
||||||
TEST_P(ParametrizedForwardTest, ActLimited) {
|
TEST_P(ParametrizedForwardTest, ActLimited) {
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
|
|||||||
@@ -1753,6 +1753,8 @@ TEST_F(SensorTest, InsideSiteFlexBody) {
|
|||||||
)";
|
)";
|
||||||
|
|
||||||
char error[1024] = {0};
|
char error[1024] = {0};
|
||||||
|
EXPECT_CALL(mock_warning_handler, Warn(testing::HasSubstr("is not rigid")))
|
||||||
|
.WillOnce(testing::Return());
|
||||||
mjModel* m = LoadModelFromString(xml, error, sizeof(error));
|
mjModel* m = LoadModelFromString(xml, error, sizeof(error));
|
||||||
ASSERT_THAT(m, NotNull()) << error;
|
ASSERT_THAT(m, NotNull()) << error;
|
||||||
mjData* d = mj_makeData(m);
|
mjData* d = mj_makeData(m);
|
||||||
@@ -1839,6 +1841,8 @@ TEST_F(SensorTest, FlexContactSensors) {
|
|||||||
)";
|
)";
|
||||||
|
|
||||||
char error[1024] = {0};
|
char error[1024] = {0};
|
||||||
|
EXPECT_CALL(mock_warning_handler, Warn(testing::HasSubstr("is not rigid")))
|
||||||
|
.WillOnce(testing::Return());
|
||||||
mjModel* m = LoadModelFromString(xml, error, sizeof(error));
|
mjModel* m = LoadModelFromString(xml, error, sizeof(error));
|
||||||
ASSERT_THAT(m, NotNull()) << error;
|
ASSERT_THAT(m, NotNull()) << error;
|
||||||
mjData* d = mj_makeData(m);
|
mjData* d = mj_makeData(m);
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ constexpr int GetExpectedStackUsageBytes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TestMjArrayList, TestMjArrayListSingleThreaded) {
|
class TestMjArrayList : public MujocoTest {};
|
||||||
|
|
||||||
|
TEST_F(TestMjArrayList, TestMjArrayListSingleThreaded) {
|
||||||
std::array<char, 1024> error;
|
std::array<char, 1024> error;
|
||||||
mjModel* m = LoadModelFromString("<mujoco/>", error.data(), error.size());
|
mjModel* m = LoadModelFromString("<mujoco/>", error.data(), error.size());
|
||||||
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error.data();
|
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error.data();
|
||||||
@@ -81,7 +83,7 @@ TEST(TestMjArrayList, TestMjArrayListSingleThreaded) {
|
|||||||
mj_deleteModel(m);
|
mj_deleteModel(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TestMjArrayList, ZeroInitialCapacity) {
|
TEST_F(TestMjArrayList, ZeroInitialCapacity) {
|
||||||
char error[1024];
|
char error[1024];
|
||||||
mjModel* m = LoadModelFromString("<mujoco/>", error, sizeof(error));
|
mjModel* m = LoadModelFromString("<mujoco/>", error, sizeof(error));
|
||||||
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error;
|
ASSERT_THAT(m, NotNull()) << "Failed to load model: " << error;
|
||||||
|
|||||||
+99
-13
@@ -24,7 +24,6 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -33,6 +32,7 @@
|
|||||||
#include <absl/base/attributes.h>
|
#include <absl/base/attributes.h>
|
||||||
#include <absl/base/const_init.h>
|
#include <absl/base/const_init.h>
|
||||||
#include <absl/base/thread_annotations.h>
|
#include <absl/base/thread_annotations.h>
|
||||||
|
#include <absl/strings/match.h>
|
||||||
#include <absl/strings/str_cat.h>
|
#include <absl/strings/str_cat.h>
|
||||||
#include <absl/strings/str_join.h>
|
#include <absl/strings/str_join.h>
|
||||||
#include <absl/synchronization/mutex.h>
|
#include <absl/synchronization/mutex.h>
|
||||||
@@ -41,35 +41,103 @@
|
|||||||
#include "src/xml/xml_global.h"
|
#include "src/xml/xml_global.h"
|
||||||
|
|
||||||
namespace mujoco {
|
namespace mujoco {
|
||||||
|
namespace {
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::Not;
|
||||||
|
using ::testing::Return;
|
||||||
|
using ::testing::Truly;
|
||||||
|
|
||||||
|
// Returns true if the warning matches a known benign warning to ignore.
|
||||||
|
bool IsBenignWarning(const std::string& msg) {
|
||||||
|
static const char* const kBenignWarnings[] = {
|
||||||
|
"is not rigid and has no equality constraints",
|
||||||
|
};
|
||||||
|
for (const char* warning : kBenignWarnings) {
|
||||||
|
if (absl::StrContains(msg, warning)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
thread_local MockWarningHandler* MockWarningHandler::active_handler = nullptr;
|
||||||
|
|
||||||
|
// Registers this handler as the active one.
|
||||||
|
MockWarningHandler::MockWarningHandler() {
|
||||||
|
prev_ = active_handler;
|
||||||
|
active_handler = this;
|
||||||
|
|
||||||
|
// By default, ignore matches in the benign warnings list
|
||||||
|
ON_CALL(*this, Warn(Truly(IsBenignWarning)))
|
||||||
|
.WillByDefault([](const std::string&) {});
|
||||||
|
|
||||||
|
// Fail on all other warnings
|
||||||
|
ON_CALL(*this, Warn(Not(Truly(IsBenignWarning))))
|
||||||
|
.WillByDefault([](const std::string& msg) {
|
||||||
|
ADD_FAILURE() << "mju_user_warning: " << msg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restores the previously active warning handler.
|
||||||
|
MockWarningHandler::~MockWarningHandler() { active_handler = prev_; }
|
||||||
|
|
||||||
|
// Configures the mock warning handler to ignore all warnings.
|
||||||
|
void MockWarningHandler::ExpectWarnings() {
|
||||||
|
EXPECT_CALL(*this, Warn(_)).WillRepeatedly(Return());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the active warning handler.
|
||||||
|
MockWarningHandler* MockWarningHandler::GetActive() { return active_handler; }
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using ::testing::NotNull;
|
using ::testing::NotNull;
|
||||||
|
|
||||||
ABSL_CONST_INIT static absl::Mutex handlers_mutex(absl::kConstInit);
|
ABSL_CONST_INIT static absl::Mutex handlers_mutex(absl::kConstInit);
|
||||||
static int guard_count ABSL_GUARDED_BY(handlers_mutex) = 0;
|
static int guard_count ABSL_GUARDED_BY(handlers_mutex) = 0;
|
||||||
|
static mjfLogHandler prev_log_handler ABSL_GUARDED_BY(handlers_mutex) = nullptr;
|
||||||
|
|
||||||
void default_mj_error_handler(const char* msg) {
|
void default_mj_log_handler(const mjLogMessage* msg) {
|
||||||
FAIL() << "mju_user_error: " << msg;
|
std::string subject = msg->subject;
|
||||||
}
|
if (msg->func) {
|
||||||
|
subject = std::string(msg->func) + ": " + msg->subject;
|
||||||
|
}
|
||||||
|
|
||||||
void default_mj_warning_handler(const char* msg) {
|
if (msg->level == mjLOG_ERROR) {
|
||||||
ADD_FAILURE() << "mju_user_warning: " << msg;
|
if (mju_user_error) {
|
||||||
|
mju_user_error(subject.c_str());
|
||||||
|
} else {
|
||||||
|
FAIL() << "mju_user_error: " << subject;
|
||||||
|
}
|
||||||
|
} else if (msg->level == mjLOG_WARNING) {
|
||||||
|
std::string full_msg = subject;
|
||||||
|
if (msg->body) {
|
||||||
|
full_msg += "\n" + std::string(msg->body);
|
||||||
|
}
|
||||||
|
if (mju_user_warning) {
|
||||||
|
mju_user_warning(full_msg.c_str());
|
||||||
|
} else if (auto* handler = MockWarningHandler::GetActive()) {
|
||||||
|
handler->Warn(full_msg);
|
||||||
|
} else {
|
||||||
|
ADD_FAILURE() << "mju_user_warning: " << full_msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
MujocoErrorTestGuard::MujocoErrorTestGuard() {
|
MujocoErrorTestGuard::MujocoErrorTestGuard() {
|
||||||
absl::MutexLock lock(handlers_mutex);
|
absl::MutexLock lock(handlers_mutex);
|
||||||
if (++guard_count == 1) {
|
if (++guard_count == 1) {
|
||||||
mju_user_error = default_mj_error_handler;
|
prev_log_handler = mju_setLogHandler(default_mj_log_handler);
|
||||||
mju_user_warning = default_mj_warning_handler;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MujocoErrorTestGuard::~MujocoErrorTestGuard() {
|
MujocoErrorTestGuard::~MujocoErrorTestGuard() {
|
||||||
absl::MutexLock lock(handlers_mutex);
|
absl::MutexLock lock(handlers_mutex);
|
||||||
if (--guard_count == 0) {
|
if (--guard_count == 0) {
|
||||||
mju_user_error = nullptr;
|
mju_setLogHandler(prev_log_handler);
|
||||||
mju_user_warning = nullptr;
|
prev_log_handler = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,11 +165,29 @@ mjModel* LoadModelFromString(std::string_view xml, char* error,
|
|||||||
if (spec) {
|
if (spec) {
|
||||||
model = mj_compile(spec, vfs);
|
model = mj_compile(spec, vfs);
|
||||||
|
|
||||||
if (error && (!model || mjs_isWarning(spec))) {
|
if (error) {
|
||||||
strncpy(error, mjs_getError(spec), error_size);
|
if (!model) {
|
||||||
error[error_size - 1] = '\0';
|
strncpy(error, mjs_getError(spec), error_size);
|
||||||
|
error[error_size - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
int num_warnings = mjs_numWarnings(spec);
|
||||||
|
if (num_warnings > 0) {
|
||||||
|
std::string all_warnings;
|
||||||
|
for (int i = 0; i < num_warnings; ++i) {
|
||||||
|
if (!all_warnings.empty()) {
|
||||||
|
all_warnings += '\n';
|
||||||
|
}
|
||||||
|
all_warnings += mjs_getWarning(spec, i);
|
||||||
|
}
|
||||||
|
strncpy(error, all_warnings.c_str(), error_size);
|
||||||
|
error[error_size - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
error[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetGlobalXmlSpec(spec);
|
SetGlobalXmlSpec(spec);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-1
@@ -44,7 +44,7 @@ namespace mujoco {
|
|||||||
inline mjtNum MjTolScale() {
|
inline mjtNum MjTolScale() {
|
||||||
static const mjtNum scale = []() {
|
static const mjtNum scale = []() {
|
||||||
const char* env = std::getenv("MJTOL_SCALE");
|
const char* env = std::getenv("MJTOL_SCALE");
|
||||||
return env ? std::atof(env) : 1.0;
|
return env ? std::strtod(env, nullptr) : 1.0;
|
||||||
}();
|
}();
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,28 @@ class MujocoErrorTestGuard {
|
|||||||
~MujocoErrorTestGuard();
|
~MujocoErrorTestGuard();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Mock handler for capturing and verifying mju_warning logs.
|
||||||
|
class MockWarningHandler {
|
||||||
|
public:
|
||||||
|
// Constructor that registers this handler as the active one.
|
||||||
|
MockWarningHandler();
|
||||||
|
// Destructor that restores the previously active handler.
|
||||||
|
~MockWarningHandler();
|
||||||
|
|
||||||
|
// Mock method called when a warning is intercepted.
|
||||||
|
MOCK_METHOD(void, Warn, (const std::string& msg));
|
||||||
|
|
||||||
|
// Allow any number of warnings without triggering test failure.
|
||||||
|
void ExpectWarnings();
|
||||||
|
|
||||||
|
// Returns the thread-local active mock warning handler.
|
||||||
|
static MockWarningHandler* GetActive();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static thread_local MockWarningHandler* active_handler;
|
||||||
|
MockWarningHandler* prev_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
// A test fixture which simplifies writing tests for the MuJoCo C API.
|
// A test fixture which simplifies writing tests for the MuJoCo C API.
|
||||||
// By default, any MuJoCo operation which triggers a warning or error will
|
// By default, any MuJoCo operation which triggers a warning or error will
|
||||||
// trigger a test failure.
|
// trigger a test failure.
|
||||||
@@ -128,6 +150,9 @@ class MujocoTest : public ::testing::Test {
|
|||||||
}
|
}
|
||||||
~MujocoTest() { mj_freeLastXML(); }
|
~MujocoTest() { mj_freeLastXML(); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MockWarningHandler mock_warning_handler;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MujocoErrorTestGuard error_guard;
|
MujocoErrorTestGuard error_guard;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ TEST_F(MujocoTestTest, MjUserWarningFailsTest) {
|
|||||||
EXPECT_NONFATAL_FAILURE(mju_warning("Warning."), "Warning.");
|
EXPECT_NONFATAL_FAILURE(mju_warning("Warning."), "Warning.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MujocoTestTest, BenignWarningDoesNotFailTest) {
|
||||||
|
// Warnings in the benign list should not trigger test failures
|
||||||
|
mju_warning(
|
||||||
|
"flex 'soft' is not rigid and has no equality constraints "
|
||||||
|
"or passive forces");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(MujocoTestTest, MjUserErrorFailsTest) {
|
TEST_F(MujocoTestTest, MjUserErrorFailsTest) {
|
||||||
EXPECT_FATAL_FAILURE(mju_error("Error."), "Error.");
|
EXPECT_FATAL_FAILURE(mju_error("Error."), "Error.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3291,5 +3291,168 @@ TEST_F(MujocoTest, CompilerTimers) {
|
|||||||
mj_deleteSpec(spec);
|
mj_deleteSpec(spec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------- test compile warning infrastructure --------------------
|
||||||
|
|
||||||
|
TEST_F(MujocoTest, CompileWarningCount) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body name="parent">
|
||||||
|
<geom size="1"/>
|
||||||
|
<flexcomp name="grid" type="grid" count="3 3 1" spacing="0.1 0.1 0.1"
|
||||||
|
dim="2" radius="0.01">
|
||||||
|
<contact internal="false"/>
|
||||||
|
</flexcomp>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
|
||||||
|
ASSERT_THAT(spec, NotNull()) << error.data();
|
||||||
|
|
||||||
|
mjModel* model = mj_compile(spec, 0);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
|
||||||
|
// flex with no passive forces should produce a warning
|
||||||
|
EXPECT_GT(mjs_numWarnings(spec), 0);
|
||||||
|
EXPECT_THAT(mjs_getWarning(spec, 0), HasSubstr("not rigid"));
|
||||||
|
|
||||||
|
mj_deleteModel(model);
|
||||||
|
mj_deleteSpec(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MujocoTest, CompileWarningOutOfBounds) {
|
||||||
|
mjSpec* spec = mj_makeSpec();
|
||||||
|
mjsBody* world = mjs_findBody(spec, "world");
|
||||||
|
mjsGeom* geom = mjs_addGeom(world, 0);
|
||||||
|
geom->size[0] = 1;
|
||||||
|
|
||||||
|
mjModel* model = mj_compile(spec, 0);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
|
||||||
|
// no warnings expected for simple model
|
||||||
|
EXPECT_EQ(mjs_numWarnings(spec), 0);
|
||||||
|
EXPECT_THAT(mjs_getWarning(spec, 0), IsNull());
|
||||||
|
EXPECT_THAT(mjs_getWarning(spec, -1), IsNull());
|
||||||
|
|
||||||
|
// nullptr spec should not crash
|
||||||
|
EXPECT_EQ(mjs_numWarnings(nullptr), 0);
|
||||||
|
EXPECT_THAT(mjs_getWarning(nullptr, 0), IsNull());
|
||||||
|
|
||||||
|
mj_deleteModel(model);
|
||||||
|
mj_deleteSpec(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MujocoTest, RecompileClearsCompileWarnings) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body name="parent">
|
||||||
|
<geom size="1"/>
|
||||||
|
<flexcomp name="grid" type="grid" count="3 3 1" spacing="0.1 0.1 0.1"
|
||||||
|
dim="2" radius="0.01">
|
||||||
|
<contact internal="false"/>
|
||||||
|
</flexcomp>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
|
||||||
|
ASSERT_THAT(spec, NotNull()) << error.data();
|
||||||
|
|
||||||
|
mjModel* model = mj_compile(spec, 0);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
int first_count = mjs_numWarnings(spec);
|
||||||
|
EXPECT_GT(first_count, 0);
|
||||||
|
|
||||||
|
// recompile — warnings should be regenerated, not accumulated
|
||||||
|
mj_deleteModel(model);
|
||||||
|
model = mj_compile(spec, 0);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
EXPECT_EQ(mjs_numWarnings(spec), first_count);
|
||||||
|
|
||||||
|
mj_deleteModel(model);
|
||||||
|
mj_deleteSpec(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MujocoTest, LoadXMLWarningInErrorBuffer) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body name="parent">
|
||||||
|
<geom size="1"/>
|
||||||
|
<flexcomp name="grid" type="grid" count="3 3 1" spacing="0.1 0.1 0.1"
|
||||||
|
dim="2" radius="0.01">
|
||||||
|
<contact internal="false"/>
|
||||||
|
</flexcomp>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
|
||||||
|
// write xml to VFS
|
||||||
|
mjVFS vfs;
|
||||||
|
mj_defaultVFS(&vfs);
|
||||||
|
mj_addBufferVFS(&vfs, "model.xml", xml, sizeof(xml));
|
||||||
|
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
error[0] = '\0';
|
||||||
|
mjModel* model = mj_loadXML("model.xml", &vfs, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
|
||||||
|
// warning should be in the error buffer
|
||||||
|
EXPECT_THAT(error.data(), HasSubstr("not rigid"));
|
||||||
|
|
||||||
|
mj_deleteModel(model);
|
||||||
|
mj_deleteVFS(&vfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MujocoTest, CompileWarningChainedToHandler) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body name="parent">
|
||||||
|
<geom size="1"/>
|
||||||
|
<flexcomp name="grid" type="grid" count="3 3 1" spacing="0.1 0.1 0.1"
|
||||||
|
dim="2" radius="0.01">
|
||||||
|
<contact internal="false"/>
|
||||||
|
</flexcomp>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjSpec* spec = mj_parseXMLString(xml, 0, error.data(), error.size());
|
||||||
|
ASSERT_THAT(spec, NotNull()) << error.data();
|
||||||
|
|
||||||
|
// install a custom log handler that captures warnings
|
||||||
|
std::vector<std::string> captured_warnings;
|
||||||
|
static thread_local std::vector<std::string>* capture_ptr = nullptr;
|
||||||
|
capture_ptr = &captured_warnings;
|
||||||
|
|
||||||
|
// install custom log handler (replaces global, so mock is bypassed)
|
||||||
|
|
||||||
|
mjfLogHandler prev = mju_setLogHandler([](const mjLogMessage* msg) {
|
||||||
|
if (msg->level == mjLOG_WARNING && capture_ptr) {
|
||||||
|
capture_ptr->push_back(msg->subject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mjModel* model = mj_compile(spec, 0);
|
||||||
|
ASSERT_THAT(model, NotNull());
|
||||||
|
|
||||||
|
// restore log handler
|
||||||
|
mju_setLogHandler(prev);
|
||||||
|
capture_ptr = nullptr;
|
||||||
|
|
||||||
|
// chaining should have forwarded warnings to our handler
|
||||||
|
EXPECT_THAT(captured_warnings, testing::Contains(HasSubstr("not rigid")));
|
||||||
|
|
||||||
|
mj_deleteModel(model);
|
||||||
|
mj_deleteSpec(spec);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace mujoco
|
} // namespace mujoco
|
||||||
|
|||||||
@@ -1181,7 +1181,7 @@ TEST_F(MjCGeomTest, IgnoreBadGeomOutsideInertiagrouprange) {
|
|||||||
TEST_F(MjCGeomTest, NanSize) {
|
TEST_F(MjCGeomTest, NanSize) {
|
||||||
// even if the caller ignores warnings, models shouldn't compile with NaN
|
// even if the caller ignores warnings, models shouldn't compile with NaN
|
||||||
// geom sizes
|
// geom sizes
|
||||||
mju_user_warning = nullptr;
|
mock_warning_handler.ExpectWarnings();
|
||||||
static constexpr char xml[] = R"(
|
static constexpr char xml[] = R"(
|
||||||
<mujoco>
|
<mujoco>
|
||||||
<worldbody>
|
<worldbody>
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ class WriteReadCompareTest : public XMLWriterTest,
|
|||||||
TEST_P(WriteReadCompareTest, WriteReadCompare) {
|
TEST_P(WriteReadCompareTest, WriteReadCompare) {
|
||||||
std::string xml = GetParam();
|
std::string xml = GetParam();
|
||||||
|
|
||||||
|
// If this is the flex_line_obj model, expect the 'is not rigid' warning
|
||||||
|
if (absl::StrContains(xml, "flex_line_obj")) {
|
||||||
|
EXPECT_CALL(mock_warning_handler, Warn(testing::HasSubstr("is not rigid")))
|
||||||
|
.WillRepeatedly(testing::Return());
|
||||||
|
}
|
||||||
|
|
||||||
// full precision float printing
|
// full precision float printing
|
||||||
FullFloatPrecision increase_precision;
|
FullFloatPrecision increase_precision;
|
||||||
|
|
||||||
|
|||||||
@@ -115,15 +115,20 @@ EMSCRIPTEN_DECLARE_VAL_TYPE(StringOrNull);
|
|||||||
mju_error("Invalid argument: %s is undefined", #val); \
|
mju_error("Invalid argument: %s is undefined", #val); \
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThrowMujocoErrorToJS(const char* msg) {
|
void ThrowMujocoErrorToJS(const mjLogMessage* msg) {
|
||||||
// Get a handle to the JS global Error constructor function, create a new
|
if (msg->level == mjLOG_ERROR) {
|
||||||
// object instance and then throw the object as an exception using the
|
std::string message = msg->subject;
|
||||||
// val::throw_() helper function.
|
if (msg->func) {
|
||||||
val(val::global("Error").new_(val("MuJoCo Error: " + std::string(msg))))
|
message = std::string(msg->func) + ": " + msg->subject;
|
||||||
.throw_();
|
}
|
||||||
|
// 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() {
|
__attribute__((constructor)) void InitMuJoCoErrorHandler() {
|
||||||
mju_user_error = ThrowMujocoErrorToJS;
|
mju_setLogHandler(ThrowMujocoErrorToJS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a descriptive error message for when a key lookup fails.
|
// Generates a descriptive error message for when a key lookup fails.
|
||||||
@@ -8730,20 +8735,44 @@ std::unique_ptr<MjSpec> parseXMLString_wrapper(const std::string &xml) {
|
|||||||
|
|
||||||
std::unique_ptr<MjModel> mj_compile_wrapper_1(const MjSpec& spec) {
|
std::unique_ptr<MjModel> mj_compile_wrapper_1(const MjSpec& spec) {
|
||||||
mjSpec* spec_ptr = spec.get();
|
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);
|
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));
|
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));
|
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<MjModel> mj_compile_wrapper_2(const MjSpec& spec, const MjVFS& vfs) {
|
std::unique_ptr<MjModel> mj_compile_wrapper_2(const MjSpec& spec, const MjVFS& vfs) {
|
||||||
mjSpec* spec_ptr = spec.get();
|
mjSpec* spec_ptr = spec.get();
|
||||||
mjVFS* vfs_ptr = vfs.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);
|
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));
|
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));
|
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10101,6 +10130,10 @@ std::optional<MjsDefault> mjs_getSpecDefault_wrapper(const MjSpec& s) {
|
|||||||
return MjsDefault(result);
|
return MjsDefault(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string mjs_getWarning_wrapper(const MjSpec& spec, int index) {
|
||||||
|
return std::string(mjs_getWarning(spec.get(), index));
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<MjsWrap> mjs_getWrap_wrapper(const MjsTendon& tendonspec, int i) {
|
std::optional<MjsWrap> mjs_getWrap_wrapper(const MjsTendon& tendonspec, int i) {
|
||||||
mjsWrap* result = mjs_getWrap(tendonspec.get(), i);
|
mjsWrap* result = mjs_getWrap(tendonspec.get(), i);
|
||||||
if (result == nullptr) {
|
if (result == nullptr) {
|
||||||
@@ -10178,6 +10211,10 @@ std::optional<MjsElement> mjs_nextElement_wrapper(const MjSpec& s, const MjsElem
|
|||||||
return MjsElement(result);
|
return MjsElement(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int mjs_numWarnings_wrapper(const MjSpec& spec) {
|
||||||
|
return mjs_numWarnings(spec.get());
|
||||||
|
}
|
||||||
|
|
||||||
std::string mjs_resolveOrientation_wrapper(const val& quat, mjtByte degree, const String& sequence, const MjsOrientation& orientation) {
|
std::string mjs_resolveOrientation_wrapper(const val& quat, mjtByte degree, const String& sequence, const MjsOrientation& orientation) {
|
||||||
CHECK_VAL(sequence);
|
CHECK_VAL(sequence);
|
||||||
UNPACK_VALUE(double, quat);
|
UNPACK_VALUE(double, quat);
|
||||||
@@ -13742,6 +13779,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
|||||||
function("mjs_getParent", &mjs_getParent_wrapper);
|
function("mjs_getParent", &mjs_getParent_wrapper);
|
||||||
function("mjs_getSpec", &mjs_getSpec_wrapper);
|
function("mjs_getSpec", &mjs_getSpec_wrapper);
|
||||||
function("mjs_getSpecDefault", &mjs_getSpecDefault_wrapper);
|
function("mjs_getSpecDefault", &mjs_getSpecDefault_wrapper);
|
||||||
|
function("mjs_getWarning", &mjs_getWarning_wrapper);
|
||||||
function("mjs_getWrap", &mjs_getWrap_wrapper);
|
function("mjs_getWrap", &mjs_getWrap_wrapper);
|
||||||
function("mjs_getWrapCoef", &mjs_getWrapCoef_wrapper);
|
function("mjs_getWrapCoef", &mjs_getWrapCoef_wrapper);
|
||||||
function("mjs_getWrapDivisor", &mjs_getWrapDivisor_wrapper);
|
function("mjs_getWrapDivisor", &mjs_getWrapDivisor_wrapper);
|
||||||
@@ -13753,6 +13791,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
|
|||||||
function("mjs_makeMesh", &mjs_makeMesh_wrapper);
|
function("mjs_makeMesh", &mjs_makeMesh_wrapper);
|
||||||
function("mjs_nextChild", &mjs_nextChild_wrapper);
|
function("mjs_nextChild", &mjs_nextChild_wrapper);
|
||||||
function("mjs_nextElement", &mjs_nextElement_wrapper);
|
function("mjs_nextElement", &mjs_nextElement_wrapper);
|
||||||
|
function("mjs_numWarnings", &mjs_numWarnings_wrapper);
|
||||||
function("mjs_resolveOrientation", &mjs_resolveOrientation_wrapper);
|
function("mjs_resolveOrientation", &mjs_resolveOrientation_wrapper);
|
||||||
function("mjs_sensorDim", &mjs_sensorDim_wrapper);
|
function("mjs_sensorDim", &mjs_sensorDim_wrapper);
|
||||||
function("mjs_setDeepCopy", &mjs_setDeepCopy_wrapper);
|
function("mjs_setDeepCopy", &mjs_setDeepCopy_wrapper);
|
||||||
|
|||||||
@@ -113,15 +113,20 @@ EMSCRIPTEN_DECLARE_VAL_TYPE(StringOrNull);
|
|||||||
mju_error("Invalid argument: %s is undefined", #val); \
|
mju_error("Invalid argument: %s is undefined", #val); \
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThrowMujocoErrorToJS(const char* msg) {
|
void ThrowMujocoErrorToJS(const mjLogMessage* msg) {
|
||||||
// Get a handle to the JS global Error constructor function, create a new
|
if (msg->level == mjLOG_ERROR) {
|
||||||
// object instance and then throw the object as an exception using the
|
std::string message = msg->subject;
|
||||||
// val::throw_() helper function.
|
if (msg->func) {
|
||||||
val(val::global("Error").new_(val("MuJoCo Error: " + std::string(msg))))
|
message = std::string(msg->func) + ": " + msg->subject;
|
||||||
.throw_();
|
}
|
||||||
|
// 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() {
|
__attribute__((constructor)) void InitMuJoCoErrorHandler() {
|
||||||
mju_user_error = ThrowMujocoErrorToJS;
|
mju_setLogHandler(ThrowMujocoErrorToJS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generates a descriptive error message for when a key lookup fails.
|
// 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) {
|
std::unique_ptr<MjModel> mj_compile_wrapper_1(const MjSpec& spec) {
|
||||||
mjSpec* spec_ptr = spec.get();
|
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);
|
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));
|
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));
|
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<MjModel> mj_compile_wrapper_2(const MjSpec& spec, const MjVFS& vfs) {
|
std::unique_ptr<MjModel> mj_compile_wrapper_2(const MjSpec& spec, const MjVFS& vfs) {
|
||||||
mjSpec* spec_ptr = spec.get();
|
mjSpec* spec_ptr = spec.get();
|
||||||
mjVFS* vfs_ptr = vfs.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);
|
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));
|
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));
|
return std::unique_ptr<MjModel>(new MjModel(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user